From f7a4ce6d6ef768ca15dadfd41156826fdc6b3e7c Mon Sep 17 00:00:00 2001 From: Brandon Nguyen <58405975+Bratah123@users.noreply.github.com> Date: Mon, 13 Jul 2026 18:25:21 -0700 Subject: [PATCH] FEAT!: implement all SWSH -> SIT format --- spirit/game/card_effects/attacks_common.py | 15 + spirit/game/card_effects/passives_common.py | 161 +++- spirit/game/card_effects/pokemon.py | 268 +++++- spirit/game/card_effects/trainers.py | 218 ++++- spirit/game/data_utils.py | 62 +- spirit/game/models/board.py | 10 +- spirit/game/scripts/cards/CEL25/Cosmoem_14.py | 5 +- spirit/game/scripts/cards/CEL25/Cosmog_13.py | 5 +- spirit/game/scripts/cards/CEL25/Dialga_20.py | 9 +- .../cards/CEL25/FlyingPikachuVMAX_7.py | 23 +- .../scripts/cards/CEL25/FlyingPikachuV_6.py | 22 +- spirit/game/scripts/cards/CEL25/Groudon_17.py | 6 +- spirit/game/scripts/cards/CEL25/HoOh_1.py | 7 +- spirit/game/scripts/cards/CEL25/Kyogre_3.py | 25 +- spirit/game/scripts/cards/CEL25/Lugia_22.py | 24 +- spirit/game/scripts/cards/CEL25/Lunala_15.py | 17 +- spirit/game/scripts/cards/CEL25/Mew_11.py | 27 +- spirit/game/scripts/cards/CEL25/Mew_25.py | 27 +- spirit/game/scripts/cards/CEL25/Palkia_4.py | 20 +- spirit/game/scripts/cards/CEL25/Pikachu_5.py | 5 +- .../ProfessorsResearchProfessorOak_23.py | 5 +- spirit/game/scripts/cards/CEL25/Reshiram_2.py | 7 +- .../game/scripts/cards/CEL25/Solgaleo_21.py | 39 +- .../cards/CEL25/SurfingPikachuVMAX_9.py | 5 +- spirit/game/scripts/cards/CEL25/Xerneas_12.py | 46 +- spirit/game/scripts/cards/CEL25/Yveltal_19.py | 22 +- spirit/game/scripts/cards/CEL25/ZacianV_16.py | 19 +- .../game/scripts/cards/CEL25/ZamazentaV_18.py | 17 +- spirit/game/scripts/cards/CEL25/Zekrom_10.py | 16 +- spirit/game/scripts/cards/PGO/Aipom_56.py | 5 +- .../scripts/cards/PGO/AlolanExeggutorV_5.py | 28 +- .../scripts/cards/PGO/AlolanExeggutorV_71.py | 28 +- .../scripts/cards/PGO/AlolanRaticate_42.py | 27 +- .../scripts/cards/PGO/AlolanRattata_41.py | 5 +- spirit/game/scripts/cards/PGO/Ambipom_57.py | 8 +- spirit/game/scripts/cards/PGO/Ariados_7.py | 11 +- spirit/game/scripts/cards/PGO/Articuno_24.py | 13 +- spirit/game/scripts/cards/PGO/Bibarel_60.py | 23 +- spirit/game/scripts/cards/PGO/Bidoof_59.py | 5 +- spirit/game/scripts/cards/PGO/Blanche_64.py | 37 +- spirit/game/scripts/cards/PGO/Blanche_82.py | 37 +- spirit/game/scripts/cards/PGO/Blastoise_17.py | 25 +- spirit/game/scripts/cards/PGO/Blissey_52.py | 17 +- spirit/game/scripts/cards/PGO/Camerupt_14.py | 5 +- spirit/game/scripts/cards/PGO/Candela_65.py | 38 +- spirit/game/scripts/cards/PGO/Candela_83.py | 38 +- spirit/game/scripts/cards/PGO/Chansey_51.py | 5 +- spirit/game/scripts/cards/PGO/Charizard_10.py | 29 +- spirit/game/scripts/cards/PGO/Charmander_8.py | 10 +- spirit/game/scripts/cards/PGO/Charmeleon_9.py | 5 +- .../game/scripts/cards/PGO/ConkeldurrV_40.py | 16 +- .../game/scripts/cards/PGO/ConkeldurrV_73.py | 16 +- .../game/scripts/cards/PGO/ConkeldurrV_74.py | 16 +- spirit/game/scripts/cards/PGO/Ditto_53.py | 5 +- .../scripts/cards/PGO/DragoniteVSTAR_50.py | 26 +- .../scripts/cards/PGO/DragoniteVSTAR_81.py | 26 +- .../game/scripts/cards/PGO/DragoniteV_49.py | 9 +- .../game/scripts/cards/PGO/DragoniteV_76.py | 9 +- spirit/game/scripts/cards/PGO/Eevee_54.py | 18 +- .../game/scripts/cards/PGO/EggIncubator_66.py | 20 +- .../game/scripts/cards/PGO/EggIncubator_87.py | 20 +- spirit/game/scripts/cards/PGO/Golisopod_26.py | 10 +- spirit/game/scripts/cards/PGO/Gyarados_22.py | 15 +- spirit/game/scripts/cards/PGO/Ivysaur_2.py | 9 +- spirit/game/scripts/cards/PGO/Lapras_23.py | 7 +- spirit/game/scripts/cards/PGO/Larvitar_37.py | 5 +- spirit/game/scripts/cards/PGO/Lunatone_34.py | 8 +- .../game/scripts/cards/PGO/LureModule_67.py | 20 +- .../game/scripts/cards/PGO/LureModule_88.py | 20 +- spirit/game/scripts/cards/PGO/Magikarp_21.py | 18 +- .../game/scripts/cards/PGO/MelmetalVMAX_48.py | 10 +- .../game/scripts/cards/PGO/MelmetalVMAX_80.py | 10 +- spirit/game/scripts/cards/PGO/MelmetalV_47.py | 22 +- spirit/game/scripts/cards/PGO/MelmetalV_75.py | 22 +- spirit/game/scripts/cards/PGO/Melmetal_46.py | 5 +- spirit/game/scripts/cards/PGO/Meltan_45.py | 7 +- .../game/scripts/cards/PGO/MewtwoVSTAR_31.py | 29 +- .../game/scripts/cards/PGO/MewtwoVSTAR_79.py | 29 +- .../game/scripts/cards/PGO/MewtwoVSTAR_86.py | 29 +- spirit/game/scripts/cards/PGO/MewtwoV_30.py | 21 +- spirit/game/scripts/cards/PGO/MewtwoV_72.py | 21 +- spirit/game/scripts/cards/PGO/Moltres_12.py | 20 +- spirit/game/scripts/cards/PGO/Natu_32.py | 5 +- spirit/game/scripts/cards/PGO/Numel_13.py | 5 +- spirit/game/scripts/cards/PGO/Onix_36.py | 7 +- spirit/game/scripts/cards/PGO/Pidove_61.py | 5 +- spirit/game/scripts/cards/PGO/Pikachu_27.py | 12 +- spirit/game/scripts/cards/PGO/Pikachu_28.py | 5 +- spirit/game/scripts/cards/PGO/PokStop_68.py | 26 +- spirit/game/scripts/cards/PGO/Pupitar_38.py | 5 +- .../scripts/cards/PGO/RadiantBlastoise_18.py | 46 +- .../scripts/cards/PGO/RadiantVenusaur_4.py | 31 +- spirit/game/scripts/cards/PGO/RareCandy_69.py | 52 +- spirit/game/scripts/cards/PGO/SlakingV_58.py | 14 +- spirit/game/scripts/cards/PGO/SlakingV_77.py | 14 +- spirit/game/scripts/cards/PGO/Slowbro_20.py | 23 +- spirit/game/scripts/cards/PGO/Slowpoke_19.py | 16 +- spirit/game/scripts/cards/PGO/Snorlax_55.py | 14 +- spirit/game/scripts/cards/PGO/Solrock_39.py | 47 +- spirit/game/scripts/cards/PGO/Spark_70.py | 39 +- spirit/game/scripts/cards/PGO/Spark_85.py | 39 +- spirit/game/scripts/cards/PGO/Spinarak_6.py | 7 +- spirit/game/scripts/cards/PGO/Steelix_44.py | 14 +- spirit/game/scripts/cards/PGO/Sylveon_35.py | 15 +- spirit/game/scripts/cards/PGO/Tranquill_62.py | 5 +- spirit/game/scripts/cards/PGO/Tyranitar_43.py | 17 +- spirit/game/scripts/cards/PGO/Unfezant_63.py | 5 +- spirit/game/scripts/cards/PGO/Venusaur_3.py | 36 +- spirit/game/scripts/cards/PGO/Wartortle_16.py | 7 +- spirit/game/scripts/cards/PGO/Wimpod_25.py | 27 +- spirit/game/scripts/cards/PGO/Xatu_33.py | 20 +- spirit/game/scripts/cards/PGO/Zapdos_29.py | 16 +- .../scripts/cards/SWSH1/AirBalloon_213.py | 5 +- .../game/scripts/cards/SWSH1/BigCharm_158.py | 5 +- .../game/scripts/cards/SWSH1/Bisharp_134.py | 9 +- spirit/game/scripts/cards/SWSH1/Blipbug_17.py | 5 +- spirit/game/scripts/cards/SWSH1/Boltund_75.py | 9 +- spirit/game/scripts/cards/SWSH1/Boltund_76.py | 16 +- spirit/game/scripts/cards/SWSH1/CelebiV_1.py | 12 +- .../scripts/cards/SWSH1/Centiskorch_39.py | 18 +- .../game/scripts/cards/SWSH1/Chinchou_68.py | 5 +- .../game/scripts/cards/SWSH1/Cinccino_147.py | 41 +- .../game/scripts/cards/SWSH1/Cinderace_34.py | 25 +- .../game/scripts/cards/SWSH1/Cinderace_35.py | 5 +- .../game/scripts/cards/SWSH1/Cinderace_36.py | 26 +- .../game/scripts/cards/SWSH1/Claydol_103.py | 9 +- .../game/scripts/cards/SWSH1/Clobbopus_111.py | 7 +- .../game/scripts/cards/SWSH1/Cloyster_41.py | 7 +- .../scripts/cards/SWSH1/Copperajah_137.py | 10 +- .../scripts/cards/SWSH1/Corviknight_135.py | 17 +- .../scripts/cards/SWSH1/Corvisquire_151.py | 19 +- .../scripts/cards/SWSH1/CramorantV_155.py | 21 +- .../scripts/cards/SWSH1/CramorantV_198.py | 21 +- .../game/scripts/cards/SWSH1/Cramorant_62.py | 5 +- .../scripts/cards/SWSH1/CrushingHammer_159.py | 21 +- spirit/game/scripts/cards/SWSH1/Cufant_136.py | 5 +- .../game/scripts/cards/SWSH1/DhelmiseV_187.py | 33 +- .../game/scripts/cards/SWSH1/DhelmiseV_9.py | 33 +- spirit/game/scripts/cards/SWSH1/Dottler_18.py | 5 +- spirit/game/scripts/cards/SWSH1/Drampa_149.py | 5 +- .../game/scripts/cards/SWSH1/Drapion_122.py | 9 +- spirit/game/scripts/cards/SWSH1/Drednaw_61.py | 15 +- .../game/scripts/cards/SWSH1/Dubwool_154.py | 8 +- spirit/game/scripts/cards/SWSH1/Durant_8.py | 13 +- .../game/scripts/cards/SWSH1/Eldegoss_21.py | 20 +- .../cards/SWSH1/EnergyRetrieval_160.py | 7 +- .../scripts/cards/SWSH1/EnergySearch_161.py | 7 +- .../scripts/cards/SWSH1/EnergySwitch_162.py | 20 +- .../scripts/cards/SWSH1/Ferrothorn_131.py | 24 +- .../game/scripts/cards/SWSH1/Frosmoth_64.py | 36 +- .../cards/SWSH1/GalarianLinoone_118.py | 5 +- .../scripts/cards/SWSH1/GalarianMeowth_127.py | 5 +- .../cards/SWSH1/GalarianObstagoon_119.py | 35 +- .../cards/SWSH1/GalarianPerrserker_128.py | 13 +- .../scripts/cards/SWSH1/GalarianPonyta_81.py | 7 +- .../cards/SWSH1/GalarianRapidash_82.py | 8 +- .../cards/SWSH1/GalarianStunfisk_132.py | 21 +- .../cards/SWSH1/GalarianZigzagoon_117.py | 19 +- .../game/scripts/cards/SWSH1/Galvantula_71.py | 7 +- spirit/game/scripts/cards/SWSH1/Gastly_83.py | 5 +- spirit/game/scripts/cards/SWSH1/Gengar_85.py | 47 +- spirit/game/scripts/cards/SWSH1/Goldeen_46.py | 5 +- .../game/scripts/cards/SWSH1/Gossifleur_20.py | 5 +- .../game/scripts/cards/SWSH1/Grapploct_113.py | 43 +- .../game/scripts/cards/SWSH1/GreatBall_164.py | 21 +- spirit/game/scripts/cards/SWSH1/Grookey_10.py | 5 +- spirit/game/scripts/cards/SWSH1/Haunter_84.py | 7 +- spirit/game/scripts/cards/SWSH1/Heatmor_26.py | 7 +- .../game/scripts/cards/SWSH1/Hitmonchan_95.py | 5 +- .../game/scripts/cards/SWSH1/Hitmonlee_94.py | 5 +- .../game/scripts/cards/SWSH1/Hoothoot_143.py | 5 +- .../scripts/cards/SWSH1/HyperPotion_166.py | 27 +- .../game/scripts/cards/SWSH1/IndeedeeV_192.py | 15 +- .../game/scripts/cards/SWSH1/IndeedeeV_91.py | 15 +- .../game/scripts/cards/SWSH1/Inteleon_58.py | 5 +- .../game/scripts/cards/SWSH1/Inteleon_59.py | 34 +- spirit/game/scripts/cards/SWSH1/KeldeoV_53.py | 7 +- spirit/game/scripts/cards/SWSH1/Kingler_44.py | 7 +- spirit/game/scripts/cards/SWSH1/Krabby_42.py | 5 +- spirit/game/scripts/cards/SWSH1/Krabby_43.py | 5 +- spirit/game/scripts/cards/SWSH1/Lanturn_69.py | 21 +- .../scripts/cards/SWSH1/LaprasVMAX_203.py | 7 +- .../game/scripts/cards/SWSH1/LaprasVMAX_50.py | 7 +- .../game/scripts/cards/SWSH1/LaprasV_189.py | 50 +- spirit/game/scripts/cards/SWSH1/LaprasV_49.py | 50 +- spirit/game/scripts/cards/SWSH1/Lapras_48.py | 21 +- .../game/scripts/cards/SWSH1/LuckyEgg_167.py | 18 +- .../game/scripts/cards/SWSH1/LumBerry_168.py | 27 +- spirit/game/scripts/cards/SWSH1/Mantine_52.py | 10 +- spirit/game/scripts/cards/SWSH1/Maractus_7.py | 5 +- spirit/game/scripts/cards/SWSH1/Mawile_129.py | 12 +- .../scripts/cards/SWSH1/MetalSaucer_170.py | 42 +- .../scripts/cards/SWSH1/MetalSaucer_214.py | 42 +- .../game/scripts/cards/SWSH1/Minccino_145.py | 16 +- .../scripts/cards/SWSH1/MorpekoVMAX_204.py | 5 +- .../scripts/cards/SWSH1/MorpekoVMAX_80.py | 5 +- .../game/scripts/cards/SWSH1/MorpekoV_190.py | 20 +- .../game/scripts/cards/SWSH1/MorpekoV_79.py | 20 +- spirit/game/scripts/cards/SWSH1/Morpeko_78.py | 5 +- .../game/scripts/cards/SWSH1/Mudsdale_106.py | 5 +- spirit/game/scripts/cards/SWSH1/Munna_87.py | 5 +- .../game/scripts/cards/SWSH1/Musharna_88.py | 9 +- spirit/game/scripts/cards/SWSH1/Nickit_125.py | 11 +- .../game/scripts/cards/SWSH1/Ninetales_23.py | 11 +- .../game/scripts/cards/SWSH1/Noctowl_144.py | 29 +- .../game/scripts/cards/SWSH1/Orbeetle_19.py | 34 +- .../scripts/cards/SWSH1/OrdinaryRod_215.py | 5 +- spirit/game/scripts/cards/SWSH1/Pikachu_65.py | 10 +- .../game/scripts/cards/SWSH1/Pincurchin_77.py | 16 +- spirit/game/scripts/cards/SWSH1/PokKid_173.py | 9 +- .../game/scripts/cards/SWSH1/Pokgear30_174.py | 23 +- .../scripts/cards/SWSH1/PokmonCatcher_175.py | 22 +- .../scripts/cards/SWSH1/Polteageist_90.py | 23 +- spirit/game/scripts/cards/SWSH1/Potion_177.py | 6 +- .../game/scripts/cards/SWSH1/Qwilfish_51.py | 23 +- spirit/game/scripts/cards/SWSH1/Raboot_32.py | 11 +- .../game/scripts/cards/SWSH1/RareCandy_180.py | 52 +- .../game/scripts/cards/SWSH1/RegirockV_104.py | 7 +- spirit/game/scripts/cards/SWSH1/Rhydon_98.py | 5 +- spirit/game/scripts/cards/SWSH1/Rhyhorn_97.py | 5 +- .../game/scripts/cards/SWSH1/Rhyperior_99.py | 26 +- .../game/scripts/cards/SWSH1/Rillaboom_14.py | 7 +- .../game/scripts/cards/SWSH1/Rillaboom_15.py | 7 +- spirit/game/scripts/cards/SWSH1/Roselia_2.py | 5 +- spirit/game/scripts/cards/SWSH1/Roselia_3.py | 5 +- spirit/game/scripts/cards/SWSH1/Roserade_4.py | 13 +- .../game/scripts/cards/SWSH1/RotomBike_181.py | 11 +- .../game/scripts/cards/SWSH1/SableyeV_120.py | 10 +- .../game/scripts/cards/SWSH1/SableyeV_194.py | 10 +- .../game/scripts/cards/SWSH1/Salandit_27.py | 7 +- .../game/scripts/cards/SWSH1/Salazzle_28.py | 9 +- .../scripts/cards/SWSH1/Sandaconda_109.py | 5 +- .../scripts/cards/SWSH1/Sandaconda_110.py | 11 +- .../game/scripts/cards/SWSH1/Scorbunny_30.py | 5 +- spirit/game/scripts/cards/SWSH1/Seaking_47.py | 17 +- .../game/scripts/cards/SWSH1/Shellder_40.py | 5 +- .../game/scripts/cards/SWSH1/Silicobra_107.py | 5 +- .../game/scripts/cards/SWSH1/Sinistea_89.py | 11 +- .../scripts/cards/SWSH1/SitrusBerry_182.py | 25 +- .../game/scripts/cards/SWSH1/Sizzlipede_38.py | 5 +- .../game/scripts/cards/SWSH1/Skorupi_121.py | 7 +- .../scripts/cards/SWSH1/SnorlaxVMAX_142.py | 5 +- .../scripts/cards/SWSH1/SnorlaxVMAX_206.py | 5 +- .../game/scripts/cards/SWSH1/SnorlaxV_141.py | 16 +- .../game/scripts/cards/SWSH1/SnorlaxV_197.py | 16 +- .../cards/SWSH1/StonjournerVMAX_116.py | 32 +- .../cards/SWSH1/StonjournerVMAX_205.py | 32 +- .../scripts/cards/SWSH1/StonjournerV_115.py | 5 +- .../scripts/cards/SWSH1/StonjournerV_193.py | 5 +- .../scripts/cards/SWSH1/Stonjourner_114.py | 5 +- .../game/scripts/cards/SWSH1/Sudowoodo_100.py | 8 +- .../game/scripts/cards/SWSH1/TapuKokoV_72.py | 14 +- .../game/scripts/cards/SWSH1/Thievul_126.py | 20 +- .../game/scripts/cards/SWSH1/Thwackey_12.py | 8 +- .../game/scripts/cards/SWSH1/Thwackey_13.py | 5 +- .../game/scripts/cards/SWSH1/TorkoalV_188.py | 20 +- .../game/scripts/cards/SWSH1/TorkoalV_24.py | 20 +- .../game/scripts/cards/SWSH1/Toxicroak_124.py | 7 +- .../game/scripts/cards/SWSH1/Turtonator_29.py | 5 +- .../game/scripts/cards/SWSH1/VictiniV_25.py | 45 +- .../scripts/cards/SWSH1/VitalityBand_185.py | 5 +- .../game/scripts/cards/SWSH1/Whimsicott_6.py | 25 +- .../scripts/cards/SWSH1/WobbuffetV_191.py | 27 +- .../game/scripts/cards/SWSH1/WobbuffetV_86.py | 27 +- spirit/game/scripts/cards/SWSH1/Wooloo_152.py | 5 +- spirit/game/scripts/cards/SWSH1/Yamper_73.py | 5 +- .../game/scripts/cards/SWSH1/ZacianV_138.py | 31 +- .../game/scripts/cards/SWSH1/ZacianV_195.py | 37 +- .../game/scripts/cards/SWSH1/ZacianV_211.py | 37 +- .../scripts/cards/SWSH1/ZamazentaV_139.py | 14 +- .../scripts/cards/SWSH1/ZamazentaV_196.py | 14 +- .../scripts/cards/SWSH1/ZamazentaV_212.py | 14 +- spirit/game/scripts/cards/SWSH10/Absol_97.py | 15 +- spirit/game/scripts/cards/SWSH10/Azelf_67.py | 11 +- .../scripts/cards/SWSH10/Bastiodon_110.py | 9 +- .../game/scripts/cards/SWSH10/BeedrillV_1.py | 27 +- .../scripts/cards/SWSH10/BeedrillV_160.py | 27 +- .../scripts/cards/SWSH10/BeedrillV_161.py | 27 +- .../game/scripts/cards/SWSH10/Bisharp_116.py | 19 +- .../game/scripts/cards/SWSH10/Bronzong_112.py | 19 +- .../game/scripts/cards/SWSH10/Bronzor_111.py | 11 +- .../cards/SWSH10/CancelingCologne_136.py | 18 +- .../game/scripts/cards/SWSH10/Chatot_129.py | 8 +- spirit/game/scripts/cards/SWSH10/Choy_137.py | 12 +- spirit/game/scripts/cards/SWSH10/Choy_182.py | 12 +- spirit/game/scripts/cards/SWSH10/Choy_200.py | 12 +- spirit/game/scripts/cards/SWSH10/Combee_11.py | 9 +- .../game/scripts/cards/SWSH10/Cranidos_76.py | 5 +- .../game/scripts/cards/SWSH10/Cyndaquil_23.py | 9 +- .../scripts/cards/SWSH10/DarkPatch_139.py | 36 +- .../scripts/cards/SWSH10/DarkraiVSTAR_99.py | 20 +- .../game/scripts/cards/SWSH10/DarkraiV_98.py | 7 +- spirit/game/scripts/cards/SWSH10/Dewott_42.py | 5 +- .../game/scripts/cards/SWSH10/Diancie_68.py | 24 +- .../game/scripts/cards/SWSH10/Drifblim_64.py | 14 +- .../game/scripts/cards/SWSH10/Drifloon_63.py | 5 +- spirit/game/scripts/cards/SWSH10/Eevee_119.py | 39 +- .../scripts/cards/SWSH10/EnergyLoto_140.py | 22 +- .../scripts/cards/SWSH10/FeatherBall_141.py | 16 +- .../cards/SWSH10/GalarianMrRimeV_49.py | 9 +- .../game/scripts/cards/SWSH10/Gallade_62.py | 28 +- .../scripts/cards/SWSH10/GapejawBog_142.py | 12 +- .../scripts/cards/SWSH10/GarchompV_117.py | 11 +- .../scripts/cards/SWSH10/GarchompV_178.py | 11 +- .../game/scripts/cards/SWSH10/Glaceon_38.py | 19 +- .../game/scripts/cards/SWSH10/Glameow_127.py | 7 +- .../game/scripts/cards/SWSH10/Golduck_29.py | 40 +- spirit/game/scripts/cards/SWSH10/Grant_144.py | 19 +- spirit/game/scripts/cards/SWSH10/Grant_185.py | 19 +- spirit/game/scripts/cards/SWSH10/Grant_203.py | 19 +- .../scripts/cards/SWSH10/GutsyPickaxe_145.py | 34 +- .../scripts/cards/SWSH10/HeatranVMAX_191.py | 24 +- .../scripts/cards/SWSH10/HeatranVMAX_26.py | 24 +- .../game/scripts/cards/SWSH10/HeatranV_165.py | 9 +- .../game/scripts/cards/SWSH10/HeatranV_25.py | 9 +- .../game/scripts/cards/SWSH10/Heracross_8.py | 5 +- .../game/scripts/cards/SWSH10/Hippowdon_80.py | 5 +- .../cards/SWSH10/HisuianArcanine_71.py | 19 +- .../scripts/cards/SWSH10/HisuianAvalugg_48.py | 14 +- .../cards/SWSH10/HisuianBasculegion_44.py | 14 +- .../cards/SWSH10/HisuianBasculin_43.py | 5 +- .../cards/SWSH10/HisuianBraviary_132.py | 7 +- .../cards/SWSH10/HisuianDecidueyeVSTAR_195.py | 22 +- .../cards/SWSH10/HisuianDecidueyeVSTAR_84.py | 22 +- .../cards/SWSH10/HisuianDecidueyeV_173.py | 13 +- .../cards/SWSH10/HisuianDecidueyeV_83.py | 13 +- .../cards/SWSH10/HisuianDecidueye_82.py | 7 +- .../cards/SWSH10/HisuianElectrode_3.py | 5 +- .../cards/SWSH10/HisuianGrowlithe_70.py | 5 +- .../cards/SWSH10/HisuianLilligantVSTAR_18.py | 48 +- .../cards/SWSH10/HisuianLilligantVSTAR_190.py | 48 +- .../cards/SWSH10/HisuianLilligantV_162.py | 5 +- .../cards/SWSH10/HisuianLilligantV_163.py | 5 +- .../cards/SWSH10/HisuianLilligantV_17.py | 5 +- .../cards/SWSH10/HisuianLilligant_16.py | 14 +- .../cards/SWSH10/HisuianOverqwil_90.py | 9 +- .../cards/SWSH10/HisuianOverqwil_91.py | 7 +- .../cards/SWSH10/HisuianQwilfish_89.py | 5 +- .../cards/SWSH10/HisuianSamurottVSTAR_102.py | 28 +- .../cards/SWSH10/HisuianSamurottVSTAR_197.py | 28 +- .../cards/SWSH10/HisuianSamurottVSTAR_209.py | 28 +- .../cards/SWSH10/HisuianSamurottV_101.py | 21 +- .../cards/SWSH10/HisuianSamurottV_176.py | 21 +- .../cards/SWSH10/HisuianSamurott_100.py | 10 +- .../cards/SWSH10/HisuianSneaslerV_174.py | 16 +- .../cards/SWSH10/HisuianSneaslerV_175.py | 16 +- .../cards/SWSH10/HisuianSneaslerV_94.py | 16 +- .../cards/SWSH10/HisuianSneasler_93.py | 12 +- .../SWSH10/HisuianTyphlosionVSTAR_193.py | 30 +- .../cards/SWSH10/HisuianTyphlosionVSTAR_54.py | 30 +- .../cards/SWSH10/HisuianTyphlosionV_169.py | 20 +- .../cards/SWSH10/HisuianTyphlosionV_53.py | 20 +- .../cards/SWSH10/HisuianTyphlosion_52.py | 30 +- .../scripts/cards/SWSH10/HisuianVoltorb_2.py | 30 +- .../game/scripts/cards/SWSH10/Hoothoot_120.py | 20 +- spirit/game/scripts/cards/SWSH10/Irida_147.py | 5 +- spirit/game/scripts/cards/SWSH10/Irida_204.py | 5 +- .../game/scripts/cards/SWSH10/JirachiV_170.py | 48 +- .../cards/SWSH10/JubilifeVillage_148.py | 19 +- .../cards/SWSH10/JubilifeVillage_212.py | 19 +- spirit/game/scripts/cards/SWSH10/Keldeo_45.py | 5 +- spirit/game/scripts/cards/SWSH10/Kirlia_61.py | 5 +- .../scripts/cards/SWSH10/KleavorVSTAR_196.py | 22 +- .../game/scripts/cards/SWSH10/KleavorV_87.py | 17 +- .../game/scripts/cards/SWSH10/Kleavor_85.py | 7 +- .../game/scripts/cards/SWSH10/Kleavor_86.py | 14 +- .../game/scripts/cards/SWSH10/Kricketot_9.py | 5 +- .../scripts/cards/SWSH10/Kricketune_10.py | 24 +- .../game/scripts/cards/SWSH10/Leafeon_13.py | 45 +- .../game/scripts/cards/SWSH10/LucarioV_78.py | 5 +- .../game/scripts/cards/SWSH10/LuxrayV_168.py | 33 +- .../game/scripts/cards/SWSH10/LuxrayV_50.py | 33 +- .../scripts/cards/SWSH10/MachampVMAX_194.py | 13 +- .../scripts/cards/SWSH10/MachampVMAX_73.py | 13 +- .../game/scripts/cards/SWSH10/MachampV_171.py | 11 +- .../game/scripts/cards/SWSH10/MachampV_172.py | 11 +- .../game/scripts/cards/SWSH10/MachampV_72.py | 11 +- .../scripts/cards/SWSH10/Magnemite_105.py | 9 +- .../game/scripts/cards/SWSH10/Magneton_106.py | 10 +- .../scripts/cards/SWSH10/Magnezone_107.py | 7 +- .../game/scripts/cards/SWSH10/Mamoswine_33.py | 14 +- .../game/scripts/cards/SWSH10/Mantine_34.py | 39 +- .../game/scripts/cards/SWSH10/Mesprit_66.py | 26 +- .../game/scripts/cards/SWSH10/Mightyena_96.py | 38 +- .../game/scripts/cards/SWSH10/Miltank_126.py | 17 +- .../game/scripts/cards/SWSH10/Mismagius_59.py | 25 +- .../game/scripts/cards/SWSH10/Noctowl_121.py | 13 +- .../scripts/cards/SWSH10/OranguruV_133.py | 20 +- .../scripts/cards/SWSH10/OranguruV_179.py | 20 +- .../SWSH10/OriginFormeDialgaVSTAR_114.py | 9 +- .../SWSH10/OriginFormeDialgaVSTAR_198.py | 9 +- .../SWSH10/OriginFormeDialgaVSTAR_210.py | 9 +- .../cards/SWSH10/OriginFormeDialgaV_113.py | 6 +- .../cards/SWSH10/OriginFormeDialgaV_177.py | 6 +- .../SWSH10/OriginFormePalkiaVSTAR_192.py | 42 +- .../SWSH10/OriginFormePalkiaVSTAR_208.py | 42 +- .../cards/SWSH10/OriginFormePalkiaVSTAR_40.py | 42 +- .../cards/SWSH10/OriginFormePalkiaV_167.py | 30 +- .../cards/SWSH10/OriginFormePalkiaV_39.py | 30 +- .../game/scripts/cards/SWSH10/Pawniard_115.py | 5 +- .../game/scripts/cards/SWSH10/Petilil_15.py | 5 +- .../game/scripts/cards/SWSH10/Piloswine_32.py | 5 +- .../game/scripts/cards/SWSH10/Poochyena_95.py | 5 +- .../game/scripts/cards/SWSH10/Psyduck_28.py | 12 +- .../game/scripts/cards/SWSH10/Purugly_128.py | 7 +- .../cards/SWSH10/RadiantHawlucha_81.py | 23 +- .../scripts/cards/SWSH10/RadiantHeatran_27.py | 5 +- spirit/game/scripts/cards/SWSH10/Ralts_60.py | 5 +- .../game/scripts/cards/SWSH10/Rampardos_77.py | 5 +- .../game/scripts/cards/SWSH10/Rapidash_22.py | 7 +- spirit/game/scripts/cards/SWSH10/Rowlet_19.py | 5 +- .../game/scripts/cards/SWSH10/Rufflet_131.py | 5 +- spirit/game/scripts/cards/SWSH10/Scyther_5.py | 5 +- .../game/scripts/cards/SWSH10/Shaymin_14.py | 12 +- .../game/scripts/cards/SWSH10/Shieldon_109.py | 5 +- .../cards/SWSH10/SpicySeasonedCurry_151.py | 15 +- .../game/scripts/cards/SWSH10/Stantler_125.py | 5 +- .../game/scripts/cards/SWSH10/StarmieV_166.py | 19 +- .../game/scripts/cards/SWSH10/StarmieV_30.py | 19 +- .../game/scripts/cards/SWSH10/Sudowoodo_74.py | 20 +- .../cards/SWSH10/SupereffectiveGlasses_152.py | 5 +- .../scripts/cards/SWSH10/SweetHoney_153.py | 19 +- spirit/game/scripts/cards/SWSH10/Swinub_31.py | 7 +- .../scripts/cards/SWSH10/Teddiursa_122.py | 21 +- .../cards/SWSH10/TempleofSinnoh_155.py | 13 +- .../cards/SWSH10/TempleofSinnoh_214.py | 13 +- .../game/scripts/cards/SWSH10/Thievul_104.py | 28 +- .../game/scripts/cards/SWSH10/Togekiss_57.py | 14 +- spirit/game/scripts/cards/SWSH10/Togepi_55.py | 14 +- .../game/scripts/cards/SWSH10/Togetic_56.py | 14 +- .../scripts/cards/SWSH10/TrekkingShoes_215.py | 5 +- .../cards/SWSH10/UnidentifiedFossil_157.py | 9 +- .../game/scripts/cards/SWSH10/Ursaluna_124.py | 8 +- .../game/scripts/cards/SWSH10/Ursaring_123.py | 5 +- spirit/game/scripts/cards/SWSH10/Uxie_65.py | 5 +- .../game/scripts/cards/SWSH10/Vespiquen_12.py | 27 +- .../scripts/cards/SWSH10/VirizionV_164.py | 37 +- .../cards/SWSH10/WaitandSeeTurbo_158.py | 21 +- .../game/scripts/cards/SWSH10/Whiscash_36.py | 5 +- .../game/scripts/cards/SWSH10/WyrdeerV_134.py | 29 +- .../game/scripts/cards/SWSH10/WyrdeerV_180.py | 29 +- .../game/scripts/cards/SWSH10/Wyrdeer_69.py | 19 +- spirit/game/scripts/cards/SWSH10/Yanmega_7.py | 7 +- .../game/scripts/cards/SWSH11/Abomasnow_43.py | 5 +- .../cards/SWSH11/AerodactylVSTAR_199.py | 12 +- spirit/game/scripts/cards/SWSH11/Aipom_144.py | 18 +- .../game/scripts/cards/SWSH11/Ambipom_145.py | 26 +- .../game/scripts/cards/SWSH11/ArcPhone_152.py | 60 +- spirit/game/scripts/cards/SWSH11/Arezu_153.py | 16 +- spirit/game/scripts/cards/SWSH11/Arezu_189.py | 16 +- spirit/game/scripts/cards/SWSH11/Arezu_204.py | 16 +- .../game/scripts/cards/SWSH11/Ariados_113.py | 23 +- .../game/scripts/cards/SWSH11/Banette_73.py | 43 +- .../scripts/cards/SWSH11/Barbaracle_107.py | 15 +- .../game/scripts/cards/SWSH11/Beautifly_8.py | 22 +- .../scripts/cards/SWSH11/Bouffalant_148.py | 26 +- .../scripts/cards/SWSH11/BoxofDisaster_154.py | 31 +- .../scripts/cards/SWSH11/BoxofDisaster_214.py | 31 +- .../game/scripts/cards/SWSH11/Bronzong_126.py | 13 +- .../game/scripts/cards/SWSH11/Carbink_108.py | 9 +- spirit/game/scripts/cards/SWSH11/Cascoon_9.py | 14 +- .../scripts/cards/SWSH11/Chandelure_26.py | 12 +- .../game/scripts/cards/SWSH11/Clefable_63.py | 14 +- .../game/scripts/cards/SWSH11/Clefairy_62.py | 51 +- .../cards/SWSH11/CollapsedStadium_215.py | 7 +- .../cards/SWSH11/ColresssExperiment_190.py | 5 +- .../cards/SWSH11/ColresssExperiment_205.py | 5 +- .../game/scripts/cards/SWSH11/Cresselia_74.py | 18 +- .../scripts/cards/SWSH11/DamagePump_156.py | 33 +- .../scripts/cards/SWSH11/DarkPatch_216.py | 36 +- .../game/scripts/cards/SWSH11/Darkrai_120.py | 9 +- .../game/scripts/cards/SWSH11/DelphoxV_173.py | 31 +- .../game/scripts/cards/SWSH11/DelphoxV_27.py | 31 +- .../game/scripts/cards/SWSH11/Dewgong_34.py | 29 +- .../game/scripts/cards/SWSH11/Dottler_19.py | 5 +- .../scripts/cards/SWSH11/DrapionVSTAR_119.py | 28 +- .../scripts/cards/SWSH11/DrapionVSTAR_200.py | 28 +- .../game/scripts/cards/SWSH11/DrapionV_182.py | 7 +- spirit/game/scripts/cards/SWSH11/Dustox_10.py | 9 +- .../game/scripts/cards/SWSH11/Eelektrik_60.py | 19 +- .../scripts/cards/SWSH11/Eelektross_61.py | 8 +- .../game/scripts/cards/SWSH11/Electrike_54.py | 7 +- .../scripts/cards/SWSH11/EnamorusV_178.py | 45 +- .../game/scripts/cards/SWSH11/EnamorusV_82.py | 45 +- .../game/scripts/cards/SWSH11/Falinks_110.py | 11 +- .../game/scripts/cards/SWSH11/Fantina_157.py | 23 +- .../game/scripts/cards/SWSH11/Fantina_191.py | 23 +- .../game/scripts/cards/SWSH11/Fantina_206.py | 23 +- .../game/scripts/cards/SWSH11/Finneon_40.py | 46 +- .../cards/SWSH11/GalarianPerrserkerV_129.py | 8 +- .../cards/SWSH11/GalarianPerrserkerV_183.py | 8 +- .../cards/SWSH11/GalarianPerrserkerV_184.py | 8 +- .../cards/SWSH11/GalarianStunfisk_127.py | 21 +- .../game/scripts/cards/SWSH11/GalladeV_181.py | 12 +- spirit/game/scripts/cards/SWSH11/Gastly_64.py | 5 +- .../scripts/cards/SWSH11/Gastrodon_102.py | 8 +- spirit/game/scripts/cards/SWSH11/Gengar_66.py | 24 +- .../scripts/cards/SWSH11/GiratinaVSTAR_131.py | 39 +- .../scripts/cards/SWSH11/GiratinaVSTAR_201.py | 39 +- .../scripts/cards/SWSH11/GiratinaVSTAR_212.py | 39 +- .../scripts/cards/SWSH11/GiratinaV_130.py | 31 +- .../scripts/cards/SWSH11/GiratinaV_185.py | 31 +- .../scripts/cards/SWSH11/GiratinaV_186.py | 31 +- .../game/scripts/cards/SWSH11/Glastrier_51.py | 18 +- spirit/game/scripts/cards/SWSH11/Gligar_95.py | 5 +- .../game/scripts/cards/SWSH11/Gliscor_96.py | 18 +- spirit/game/scripts/cards/SWSH11/Gloom_2.py | 5 +- .../game/scripts/cards/SWSH11/Greedent_151.py | 16 +- .../game/scripts/cards/SWSH11/Hariyama_98.py | 14 +- .../game/scripts/cards/SWSH11/Haunter_65.py | 5 +- .../cards/SWSH11/HisuianArcanine_84.py | 5 +- .../cards/SWSH11/HisuianBasculegion_45.py | 18 +- .../cards/SWSH11/HisuianBasculin_44.py | 5 +- .../cards/SWSH11/HisuianElectrodeV_172.py | 15 +- .../cards/SWSH11/HisuianGoodraVSTAR_136.py | 23 +- .../cards/SWSH11/HisuianGoodraVSTAR_202.py | 23 +- .../cards/SWSH11/HisuianGoodraV_135.py | 8 +- .../cards/SWSH11/HisuianGoodraV_187.py | 8 +- .../scripts/cards/SWSH11/HisuianGoodra_134.py | 20 +- .../cards/SWSH11/HisuianGrowlithe_83.py | 7 +- .../cards/SWSH11/HisuianSliggoo_133.py | 5 +- .../cards/SWSH11/HisuianZoroarkVSTAR_147.py | 27 +- .../cards/SWSH11/HisuianZoroarkVSTAR_203.py | 27 +- .../cards/SWSH11/HisuianZoroarkVSTAR_213.py | 27 +- .../cards/SWSH11/HisuianZoroarkV_146.py | 12 +- .../scripts/cards/SWSH11/HisuianZoroark_76.py | 6 +- .../scripts/cards/SWSH11/HisuianZorua_75.py | 5 +- .../scripts/cards/SWSH11/Honchkrow_115.py | 19 +- spirit/game/scripts/cards/SWSH11/Hoopa_122.py | 14 +- spirit/game/scripts/cards/SWSH11/Horsea_35.py | 5 +- spirit/game/scripts/cards/SWSH11/Inkay_121.py | 5 +- spirit/game/scripts/cards/SWSH11/Inkay_77.py | 5 +- spirit/game/scripts/cards/SWSH11/Iscan_158.py | 12 +- spirit/game/scripts/cards/SWSH11/Iscan_192.py | 12 +- spirit/game/scripts/cards/SWSH11/Iscan_207.py | 12 +- spirit/game/scripts/cards/SWSH11/Jynx_68.py | 16 +- .../game/scripts/cards/SWSH11/Kingdra_37.py | 22 +- .../game/scripts/cards/SWSH11/Komala_149.py | 30 +- .../scripts/cards/SWSH11/KyuremVMAX_197.py | 43 +- .../scripts/cards/SWSH11/KyuremVMAX_49.py | 43 +- .../game/scripts/cards/SWSH11/KyuremV_174.py | 27 +- .../game/scripts/cards/SWSH11/KyuremV_48.py | 27 +- spirit/game/scripts/cards/SWSH11/Lady_159.py | 9 +- spirit/game/scripts/cards/SWSH11/Lady_193.py | 9 +- spirit/game/scripts/cards/SWSH11/Lady_208.py | 9 +- .../scripts/cards/SWSH11/LakeAcuity_160.py | 21 +- .../game/scripts/cards/SWSH11/Lampent_25.py | 16 +- .../game/scripts/cards/SWSH11/Landorus_105.py | 5 +- spirit/game/scripts/cards/SWSH11/Litleo_28.py | 7 +- .../game/scripts/cards/SWSH11/Litwick_24.py | 5 +- .../game/scripts/cards/SWSH11/Lumineon_41.py | 5 +- .../game/scripts/cards/SWSH11/Luvdisc_38.py | 11 +- .../game/scripts/cards/SWSH11/Machamp_88.py | 40 +- .../game/scripts/cards/SWSH11/Magcargo_22.py | 17 +- .../game/scripts/cards/SWSH11/Magearna_128.py | 17 +- .../cards/SWSH11/MagnezoneVSTAR_198.py | 10 +- .../scripts/cards/SWSH11/MagnezoneVSTAR_57.py | 10 +- .../scripts/cards/SWSH11/MagnezoneV_175.py | 8 +- .../scripts/cards/SWSH11/MagnezoneV_56.py | 8 +- .../game/scripts/cards/SWSH11/Makuhita_97.py | 7 +- .../game/scripts/cards/SWSH11/Malamar_78.py | 15 +- .../game/scripts/cards/SWSH11/Manectric_55.py | 5 +- spirit/game/scripts/cards/SWSH11/Mawile_71.py | 20 +- .../game/scripts/cards/SWSH11/Medicham_100.py | 22 +- .../game/scripts/cards/SWSH11/Meditite_99.py | 5 +- .../game/scripts/cards/SWSH11/Mienshao_104.py | 5 +- .../game/scripts/cards/SWSH11/Mimikyu_80.py | 18 +- .../cards/SWSH11/MissFortuneSisters_164.py | 25 +- .../cards/SWSH11/MissFortuneSisters_194.py | 25 +- .../cards/SWSH11/MissFortuneSisters_209.py | 25 +- spirit/game/scripts/cards/SWSH11/MrMime_67.py | 32 +- .../game/scripts/cards/SWSH11/Nuzleaf_12.py | 14 +- .../game/scripts/cards/SWSH11/Orbeetle_20.py | 30 +- .../scripts/cards/SWSH11/PanicMask_165.py | 17 +- spirit/game/scripts/cards/SWSH11/Paras_4.py | 5 +- .../game/scripts/cards/SWSH11/Parasect_5.py | 21 +- .../game/scripts/cards/SWSH11/PidgeotV_137.py | 23 +- .../game/scripts/cards/SWSH11/PidgeotV_188.py | 23 +- .../game/scripts/cards/SWSH11/Pikachu_52.py | 13 +- .../game/scripts/cards/SWSH11/Politoed_32.py | 21 +- .../game/scripts/cards/SWSH11/Poliwag_30.py | 5 +- .../game/scripts/cards/SWSH11/Poliwhirl_31.py | 5 +- .../game/scripts/cards/SWSH11/Poliwrath_85.py | 24 +- .../game/scripts/cards/SWSH11/Porygon2_141.py | 26 +- .../game/scripts/cards/SWSH11/PorygonZ_142.py | 3 +- .../game/scripts/cards/SWSH11/Porygon_140.py | 31 +- spirit/game/scripts/cards/SWSH11/Pyroar_29.py | 18 +- .../cards/SWSH11/RadiantGardevoir_69.py | 11 +- .../SWSH11/RadiantHisuianSneasler_123.py | 11 +- .../cards/SWSH11/RadiantSteelix_124.py | 24 +- spirit/game/scripts/cards/SWSH11/Raichu_53.py | 14 +- .../scripts/cards/SWSH11/Relicanth_101.py | 6 +- spirit/game/scripts/cards/SWSH11/Rhydon_90.py | 5 +- .../game/scripts/cards/SWSH11/Rhyhorn_89.py | 5 +- .../game/scripts/cards/SWSH11/Rhyperior_91.py | 24 +- spirit/game/scripts/cards/SWSH11/Riley_166.py | 24 +- .../game/scripts/cards/SWSH11/Rockruff_109.py | 5 +- .../game/scripts/cards/SWSH11/Roselia_14.py | 7 +- .../game/scripts/cards/SWSH11/Roserade_15.py | 25 +- .../game/scripts/cards/SWSH11/RotomV_176.py | 40 +- .../game/scripts/cards/SWSH11/RotomV_177.py | 40 +- spirit/game/scripts/cards/SWSH11/RotomV_58.py | 40 +- spirit/game/scripts/cards/SWSH11/Seadra_36.py | 19 +- spirit/game/scripts/cards/SWSH11/Seedot_11.py | 5 +- .../game/scripts/cards/SWSH11/Seviper_116.py | 6 +- .../game/scripts/cards/SWSH11/Shiftry_13.py | 24 +- spirit/game/scripts/cards/SWSH11/Silcoon_7.py | 11 +- spirit/game/scripts/cards/SWSH11/Slugma_21.py | 19 +- .../game/scripts/cards/SWSH11/Spectrier_81.py | 20 +- .../game/scripts/cards/SWSH11/Spinarak_112.py | 7 +- .../scripts/cards/SWSH11/Spiritomb_117.py | 28 +- .../scripts/cards/SWSH11/Stonjourner_111.py | 45 +- .../game/scripts/cards/SWSH11/Sudowoodo_94.py | 23 +- spirit/game/scripts/cards/SWSH11/Swanna_47.py | 9 +- .../game/scripts/cards/SWSH11/Thorton_167.py | 6 +- .../game/scripts/cards/SWSH11/Thorton_195.py | 6 +- .../game/scripts/cards/SWSH11/Thorton_210.py | 6 +- .../game/scripts/cards/SWSH11/ToolBox_168.py | 33 +- .../game/scripts/cards/SWSH11/Torkoal_23.py | 7 +- .../game/scripts/cards/SWSH11/Trevenant_17.py | 20 +- spirit/game/scripts/cards/SWSH11/Tynamo_59.py | 13 +- .../game/scripts/cards/SWSH11/Vileplume_3.py | 19 +- spirit/game/scripts/cards/SWSH11/Volo_169.py | 27 +- spirit/game/scripts/cards/SWSH11/Volo_196.py | 27 +- spirit/game/scripts/cards/SWSH11/Volo_211.py | 27 +- .../scripts/cards/SWSH11/WindupArm_170.py | 17 +- spirit/game/scripts/cards/SWSH11/Wurmple_6.py | 14 +- .../cards/SWSH12/AlolanVulpixVSTAR_197.py | 38 +- .../cards/SWSH12/AlolanVulpixVSTAR_34.py | 38 +- .../scripts/cards/SWSH12/AlolanVulpixV_173.py | 5 +- .../scripts/cards/SWSH12/AlolanVulpixV_33.py | 5 +- .../game/scripts/cards/SWSH12/Altaria_143.py | 24 +- .../game/scripts/cards/SWSH12/Amoonguss_12.py | 27 +- .../game/scripts/cards/SWSH12/Anorith_95.py | 26 +- .../game/scripts/cards/SWSH12/Araquanid_48.py | 21 +- .../game/scripts/cards/SWSH12/Arcanine_20.py | 16 +- spirit/game/scripts/cards/SWSH12/Ariados_4.py | 22 +- .../game/scripts/cards/SWSH12/Armaldo_96.py | 13 +- .../game/scripts/cards/SWSH12/Articuno_36.py | 18 +- spirit/game/scripts/cards/SWSH12/Baltoy_93.py | 9 +- .../game/scripts/cards/SWSH12/Beheeyem_80.py | 6 +- .../game/scripts/cards/SWSH12/Beldum_117.py | 16 +- .../game/scripts/cards/SWSH12/Braixen_26.py | 13 +- .../game/scripts/cards/SWSH12/Buneary_144.py | 7 +- .../scripts/cards/SWSH12/ChesnaughtV_15.py | 21 +- .../scripts/cards/SWSH12/ChesnaughtV_171.py | 21 +- .../game/scripts/cards/SWSH12/Chimecho_74.py | 25 +- .../game/scripts/cards/SWSH12/Claydol_94.py | 30 +- .../game/scripts/cards/SWSH12/Cobalion_126.py | 41 +- .../game/scripts/cards/SWSH12/Crobat_105.py | 22 +- .../game/scripts/cards/SWSH12/Dedenne_85.py | 18 +- .../game/scripts/cards/SWSH12/Delphox_27.py | 17 +- .../game/scripts/cards/SWSH12/Donphan_92.py | 12 +- .../game/scripts/cards/SWSH12/Dragapult_89.py | 42 +- .../scripts/cards/SWSH12/Dragonair_130.py | 7 +- .../scripts/cards/SWSH12/Dragonite_131.py | 6 +- .../game/scripts/cards/SWSH12/Dratini_129.py | 5 +- spirit/game/scripts/cards/SWSH12/Dreepy_87.py | 5 +- .../game/scripts/cards/SWSH12/Drowzee_60.py | 7 +- .../game/scripts/cards/SWSH12/Duosion_77.py | 14 +- spirit/game/scripts/cards/SWSH12/Durant_13.py | 6 +- spirit/game/scripts/cards/SWSH12/Emolga_54.py | 9 +- spirit/game/scripts/cards/SWSH12/Feebas_39.py | 23 +- .../game/scripts/cards/SWSH12/Fennekin_25.py | 9 +- .../game/scripts/cards/SWSH12/Froslass_43.py | 24 +- .../scripts/cards/SWSH12/GapejawBog_213.py | 13 +- .../game/scripts/cards/SWSH12/Gardevoir_69.py | 14 +- spirit/game/scripts/cards/SWSH12/Glalie_42.py | 10 +- .../game/scripts/cards/SWSH12/Hawlucha_98.py | 8 +- .../cards/SWSH12/HisuianArcanineV_179.py | 31 +- .../cards/SWSH12/HisuianArcanineV_90.py | 31 +- .../cards/SWSH12/HisuianBraviary_149.py | 16 +- .../cards/SWSH12/HisuianLilligant_10.py | 19 +- spirit/game/scripts/cards/SWSH12/HoOhV_140.py | 36 +- spirit/game/scripts/cards/SWSH12/HoOhV_187.py | 36 +- .../scripts/cards/SWSH12/Honchkrow_107.py | 14 +- spirit/game/scripts/cards/SWSH12/Hypno_61.py | 13 +- .../scripts/cards/SWSH12/Incineroar_32.py | 46 +- .../game/scripts/cards/SWSH12/Indeedee_86.py | 6 +- spirit/game/scripts/cards/SWSH12/Jynx_62.py | 27 +- spirit/game/scripts/cards/SWSH12/Keldeo_46.py | 8 +- spirit/game/scripts/cards/SWSH12/Kirlia_68.py | 8 +- spirit/game/scripts/cards/SWSH12/Klang_124.py | 5 +- spirit/game/scripts/cards/SWSH12/Klink_123.py | 9 +- .../scripts/cards/SWSH12/Klinklang_125.py | 7 +- .../game/scripts/cards/SWSH12/Krokorok_112.py | 16 +- .../scripts/cards/SWSH12/Krookodile_113.py | 20 +- .../game/scripts/cards/SWSH12/Lanturn_52.py | 5 +- spirit/game/scripts/cards/SWSH12/Litten_30.py | 5 +- .../game/scripts/cards/SWSH12/Lopunny_145.py | 5 +- .../game/scripts/cards/SWSH12/LugiaV_138.py | 7 +- .../game/scripts/cards/SWSH12/LugiaV_185.py | 7 +- .../scripts/cards/SWSH12/MagearnaV_128.py | 13 +- .../scripts/cards/SWSH12/MagearnaV_182.py | 13 +- .../game/scripts/cards/SWSH12/Mareanie_114.py | 7 +- .../scripts/cards/SWSH12/MawileVSTAR_200.py | 23 +- .../scripts/cards/SWSH12/MawileVSTAR_71.py | 23 +- .../game/scripts/cards/SWSH12/MawileV_178.py | 25 +- .../game/scripts/cards/SWSH12/MawileV_70.py | 25 +- .../game/scripts/cards/SWSH12/Medicham_73.py | 29 +- .../game/scripts/cards/SWSH12/Meditite_72.py | 10 +- .../game/scripts/cards/SWSH12/Meowstic_82.py | 20 +- .../scripts/cards/SWSH12/Metagross_119.py | 9 +- .../game/scripts/cards/SWSH12/Metang_118.py | 5 +- .../game/scripts/cards/SWSH12/Milotic_40.py | 9 +- .../scripts/cards/SWSH12/Misdreavus_63.py | 7 +- .../game/scripts/cards/SWSH12/Mismagius_64.py | 22 +- .../game/scripts/cards/SWSH12/Morpeko_116.py | 21 +- .../game/scripts/cards/SWSH12/Murkrow_106.py | 11 +- .../game/scripts/cards/SWSH12/Ninetales_18.py | 16 +- .../game/scripts/cards/SWSH12/Noivern_133.py | 24 +- .../game/scripts/cards/SWSH12/OmastarV_174.py | 22 +- .../game/scripts/cards/SWSH12/OmastarV_35.py | 22 +- .../scripts/cards/SWSH12/Palossand_100.py | 5 +- spirit/game/scripts/cards/SWSH12/Phanpy_91.py | 5 +- spirit/game/scripts/cards/SWSH12/Phione_45.py | 15 +- .../game/scripts/cards/SWSH12/Pikachu_49.py | 15 +- spirit/game/scripts/cards/SWSH12/Ponyta_21.py | 5 +- .../cards/SWSH12/RadiantAlakazam_59.py | 47 +- .../cards/SWSH12/RadiantJirachi_120.py | 28 +- .../cards/SWSH12/RadiantTsareena_16.py | 17 +- spirit/game/scripts/cards/SWSH12/Raichu_50.py | 8 +- spirit/game/scripts/cards/SWSH12/Ralts_67.py | 26 +- .../game/scripts/cards/SWSH12/Rapidash_22.py | 32 +- .../cards/SWSH12/RegidragoVSTAR_136.py | 62 +- .../cards/SWSH12/RegidragoVSTAR_201.py | 62 +- .../scripts/cards/SWSH12/RegidragoV_135.py | 18 +- .../scripts/cards/SWSH12/RegidragoV_183.py | 18 +- .../scripts/cards/SWSH12/RegidragoV_184.py | 18 +- .../scripts/cards/SWSH12/RegielekiVMAX_198.py | 22 +- .../scripts/cards/SWSH12/RegielekiVMAX_58.py | 22 +- .../scripts/cards/SWSH12/RegielekiV_175.py | 8 +- .../scripts/cards/SWSH12/RegielekiV_57.py | 8 +- .../game/scripts/cards/SWSH12/Relicanth_44.py | 19 +- .../scripts/cards/SWSH12/ReshiramV_172.py | 18 +- .../game/scripts/cards/SWSH12/ReshiramV_24.py | 18 +- .../game/scripts/cards/SWSH12/Reuniclus_78.py | 31 +- spirit/game/scripts/cards/SWSH12/Rotom_53.py | 5 +- .../game/scripts/cards/SWSH12/Rufflet_148.py | 5 +- .../game/scripts/cards/SWSH12/Sandygast_99.py | 5 +- .../cards/SWSH12/SerperiorVSTAR_196.py | 32 +- .../cards/SWSH12/SerperiorVSTAR_210.py | 32 +- .../scripts/cards/SWSH12/SerperiorVSTAR_8.py | 32 +- .../scripts/cards/SWSH12/SerperiorV_170.py | 10 +- .../game/scripts/cards/SWSH12/SerperiorV_7.py | 10 +- .../game/scripts/cards/SWSH12/Sigilyph_75.py | 19 +- .../scripts/cards/SWSH12/SkuntankV_108.py | 21 +- .../scripts/cards/SWSH12/SkuntankV_180.py | 21 +- .../scripts/cards/SWSH12/SkuntankV_181.py | 21 +- .../game/scripts/cards/SWSH12/Slurpuff_84.py | 5 +- .../game/scripts/cards/SWSH12/Smeargle_137.py | 10 +- .../game/scripts/cards/SWSH12/Snorunt_41.py | 7 +- .../game/scripts/cards/SWSH12/Spinda_141.py | 13 +- .../scripts/cards/SWSH12/Stonjourner_101.py | 11 +- .../game/scripts/cards/SWSH12/Stunfisk_55.py | 9 +- .../game/scripts/cards/SWSH12/Sunflora_6.py | 14 +- .../game/scripts/cards/SWSH12/Swablu_142.py | 5 +- .../scripts/cards/SWSH12/Talonflame_29.py | 7 +- .../game/scripts/cards/SWSH12/Terrakion_97.py | 15 +- .../scripts/cards/SWSH12/Togedemaru_127.py | 19 +- .../game/scripts/cards/SWSH12/Torracat_31.py | 15 +- .../game/scripts/cards/SWSH12/Toxapex_115.py | 7 +- .../scripts/cards/SWSH12/Toxicroak_110.py | 13 +- .../cards/SWSH12/UnidentifiedFossil_165.py | 10 +- .../scripts/cards/SWSH12/UnownVSTAR_199.py | 42 +- .../scripts/cards/SWSH12/UnownVSTAR_66.py | 42 +- .../game/scripts/cards/SWSH12/UnownV_176.py | 15 +- .../game/scripts/cards/SWSH12/UnownV_177.py | 15 +- spirit/game/scripts/cards/SWSH12/UnownV_65.py | 15 +- .../scripts/cards/SWSH12/UrsalunaV_102.py | 8 +- .../game/scripts/cards/SWSH12/Venomoth_2.py | 29 +- .../game/scripts/cards/SWSH12/Victini_23.py | 30 +- .../game/scripts/cards/SWSH12/Virizion_14.py | 10 +- spirit/game/scripts/cards/SWSH12/Vulpix_17.py | 5 +- .../game/scripts/cards/SWSH12/Wailmer_37.py | 12 +- .../game/scripts/cards/SWSH12/Wailord_38.py | 13 +- .../game/scripts/cards/SWSH12/Zeraora_56.py | 6 +- .../game/scripts/cards/SWSH12/Zygarde_134.py | 11 +- .../game/scripts/cards/SWSH2/Aegislash_135.py | 5 +- .../game/scripts/cards/SWSH2/Alcremie_87.py | 23 +- .../game/scripts/cards/SWSH2/Appletun_23.py | 27 +- spirit/game/scripts/cards/SWSH2/Applin_20.py | 5 +- spirit/game/scripts/cards/SWSH2/Applin_21.py | 5 +- .../game/scripts/cards/SWSH2/Arcanine_28.py | 17 +- .../scripts/cards/SWSH2/Barbaracle_104.py | 16 +- .../scripts/cards/SWSH2/Barraskewda_53.py | 23 +- spirit/game/scripts/cards/SWSH2/Bewear_150.py | 28 +- .../game/scripts/cards/SWSH2/BigCharm_206.py | 5 +- .../game/scripts/cards/SWSH2/Binacle_103.py | 7 +- .../game/scripts/cards/SWSH2/BoltundV_181.py | 22 +- .../game/scripts/cards/SWSH2/BoltundV_67.py | 22 +- .../game/scripts/cards/SWSH2/Bunnelby_146.py | 7 +- .../scripts/cards/SWSH2/BurningScarf_155.py | 23 +- .../game/scripts/cards/SWSH2/Butterfree_3.py | 10 +- .../cards/SWSH2/CapaciousBucket_156.py | 18 +- spirit/game/scripts/cards/SWSH2/Caterpie_1.py | 11 +- .../game/scripts/cards/SWSH2/Chandelure_33.py | 19 +- .../game/scripts/cards/SWSH2/Charjabug_65.py | 9 +- spirit/game/scripts/cards/SWSH2/Chatot_142.py | 27 +- .../scripts/cards/SWSH2/CinderaceVMAX_194.py | 17 +- .../scripts/cards/SWSH2/CinderaceVMAX_36.py | 17 +- .../scripts/cards/SWSH2/CinderaceV_178.py | 22 +- .../game/scripts/cards/SWSH2/CinderaceV_35.py | 22 +- .../game/scripts/cards/SWSH2/Clefable_75.py | 29 +- .../game/scripts/cards/SWSH2/Clefairy_74.py | 7 +- .../game/scripts/cards/SWSH2/Coalossal_107.py | 53 +- .../scripts/cards/SWSH2/CopperajahVMAX_137.py | 6 +- .../scripts/cards/SWSH2/CopperajahVMAX_199.py | 6 +- .../scripts/cards/SWSH2/CopperajahV_136.py | 5 +- .../scripts/cards/SWSH2/CopperajahV_187.py | 5 +- .../game/scripts/cards/SWSH2/Cramorant_51.py | 8 +- .../scripts/cards/SWSH2/CursedShovel_157.py | 22 +- spirit/game/scripts/cards/SWSH2/Dan_158.py | 10 +- .../game/scripts/cards/SWSH2/Diggersby_147.py | 23 +- .../game/scripts/cards/SWSH2/Doublade_134.py | 5 +- .../scripts/cards/SWSH2/DragapultVMAX_197.py | 22 +- .../scripts/cards/SWSH2/DragapultVMAX_93.py | 22 +- .../scripts/cards/SWSH2/DragapultV_183.py | 10 +- .../game/scripts/cards/SWSH2/DragapultV_92.py | 10 +- .../game/scripts/cards/SWSH2/Dragapult_91.py | 15 +- .../game/scripts/cards/SWSH2/Drakloak_90.py | 14 +- spirit/game/scripts/cards/SWSH2/Dreepy_89.py | 5 +- .../game/scripts/cards/SWSH2/DubwoolV_153.py | 8 +- .../game/scripts/cards/SWSH2/DubwoolV_188.py | 8 +- .../game/scripts/cards/SWSH2/Duraludon_138.py | 9 +- spirit/game/scripts/cards/SWSH2/Durant_132.py | 21 +- spirit/game/scripts/cards/SWSH2/EiscueV_55.py | 20 +- spirit/game/scripts/cards/SWSH2/Eiscue_54.py | 21 +- .../game/scripts/cards/SWSH2/EldegossV_176.py | 25 +- .../game/scripts/cards/SWSH2/EldegossV_19.py | 25 +- .../game/scripts/cards/SWSH2/Electivire_59.py | 14 +- .../game/scripts/cards/SWSH2/Electrode_57.py | 8 +- .../game/scripts/cards/SWSH2/FalinksV_110.py | 15 +- .../game/scripts/cards/SWSH2/FalinksV_185.py | 15 +- .../game/scripts/cards/SWSH2/Falinks_109.py | 13 +- spirit/game/scripts/cards/SWSH2/Flapple_22.py | 46 +- .../game/scripts/cards/SWSH2/Frosmoth_204.py | 36 +- .../game/scripts/cards/SWSH2/FullHeal_159.py | 5 +- .../game/scripts/cards/SWSH2/GalarMine_160.py | 14 +- .../scripts/cards/SWSH2/GalarianCorsola_78.py | 5 +- .../scripts/cards/SWSH2/GalarianCursola_79.py | 28 +- .../cards/SWSH2/GalarianDarmanitan_48.py | 7 +- .../cards/SWSH2/GalarianDarumaka_47.py | 7 +- .../cards/SWSH2/GalarianFarfetchd_94.py | 5 +- .../scripts/cards/SWSH2/GalarianMeowth_126.py | 24 +- .../scripts/cards/SWSH2/GalarianMrMime_37.py | 9 +- .../scripts/cards/SWSH2/GalarianMrRime_38.py | 23 +- .../cards/SWSH2/GalarianPerrserker_127.py | 5 +- .../cards/SWSH2/GalarianPerrserker_205.py | 13 +- .../cards/SWSH2/GalarianRunerigus_102.py | 11 +- .../cards/SWSH2/GalarianSirfetchd_95.py | 14 +- .../cards/SWSH2/GalarianWeezing_113.py | 25 +- .../scripts/cards/SWSH2/GalarianYamask_101.py | 5 +- .../game/scripts/cards/SWSH2/Garbodor_118.py | 25 +- .../game/scripts/cards/SWSH2/Greedent_152.py | 18 +- .../scripts/cards/SWSH2/Grimmsnarl_125.py | 17 +- .../game/scripts/cards/SWSH2/Growlithe_27.py | 16 +- .../game/scripts/cards/SWSH2/Gyarados_40.py | 10 +- spirit/game/scripts/cards/SWSH2/Hatenna_83.py | 7 +- .../game/scripts/cards/SWSH2/Hatterene_85.py | 28 +- spirit/game/scripts/cards/SWSH2/Hattrem_84.py | 8 +- .../game/scripts/cards/SWSH2/Hawlucha_148.py | 5 +- spirit/game/scripts/cards/SWSH2/Heatmor_34.py | 9 +- .../game/scripts/cards/SWSH2/Heliolisk_64.py | 23 +- .../game/scripts/cards/SWSH2/Helioptile_63.py | 5 +- .../game/scripts/cards/SWSH2/Heracross_6.py | 5 +- .../game/scripts/cards/SWSH2/Indeedee_88.py | 16 +- .../scripts/cards/SWSH2/InteleonVMAX_195.py | 26 +- .../scripts/cards/SWSH2/InteleonVMAX_50.py | 26 +- .../game/scripts/cards/SWSH2/InteleonV_180.py | 14 +- .../game/scripts/cards/SWSH2/InteleonV_49.py | 14 +- spirit/game/scripts/cards/SWSH2/Lampent_32.py | 22 +- spirit/game/scripts/cards/SWSH2/Litwick_31.py | 7 +- spirit/game/scripts/cards/SWSH2/Lombre_8.py | 9 +- spirit/game/scripts/cards/SWSH2/Lotad_7.py | 5 +- spirit/game/scripts/cards/SWSH2/Ludicolo_9.py | 8 +- spirit/game/scripts/cards/SWSH2/Luxio_61.py | 6 +- spirit/game/scripts/cards/SWSH2/Luxray_62.py | 11 +- .../game/scripts/cards/SWSH2/Magikarp_39.py | 5 +- spirit/game/scripts/cards/SWSH2/Magmar_29.py | 5 +- .../game/scripts/cards/SWSH2/Magmortar_30.py | 21 +- .../scripts/cards/SWSH2/MalamarVMAX_122.py | 20 +- .../scripts/cards/SWSH2/MalamarVMAX_198.py | 20 +- .../game/scripts/cards/SWSH2/MalamarV_121.py | 10 +- .../game/scripts/cards/SWSH2/MalamarV_186.py | 10 +- .../game/scripts/cards/SWSH2/Mandibuzz_120.py | 22 +- .../game/scripts/cards/SWSH2/Masquerain_11.py | 14 +- .../game/scripts/cards/SWSH2/Medicham_98.py | 13 +- .../game/scripts/cards/SWSH2/Meditite_97.py | 7 +- spirit/game/scripts/cards/SWSH2/Metapod_2.py | 11 +- spirit/game/scripts/cards/SWSH2/Milcery_86.py | 5 +- .../game/scripts/cards/SWSH2/MiloticV_179.py | 13 +- .../game/scripts/cards/SWSH2/MiloticV_43.py | 13 +- .../game/scripts/cards/SWSH2/Morgrem_124.py | 15 +- spirit/game/scripts/cards/SWSH2/Morpeko_73.py | 43 +- spirit/game/scripts/cards/SWSH2/Natu_76.py | 5 +- .../scripts/cards/SWSH2/NinetalesV_177.py | 31 +- .../game/scripts/cards/SWSH2/NinetalesV_26.py | 31 +- .../game/scripts/cards/SWSH2/Ninetales_25.py | 15 +- spirit/game/scripts/cards/SWSH2/Nugget_162.py | 26 +- spirit/game/scripts/cards/SWSH2/Oleana_163.py | 28 +- spirit/game/scripts/cards/SWSH2/Oleana_191.py | 28 +- spirit/game/scripts/cards/SWSH2/Oleana_202.py | 28 +- .../game/scripts/cards/SWSH2/Palossand_82.py | 22 +- .../game/scripts/cards/SWSH2/Palpitoad_45.py | 7 +- .../game/scripts/cards/SWSH2/Pelipper_42.py | 21 +- .../game/scripts/cards/SWSH2/Phantump_14.py | 22 +- spirit/game/scripts/cards/SWSH2/Pidove_143.py | 21 +- .../scripts/cards/SWSH2/PincurchinV_72.py | 20 +- .../game/scripts/cards/SWSH2/PokBall_164.py | 18 +- .../game/scripts/cards/SWSH2/Probopass_131.py | 14 +- .../scripts/cards/SWSH2/RillaboomVMAX_18.py | 16 +- .../scripts/cards/SWSH2/RillaboomVMAX_193.py | 16 +- .../game/scripts/cards/SWSH2/RillaboomV_17.py | 17 +- .../scripts/cards/SWSH2/RillaboomV_175.py | 17 +- .../scripts/cards/SWSH2/SandacondaV_108.py | 19 +- .../scripts/cards/SWSH2/SandacondaV_184.py | 19 +- .../game/scripts/cards/SWSH2/Sandygast_81.py | 5 +- spirit/game/scripts/cards/SWSH2/Scizor_128.py | 14 +- .../scripts/cards/SWSH2/ScoopUpNet_207.py | 5 +- spirit/game/scripts/cards/SWSH2/Scyther_4.py | 5 +- .../game/scripts/cards/SWSH2/Seismitoad_46.py | 9 +- spirit/game/scripts/cards/SWSH2/Shinx_60.py | 5 +- spirit/game/scripts/cards/SWSH2/Shuckle_5.py | 22 +- .../game/scripts/cards/SWSH2/Sigilyph_80.py | 20 +- .../game/scripts/cards/SWSH2/Skuntank_115.py | 7 +- spirit/game/scripts/cards/SWSH2/Skyla_166.py | 6 +- .../game/scripts/cards/SWSH2/Snorlax_141.py | 10 +- .../game/scripts/cards/SWSH2/Spiritomb_116.py | 16 +- .../scripts/cards/SWSH2/Stonjourner_111.py | 13 +- .../game/scripts/cards/SWSH2/Stufful_149.py | 5 +- spirit/game/scripts/cards/SWSH2/Stunky_114.py | 7 +- spirit/game/scripts/cards/SWSH2/Surskit_10.py | 5 +- .../scripts/cards/SWSH2/ToolScrapper_168.py | 40 +- .../scripts/cards/SWSH2/ToolScrapper_208.py | 40 +- spirit/game/scripts/cards/SWSH2/Toxel_68.py | 7 +- .../scripts/cards/SWSH2/ToxtricityVMAX_196.py | 7 +- .../scripts/cards/SWSH2/ToxtricityVMAX_71.py | 7 +- .../scripts/cards/SWSH2/ToxtricityV_182.py | 9 +- .../scripts/cards/SWSH2/ToxtricityV_70.py | 9 +- .../game/scripts/cards/SWSH2/Toxtricity_69.py | 13 +- .../game/scripts/cards/SWSH2/Trevenant_15.py | 5 +- .../game/scripts/cards/SWSH2/Trubbish_117.py | 7 +- .../cards/SWSH2/TurffieldStadium_170.py | 31 +- spirit/game/scripts/cards/SWSH2/Tympole_44.py | 7 +- .../game/scripts/cards/SWSH2/Unfezant_145.py | 8 +- .../game/scripts/cards/SWSH2/Vikavolt_66.py | 7 +- spirit/game/scripts/cards/SWSH2/Voltorb_56.py | 5 +- .../game/scripts/cards/SWSH2/Vullaby_119.py | 14 +- spirit/game/scripts/cards/SWSH2/Vulpix_24.py | 7 +- .../game/scripts/cards/SWSH2/Whiscash_100.py | 20 +- spirit/game/scripts/cards/SWSH2/Wingull_41.py | 5 +- spirit/game/scripts/cards/SWSH2/Xatu_77.py | 30 +- spirit/game/scripts/cards/SWSH2/Zacian_139.py | 18 +- .../game/scripts/cards/SWSH2/Zamazenta_140.py | 17 +- spirit/game/scripts/cards/SWSH3/Aggron_123.py | 5 +- .../game/scripts/cards/SWSH3/Arctozolt_66.py | 17 +- spirit/game/scripts/cards/SWSH3/Beartic_49.py | 5 +- .../scripts/cards/SWSH3/BillowingSmoke_158.py | 18 +- .../scripts/cards/SWSH3/BirdKeeper_159.py | 16 +- .../game/scripts/cards/SWSH3/Blaziken_24.py | 23 +- .../game/scripts/cards/SWSH3/Bounsweet_14.py | 6 +- .../game/scripts/cards/SWSH3/Bunnelby_150.py | 9 +- .../scripts/cards/SWSH3/ButterfreeVMAX_190.py | 9 +- .../scripts/cards/SWSH3/ButterfreeVMAX_2.py | 9 +- .../game/scripts/cards/SWSH3/ButterfreeV_1.py | 9 +- .../scripts/cards/SWSH3/ButterfreeV_177.py | 9 +- .../cards/SWSH3/CapeofToughness_160.py | 9 +- .../game/scripts/cards/SWSH3/Carnivine_5.py | 19 +- .../cards/SWSH3/CentiskorchVMAX_191.py | 23 +- .../scripts/cards/SWSH3/CentiskorchVMAX_34.py | 23 +- .../scripts/cards/SWSH3/CentiskorchV_179.py | 26 +- .../scripts/cards/SWSH3/CentiskorchV_33.py | 26 +- .../scripts/cards/SWSH3/CharizardVMAX_20.py | 5 +- .../game/scripts/cards/SWSH3/CharizardV_19.py | 5 +- .../game/scripts/cards/SWSH3/Coalossal_198.py | 53 +- .../game/scripts/cards/SWSH3/Combusken_23.py | 7 +- .../scripts/cards/SWSH3/Copperajah_132.py | 17 +- .../scripts/cards/SWSH3/Corviknight_156.py | 35 +- .../scripts/cards/SWSH3/Corvisquire_155.py | 5 +- .../game/scripts/cards/SWSH3/CrobatV_104.py | 12 +- .../game/scripts/cards/SWSH3/CrobatV_182.py | 12 +- .../game/scripts/cards/SWSH3/Darkrai_105.py | 23 +- .../game/scripts/cards/SWSH3/Decidueye_13.py | 17 +- spirit/game/scripts/cards/SWSH3/Dedenne_78.py | 9 +- .../game/scripts/cards/SWSH3/Delcatty_142.py | 12 +- .../game/scripts/cards/SWSH3/Dracovish_53.py | 12 +- .../game/scripts/cards/SWSH3/Dracozolt_65.py | 15 +- spirit/game/scripts/cards/SWSH3/Dugtrio_85.py | 5 +- .../game/scripts/cards/SWSH3/Electrike_58.py | 5 +- .../scripts/cards/SWSH3/Escavalier_124.py | 13 +- .../scripts/cards/SWSH3/EternatusVMAX_117.py | 38 +- .../scripts/cards/SWSH3/EternatusVMAX_192.py | 38 +- .../scripts/cards/SWSH3/EternatusV_116.py | 37 +- .../scripts/cards/SWSH3/FamiliarBell_161.py | 34 +- spirit/game/scripts/cards/SWSH3/Feebas_38.py | 5 +- .../scripts/cards/SWSH3/Fletchinder_31.py | 7 +- spirit/game/scripts/cards/SWSH3/Flygon_91.py | 24 +- spirit/game/scripts/cards/SWSH3/Furret_136.py | 8 +- .../cards/SWSH3/GalarianDarmanitan_28.py | 20 +- .../cards/SWSH3/GalarianDarmanitan_44.py | 7 +- .../scripts/cards/SWSH3/GalarianMrMime_35.py | 5 +- .../scripts/cards/SWSH3/GalarianMrRime_36.py | 59 +- .../cards/SWSH3/GalarianSirfetchd_98.py | 14 +- .../cards/SWSH3/GalarianSlowbroV_99.py | 26 +- .../cards/SWSH3/GalarianStunfiskV_128.py | 59 +- .../cards/SWSH3/GalarianStunfiskV_184.py | 59 +- .../scripts/cards/SWSH3/GlimwoodTangle_162.py | 14 +- .../game/scripts/cards/SWSH3/Golisopod_18.py | 13 +- spirit/game/scripts/cards/SWSH3/Gothita_73.py | 26 +- .../game/scripts/cards/SWSH3/Gothitelle_75.py | 23 +- .../game/scripts/cards/SWSH3/Gothorita_74.py | 19 +- .../game/scripts/cards/SWSH3/Granbull_71.py | 13 +- .../game/scripts/cards/SWSH3/Greedent_153.py | 26 +- spirit/game/scripts/cards/SWSH3/Grimer_100.py | 5 +- .../scripts/cards/SWSH3/GrimmsnarlVMAX_115.py | 13 +- .../scripts/cards/SWSH3/GrimmsnarlV_114.py | 22 +- spirit/game/scripts/cards/SWSH3/Heatran_25.py | 9 +- .../game/scripts/cards/SWSH3/Hippopotas_93.py | 5 +- .../game/scripts/cards/SWSH3/Hippowdon_94.py | 5 +- spirit/game/scripts/cards/SWSH3/Hoopa_111.py | 11 +- .../game/scripts/cards/SWSH3/HoundoomV_178.py | 25 +- .../game/scripts/cards/SWSH3/HoundoomV_21.py | 25 +- .../game/scripts/cards/SWSH3/Hydreigon_110.py | 33 +- .../scripts/cards/SWSH3/Kangaskhan_133.py | 5 +- .../game/scripts/cards/SWSH3/Karrablast_8.py | 5 +- spirit/game/scripts/cards/SWSH3/Klang_126.py | 16 +- spirit/game/scripts/cards/SWSH3/Klink_125.py | 16 +- .../game/scripts/cards/SWSH3/Klinklang_127.py | 18 +- spirit/game/scripts/cards/SWSH3/Lairon_122.py | 5 +- .../game/scripts/cards/SWSH3/Larvesta_29.py | 7 +- .../game/scripts/cards/SWSH3/Liepard_107.py | 7 +- spirit/game/scripts/cards/SWSH3/Lugia_140.py | 17 +- .../game/scripts/cards/SWSH3/Lunatone_72.py | 32 +- .../game/scripts/cards/SWSH3/Manectric_59.py | 8 +- .../game/scripts/cards/SWSH3/Mareanie_51.py | 10 +- .../game/scripts/cards/SWSH3/Melmetal_130.py | 5 +- spirit/game/scripts/cards/SWSH3/MewV_69.py | 17 +- spirit/game/scripts/cards/SWSH3/Milotic_39.py | 13 +- spirit/game/scripts/cards/SWSH3/Mimikyu_81.py | 9 +- .../game/scripts/cards/SWSH3/Morelull_79.py | 7 +- spirit/game/scripts/cards/SWSH3/Muk_101.py | 7 +- spirit/game/scripts/cards/SWSH3/Nickit_112.py | 12 +- spirit/game/scripts/cards/SWSH3/OldPC_164.py | 16 +- spirit/game/scripts/cards/SWSH3/Parasect_4.py | 7 +- .../game/scripts/cards/SWSH3/Passimian_97.py | 17 +- .../game/scripts/cards/SWSH3/Pincurchin_64.py | 10 +- .../SWSH3/PokmonBreedersNurturing_166.py | 43 +- .../SWSH3/PokmonBreedersNurturing_188.py | 43 +- .../SWSH3/PokmonBreedersNurturing_195.py | 43 +- .../scripts/cards/SWSH3/Polteageist_83.py | 32 +- spirit/game/scripts/cards/SWSH3/Pupitar_87.py | 29 +- .../game/scripts/cards/SWSH3/Purrloin_106.py | 7 +- .../scripts/cards/SWSH3/RareFossil_167.py | 9 +- .../game/scripts/cards/SWSH3/Relicanth_40.py | 7 +- .../scripts/cards/SWSH3/RhyperiorV_181.py | 7 +- .../game/scripts/cards/SWSH3/RhyperiorV_95.py | 7 +- .../game/scripts/cards/SWSH3/Rillaboom_197.py | 7 +- .../game/scripts/cards/SWSH3/Rookidee_154.py | 15 +- .../game/scripts/cards/SWSH3/RoseTower_169.py | 23 +- spirit/game/scripts/cards/SWSH3/Rowlet_11.py | 19 +- .../scripts/cards/SWSH3/SalamenceVMAX_144.py | 7 +- .../scripts/cards/SWSH3/SalamenceVMAX_194.py | 7 +- .../scripts/cards/SWSH3/SalamenceV_143.py | 5 +- .../scripts/cards/SWSH3/SalamenceV_185.py | 5 +- .../scripts/cards/SWSH3/ScizorVMAX_119.py | 5 +- .../scripts/cards/SWSH3/ScizorVMAX_193.py | 5 +- .../game/scripts/cards/SWSH3/ScizorV_118.py | 20 +- .../game/scripts/cards/SWSH3/ScizorV_183.py | 20 +- .../game/scripts/cards/SWSH3/Sentret_135.py | 5 +- .../game/scripts/cards/SWSH3/Shiinotic_80.py | 21 +- .../game/scripts/cards/SWSH3/Simipour_42.py | 11 +- spirit/game/scripts/cards/SWSH3/Simisage_7.py | 11 +- .../game/scripts/cards/SWSH3/Simisear_27.py | 11 +- .../game/scripts/cards/SWSH3/Sinistea_82.py | 5 +- .../game/scripts/cards/SWSH3/Skarmory_120.py | 5 +- spirit/game/scripts/cards/SWSH3/Skitty_141.py | 10 +- .../game/scripts/cards/SWSH3/Skwovet_152.py | 5 +- spirit/game/scripts/cards/SWSH3/Solrock_92.py | 17 +- .../game/scripts/cards/SWSH3/Spikemuth_170.py | 14 +- .../game/scripts/cards/SWSH3/Staraptor_147.py | 15 +- .../game/scripts/cards/SWSH3/Staravia_146.py | 5 +- spirit/game/scripts/cards/SWSH3/Starly_145.py | 19 +- spirit/game/scripts/cards/SWSH3/Steenee_15.py | 5 +- .../scripts/cards/SWSH3/StruggleGloves_171.py | 21 +- spirit/game/scripts/cards/SWSH3/Suicune_37.py | 19 +- spirit/game/scripts/cards/SWSH3/Swanna_149.py | 28 +- .../game/scripts/cards/SWSH3/Talonflame_32.py | 19 +- .../game/scripts/cards/SWSH3/TapuKoko_61.py | 5 +- .../game/scripts/cards/SWSH3/Teddiursa_138.py | 12 +- .../game/scripts/cards/SWSH3/Thievul_113.py | 5 +- spirit/game/scripts/cards/SWSH3/Torchic_22.py | 5 +- spirit/game/scripts/cards/SWSH3/Toxapex_52.py | 10 +- .../game/scripts/cards/SWSH3/Toxtricity_63.py | 15 +- .../game/scripts/cards/SWSH3/Trapinch_89.py | 5 +- .../game/scripts/cards/SWSH3/Tsareena_16.py | 42 +- .../scripts/cards/SWSH3/TurboPatch_172.py | 37 +- .../scripts/cards/SWSH3/TurboPatch_200.py | 37 +- .../game/scripts/cards/SWSH3/Tyranitar_88.py | 16 +- .../game/scripts/cards/SWSH3/Ursaring_139.py | 5 +- .../game/scripts/cards/SWSH3/Vanillish_46.py | 7 +- .../game/scripts/cards/SWSH3/Vanillite_45.py | 7 +- .../game/scripts/cards/SWSH3/Vanilluxe_47.py | 21 +- spirit/game/scripts/cards/SWSH3/Vibrava_90.py | 5 +- .../game/scripts/cards/SWSH3/VikavoltV_180.py | 20 +- .../game/scripts/cards/SWSH3/VikavoltV_60.py | 20 +- .../game/scripts/cards/SWSH3/Volcarona_30.py | 5 +- .../game/scripts/cards/SWSH3/Wigglytuff_68.py | 9 +- spirit/game/scripts/cards/SWSH3/Wimpod_17.py | 5 +- .../game/scripts/cards/SWSH3/Wishiwashi_50.py | 5 +- .../game/scripts/cards/SWSH3/YellHorn_173.py | 17 +- .../scripts/cards/SWSH35/AlcremieVMAX_23.py | 51 +- .../game/scripts/cards/SWSH35/AlcremieV_22.py | 23 +- .../game/scripts/cards/SWSH35/Altaria_49.py | 15 +- .../game/scripts/cards/SWSH35/Beedrill_4.py | 11 +- .../scripts/cards/SWSH35/Centiskorch_10.py | 7 +- .../scripts/cards/SWSH35/CharizardVMAX_74.py | 5 +- .../scripts/cards/SWSH35/CharizardV_79.py | 5 +- .../scripts/cards/SWSH35/DrednawVMAX_15.py | 8 +- .../scripts/cards/SWSH35/DrednawVMAX_75.py | 8 +- .../game/scripts/cards/SWSH35/DrednawV_14.py | 8 +- .../game/scripts/cards/SWSH35/DrednawV_69.py | 8 +- .../scripts/cards/SWSH35/DuraludonV_47.py | 8 +- .../game/scripts/cards/SWSH35/EldegossV_5.py | 25 +- .../game/scripts/cards/SWSH35/FullHeal_51.py | 5 +- .../cards/SWSH35/GalarianCursolaV_21.py | 26 +- .../cards/SWSH35/GalarianCursolaV_71.py | 26 +- .../cards/SWSH35/GalarianLinoone_36.py | 5 +- .../cards/SWSH35/GalarianObstagoon_37.py | 34 +- .../cards/SWSH35/GalarianZigzagoon_35.py | 5 +- .../scripts/cards/SWSH35/GardevoirVMAX_17.py | 5 +- .../scripts/cards/SWSH35/GardevoirVMAX_76.py | 5 +- .../scripts/cards/SWSH35/GardevoirV_16.py | 10 +- .../scripts/cards/SWSH35/GardevoirV_70.py | 10 +- .../scripts/cards/SWSH35/GrapploctV_32.py | 16 +- .../scripts/cards/SWSH35/GrapploctV_72.py | 16 +- .../game/scripts/cards/SWSH35/GreatBall_52.py | 21 +- .../game/scripts/cards/SWSH35/Hatterene_20.py | 23 +- .../scripts/cards/SWSH35/HyperPotion_54.py | 27 +- .../scripts/cards/SWSH35/IncineroarV_8.py | 27 +- spirit/game/scripts/cards/SWSH35/Inkay_44.py | 11 +- .../game/scripts/cards/SWSH35/Liepard_40.py | 7 +- .../game/scripts/cards/SWSH35/LucarioV_27.py | 7 +- .../game/scripts/cards/SWSH35/Lycanroc_30.py | 4 +- .../game/scripts/cards/SWSH35/Machamp_26.py | 16 +- .../game/scripts/cards/SWSH35/Machoke_25.py | 5 +- spirit/game/scripts/cards/SWSH35/Machop_24.py | 5 +- .../game/scripts/cards/SWSH35/Malamar_45.py | 9 +- spirit/game/scripts/cards/SWSH35/Nickit_46.py | 5 +- .../game/scripts/cards/SWSH35/PokBall_59.py | 18 +- spirit/game/scripts/cards/SWSH35/Potion_61.py | 6 +- .../game/scripts/cards/SWSH35/Purrloin_39.py | 7 +- .../game/scripts/cards/SWSH35/Rockruff_29.py | 5 +- .../game/scripts/cards/SWSH35/RotomBike_63.py | 11 +- .../game/scripts/cards/SWSH35/Scrafty_42.py | 18 +- .../game/scripts/cards/SWSH35/Sharpedo_12.py | 5 +- .../cards/SWSH35/SuspiciousFoodTin_66.py | 38 +- .../cards/SWSH35/SuspiciousFoodTin_80.py | 38 +- .../game/scripts/cards/SWSH35/Trubbish_43.py | 7 +- .../cards/SWSH35/TurffieldStadium_68.py | 31 +- .../game/scripts/cards/SWSH35/VenusaurV_1.py | 9 +- spirit/game/scripts/cards/SWSH35/Victini_7.py | 5 +- spirit/game/scripts/cards/SWSH35/Vulpix_6.py | 7 +- .../game/scripts/cards/SWSH35/WailordV_13.py | 20 +- spirit/game/scripts/cards/SWSH35/Weedle_2.py | 5 +- .../game/scripts/cards/SWSH35/Zygarde_28.py | 5 +- .../scripts/cards/SWSH4/AegislashVMAX_127.py | 5 +- .../scripts/cards/SWSH4/AegislashVMAX_190.py | 5 +- .../scripts/cards/SWSH4/AegislashV_126.py | 10 +- .../scripts/cards/SWSH4/AegislashV_177.py | 10 +- .../game/scripts/cards/SWSH4/AlakazamV_172.py | 13 +- .../game/scripts/cards/SWSH4/Alcremie_81.py | 24 +- .../game/scripts/cards/SWSH4/Allister_146.py | 16 +- .../game/scripts/cards/SWSH4/Allister_179.py | 16 +- .../game/scripts/cards/SWSH4/Allister_192.py | 16 +- .../game/scripts/cards/SWSH4/AmpharosV_171.py | 24 +- .../game/scripts/cards/SWSH4/AmpharosV_49.py | 24 +- .../game/scripts/cards/SWSH4/Arrokuda_41.py | 15 +- spirit/game/scripts/cards/SWSH4/Banette_68.py | 6 +- .../scripts/cards/SWSH4/Barraskewda_42.py | 23 +- spirit/game/scripts/cards/SWSH4/Bea_147.py | 28 +- spirit/game/scripts/cards/SWSH4/Bea_180.py | 28 +- spirit/game/scripts/cards/SWSH4/Bea_193.py | 28 +- spirit/game/scripts/cards/SWSH4/Beauty_148.py | 5 +- spirit/game/scripts/cards/SWSH4/Beauty_181.py | 5 +- spirit/game/scripts/cards/SWSH4/Beauty_194.py | 5 +- spirit/game/scripts/cards/SWSH4/Beedrill_3.py | 27 +- spirit/game/scripts/cards/SWSH4/Beldum_116.py | 5 +- .../cards/SWSH4/CapeofToughness_200.py | 9 +- .../game/scripts/cards/SWSH4/CaraLiss_149.py | 6 +- spirit/game/scripts/cards/SWSH4/Celebi_9.py | 28 +- .../game/scripts/cards/SWSH4/Charizard_25.py | 30 +- .../game/scripts/cards/SWSH4/Charmander_23.py | 5 +- .../game/scripts/cards/SWSH4/Charmeleon_24.py | 5 +- spirit/game/scripts/cards/SWSH4/Chatot_139.py | 9 +- spirit/game/scripts/cards/SWSH4/Chewtle_38.py | 5 +- .../game/scripts/cards/SWSH4/Chimecho_72.py | 25 +- .../scripts/cards/SWSH4/CirchesterBath_150.py | 6 +- .../game/scripts/cards/SWSH4/Clefable_64.py | 30 +- .../game/scripts/cards/SWSH4/Clefairy_63.py | 34 +- .../scripts/cards/SWSH4/CoalossalVMAX_189.py | 18 +- .../scripts/cards/SWSH4/CoalossalVMAX_99.py | 18 +- .../scripts/cards/SWSH4/CoalossalV_173.py | 7 +- .../game/scripts/cards/SWSH4/CoalossalV_98.py | 7 +- .../game/scripts/cards/SWSH4/Cramorant_40.py | 25 +- spirit/game/scripts/cards/SWSH4/Dedenne_77.py | 7 +- spirit/game/scripts/cards/SWSH4/Dewott_34.py | 24 +- .../game/scripts/cards/SWSH4/Dhelmise_19.py | 11 +- spirit/game/scripts/cards/SWSH4/Dialga_121.py | 18 +- spirit/game/scripts/cards/SWSH4/Diancie_79.py | 17 +- spirit/game/scripts/cards/SWSH4/Donphan_87.py | 5 +- .../game/scripts/cards/SWSH4/DrapionV_106.py | 14 +- .../game/scripts/cards/SWSH4/DrapionV_175.py | 14 +- spirit/game/scripts/cards/SWSH4/Drednaw_39.py | 13 +- spirit/game/scripts/cards/SWSH4/Drilbur_91.py | 5 +- .../scripts/cards/SWSH4/DroneRotom_151.py | 15 +- .../game/scripts/cards/SWSH4/Duraludon_129.py | 8 +- .../game/scripts/cards/SWSH4/Dusclops_70.py | 7 +- .../game/scripts/cards/SWSH4/Dusknoir_71.py | 17 +- spirit/game/scripts/cards/SWSH4/Duskull_69.py | 29 +- .../game/scripts/cards/SWSH4/Eelektrik_58.py | 19 +- .../game/scripts/cards/SWSH4/Eelektross_59.py | 53 +- .../game/scripts/cards/SWSH4/Electrike_51.py | 5 +- .../game/scripts/cards/SWSH4/Electrode_46.py | 38 +- .../game/scripts/cards/SWSH4/Excadrill_122.py | 5 +- .../game/scripts/cards/SWSH4/Exeggutor_5.py | 26 +- .../game/scripts/cards/SWSH4/Exploud_137.py | 5 +- .../game/scripts/cards/SWSH4/Ferroseed_123.py | 5 +- .../scripts/cards/SWSH4/Ferrothorn_124.py | 16 +- spirit/game/scripts/cards/SWSH4/Flareon_26.py | 21 +- .../scripts/cards/SWSH4/Forretress_114.py | 14 +- .../cards/SWSH4/GalarianDarmanitanVMAX_187.py | 5 +- .../cards/SWSH4/GalarianDarmanitanVMAX_37.py | 5 +- .../cards/SWSH4/GalarianDarmanitanV_169.py | 9 +- .../cards/SWSH4/GalarianDarmanitanV_36.py | 9 +- .../scripts/cards/SWSH4/GalarianMeowth_112.py | 5 +- .../cards/SWSH4/GalarianObstagoon_198.py | 35 +- .../cards/SWSH4/GalarianPerrserker_113.py | 24 +- .../cards/SWSH4/GalarianSirfetchdV_174.py | 23 +- .../cards/SWSH4/GalarianStunfisk_125.py | 29 +- .../game/scripts/cards/SWSH4/Galvantula_56.py | 9 +- .../game/scripts/cards/SWSH4/Garbodor_111.py | 31 +- .../game/scripts/cards/SWSH4/Genesect_16.py | 14 +- .../game/scripts/cards/SWSH4/Girafarig_65.py | 18 +- spirit/game/scripts/cards/SWSH4/Gogoat_18.py | 5 +- .../game/scripts/cards/SWSH4/Grapploct_101.py | 19 +- .../scripts/cards/SWSH4/HerosMedal_152.py | 28 +- .../scripts/cards/SWSH4/HerosMedal_201.py | 28 +- .../game/scripts/cards/SWSH4/Hitmontop_88.py | 13 +- .../game/scripts/cards/SWSH4/Jirachi_119.py | 12 +- spirit/game/scripts/cards/SWSH4/Jolteon_47.py | 18 +- spirit/game/scripts/cards/SWSH4/Kakuna_2.py | 5 +- .../game/scripts/cards/SWSH4/Krokorok_108.py | 5 +- .../scripts/cards/SWSH4/Krookodile_109.py | 13 +- .../scripts/cards/SWSH4/LeagueStaff_153.py | 20 +- spirit/game/scripts/cards/SWSH4/Leon_154.py | 15 +- spirit/game/scripts/cards/SWSH4/Leon_182.py | 15 +- spirit/game/scripts/cards/SWSH4/Leon_195.py | 15 +- .../game/scripts/cards/SWSH4/Loudred_136.py | 5 +- .../game/scripts/cards/SWSH4/Lucario_120.py | 7 +- spirit/game/scripts/cards/SWSH4/Lugia_132.py | 11 +- .../game/scripts/cards/SWSH4/Magcargo_28.py | 5 +- .../game/scripts/cards/SWSH4/Magearna_128.py | 8 +- .../game/scripts/cards/SWSH4/Manectric_52.py | 12 +- .../scripts/cards/SWSH4/MemoryCapsule_155.py | 5 +- .../scripts/cards/SWSH4/MemoryCapsule_202.py | 5 +- .../game/scripts/cards/SWSH4/Metagross_118.py | 25 +- .../game/scripts/cards/SWSH4/Mightyena_104.py | 5 +- spirit/game/scripts/cards/SWSH4/Milcery_80.py | 5 +- .../scripts/cards/SWSH4/MoomooCheese_156.py | 23 +- .../game/scripts/cards/SWSH4/Mudsdale_97.py | 13 +- spirit/game/scripts/cards/SWSH4/Nessa_157.py | 22 +- spirit/game/scripts/cards/SWSH4/Nessa_183.py | 22 +- spirit/game/scripts/cards/SWSH4/Nessa_196.py | 22 +- spirit/game/scripts/cards/SWSH4/Nincada_13.py | 5 +- spirit/game/scripts/cards/SWSH4/Ninjask_14.py | 26 +- spirit/game/scripts/cards/SWSH4/Opal_158.py | 18 +- spirit/game/scripts/cards/SWSH4/Opal_184.py | 18 +- spirit/game/scripts/cards/SWSH4/Opal_197.py | 18 +- .../game/scripts/cards/SWSH4/Oranguru_199.py | 6 +- .../scripts/cards/SWSH4/OrbeetleVMAX_186.py | 17 +- .../scripts/cards/SWSH4/OrbeetleVMAX_21.py | 17 +- .../game/scripts/cards/SWSH4/OrbeetleV_166.py | 8 +- .../game/scripts/cards/SWSH4/OrbeetleV_20.py | 8 +- spirit/game/scripts/cards/SWSH4/Phanpy_86.py | 5 +- .../scripts/cards/SWSH4/PikachuVMAX_188.py | 16 +- .../scripts/cards/SWSH4/PikachuVMAX_44.py | 16 +- .../game/scripts/cards/SWSH4/PikachuV_170.py | 15 +- .../game/scripts/cards/SWSH4/PikachuV_43.py | 15 +- .../game/scripts/cards/SWSH4/Pincurchin_62.py | 5 +- spirit/game/scripts/cards/SWSH4/Pineco_8.py | 5 +- .../game/scripts/cards/SWSH4/Quagsire_84.py | 13 +- .../game/scripts/cards/SWSH4/Rayquaza_138.py | 21 +- .../game/scripts/cards/SWSH4/Regirock_89.py | 13 +- spirit/game/scripts/cards/SWSH4/Riolu_90.py | 5 +- .../scripts/cards/SWSH4/RockyHelmet_159.py | 27 +- .../game/scripts/cards/SWSH4/Sableye_105.py | 29 +- .../game/scripts/cards/SWSH4/Samurott_35.py | 28 +- .../game/scripts/cards/SWSH4/Sandile_107.py | 5 +- spirit/game/scripts/cards/SWSH4/Shaymin_15.py | 25 +- .../game/scripts/cards/SWSH4/Shedinja_66.py | 20 +- spirit/game/scripts/cards/SWSH4/Shiftry_12.py | 24 +- spirit/game/scripts/cards/SWSH4/Shuckle_85.py | 13 +- spirit/game/scripts/cards/SWSH4/Shuppet_67.py | 5 +- spirit/game/scripts/cards/SWSH4/Skiddo_17.py | 17 +- spirit/game/scripts/cards/SWSH4/Slugma_27.py | 7 +- .../game/scripts/cards/SWSH4/Snorlax_131.py | 18 +- .../game/scripts/cards/SWSH4/SteelixV_115.py | 7 +- .../game/scripts/cards/SWSH4/SteelixV_176.py | 7 +- .../game/scripts/cards/SWSH4/Swellow_134.py | 14 +- spirit/game/scripts/cards/SWSH4/Swoobat_74.py | 5 +- .../game/scripts/cards/SWSH4/Taillow_133.py | 5 +- .../scripts/cards/SWSH4/TalonflameV_168.py | 9 +- .../scripts/cards/SWSH4/TalonflameV_29.py | 9 +- .../cards/SWSH4/TelescopicSight_160.py | 21 +- .../cards/SWSH4/TelescopicSight_203.py | 21 +- .../game/scripts/cards/SWSH4/Terrakion_92.py | 8 +- .../scripts/cards/SWSH4/TogekissVMAX_141.py | 5 +- .../scripts/cards/SWSH4/TogekissVMAX_191.py | 5 +- .../game/scripts/cards/SWSH4/TogekissV_140.py | 6 +- .../game/scripts/cards/SWSH4/TogekissV_178.py | 6 +- .../game/scripts/cards/SWSH4/Tornadus_142.py | 12 +- .../game/scripts/cards/SWSH4/Toucannon_145.py | 21 +- .../game/scripts/cards/SWSH4/Trubbish_110.py | 6 +- .../game/scripts/cards/SWSH4/Trumbeak_144.py | 11 +- spirit/game/scripts/cards/SWSH4/Tynamo_57.py | 11 +- .../game/scripts/cards/SWSH4/Vaporeon_30.py | 22 +- spirit/game/scripts/cards/SWSH4/Wailmer_31.py | 7 +- spirit/game/scripts/cards/SWSH4/Wailord_32.py | 17 +- spirit/game/scripts/cards/SWSH4/Weedle_1.py | 13 +- .../game/scripts/cards/SWSH4/Whimsicott_76.py | 23 +- .../game/scripts/cards/SWSH4/Whismur_135.py | 5 +- spirit/game/scripts/cards/SWSH4/Woobat_73.py | 18 +- .../scripts/cards/SWSH4/WyndonStadium_161.py | 18 +- spirit/game/scripts/cards/SWSH4/Xerneas_78.py | 8 +- spirit/game/scripts/cards/SWSH4/Yanma_6.py | 5 +- spirit/game/scripts/cards/SWSH4/Yanmega_7.py | 5 +- spirit/game/scripts/cards/SWSH4/Zacian_82.py | 14 +- .../game/scripts/cards/SWSH4/Zamazenta_102.py | 24 +- spirit/game/scripts/cards/SWSH4/Zapdos_48.py | 20 +- .../game/scripts/cards/SWSH4/ZarudeV_167.py | 40 +- spirit/game/scripts/cards/SWSH4/ZarudeV_22.py | 40 +- spirit/game/scripts/cards/SWSH4/Zekrom_60.py | 14 +- spirit/game/scripts/cards/SWSH4/Zygarde_93.py | 7 +- .../scripts/cards/SWSH45/AlcremieVMAX_73.py | 51 +- .../game/scripts/cards/SWSH45/AlcremieV_64.py | 23 +- .../game/scripts/cards/SWSH45/BallGuy_57.py | 35 +- .../game/scripts/cards/SWSH45/BallGuy_65.py | 35 +- .../scripts/cards/SWSH45/BirdKeeper_66.py | 16 +- .../game/scripts/cards/SWSH45/CaraLiss_67.py | 6 +- spirit/game/scripts/cards/SWSH45/Celebi_3.py | 27 +- .../scripts/cards/SWSH45/CinderaceVMAX_19.py | 17 +- .../scripts/cards/SWSH45/CinderaceV_18.py | 22 +- .../scripts/cards/SWSH45/CramorantVMAX_55.py | 5 +- .../scripts/cards/SWSH45/CramorantV_54.py | 21 +- .../game/scripts/cards/SWSH45/Cramorant_28.py | 5 +- .../scripts/cards/SWSH45/CrobatVMAX_45.py | 17 +- .../game/scripts/cards/SWSH45/Decidueye_8.py | 17 +- .../scripts/cards/SWSH45/DhelmiseVMAX_10.py | 20 +- .../game/scripts/cards/SWSH45/DhelmiseV_9.py | 33 +- .../game/scripts/cards/SWSH45/DittoVMAX_51.py | 31 +- .../game/scripts/cards/SWSH45/Drednaw_27.py | 5 +- spirit/game/scripts/cards/SWSH45/Eevee_52.py | 14 +- .../game/scripts/cards/SWSH45/Eldegoss_15.py | 20 +- .../game/scripts/cards/SWSH45/Frosmoth_30.py | 36 +- .../cards/SWSH45/GalarianWeezing_42.py | 25 +- .../game/scripts/cards/SWSH45/GreedentV_53.py | 7 +- .../game/scripts/cards/SWSH45/Grookey_11.py | 5 +- .../scripts/cards/SWSH45/GymTrainer_59.py | 7 +- .../scripts/cards/SWSH45/GymTrainer_68.py | 7 +- .../game/scripts/cards/SWSH45/IndeedeeV_39.py | 15 +- .../game/scripts/cards/SWSH45/Indeedee_56.py | 26 +- .../game/scripts/cards/SWSH45/Koffing_41.py | 17 +- spirit/game/scripts/cards/SWSH45/Kyogre_21.py | 5 +- spirit/game/scripts/cards/SWSH45/Luxio_32.py | 6 +- spirit/game/scripts/cards/SWSH45/Luxray_33.py | 11 +- .../game/scripts/cards/SWSH45/Manaphy_24.py | 27 +- .../scripts/cards/SWSH45/MorpekoVMAX_38.py | 5 +- .../game/scripts/cards/SWSH45/MorpekoV_37.py | 20 +- .../game/scripts/cards/SWSH45/Morpeko_35.py | 10 +- .../game/scripts/cards/SWSH45/Morpeko_36.py | 10 +- spirit/game/scripts/cards/SWSH45/Nickit_47.py | 11 +- spirit/game/scripts/cards/SWSH45/PokKid_70.py | 9 +- .../game/scripts/cards/SWSH45/Reshiram_17.py | 5 +- .../game/scripts/cards/SWSH45/Rillaboom_13.py | 7 +- spirit/game/scripts/cards/SWSH45/Rotom_34.py | 32 +- spirit/game/scripts/cards/SWSH45/Rowlet_6.py | 19 +- .../scripts/cards/SWSH45/RustedShield_61.py | 9 +- .../scripts/cards/SWSH45/RustedSword_62.py | 23 +- spirit/game/scripts/cards/SWSH45/Shinx_31.py | 5 +- spirit/game/scripts/cards/SWSH45/Skyla_72.py | 6 +- spirit/game/scripts/cards/SWSH45/Snom_29.py | 7 +- .../scripts/cards/SWSH45/TeamYellTowel_63.py | 31 +- .../game/scripts/cards/SWSH45/Thievul_48.py | 8 +- .../game/scripts/cards/SWSH45/Thwackey_12.py | 16 +- .../game/scripts/cards/SWSH45/Trapinch_40.py | 5 +- spirit/game/scripts/cards/SWSH45/Tropius_5.py | 23 +- .../game/scripts/cards/SWSH45/Volcanion_25.py | 5 +- spirit/game/scripts/cards/SWSH45/Yanmega_2.py | 9 +- spirit/game/scripts/cards/SWSH45/Zarude_16.py | 5 +- .../game/scripts/cards/SWSH5/Aegislash_107.py | 10 +- .../game/scripts/cards/SWSH5/Aegislash_108.py | 22 +- spirit/game/scripts/cards/SWSH5/Baltoy_57.py | 5 +- .../game/scripts/cards/SWSH5/Bellsprout_1.py | 11 +- .../game/scripts/cards/SWSH5/Bisharp_104.py | 9 +- spirit/game/scripts/cards/SWSH5/Boltund_53.py | 33 +- .../scripts/cards/SWSH5/Bouffalant_118.py | 8 +- .../game/scripts/cards/SWSH5/Bronzong_102.py | 41 +- spirit/game/scripts/cards/SWSH5/Bruno_121.py | 12 +- spirit/game/scripts/cards/SWSH5/Bruno_158.py | 12 +- spirit/game/scripts/cards/SWSH5/Bruno_172.py | 12 +- spirit/game/scripts/cards/SWSH5/Cacturne_5.py | 10 +- .../scripts/cards/SWSH5/CampingGear_122.py | 16 +- spirit/game/scripts/cards/SWSH5/Carkol_79.py | 5 +- .../game/scripts/cards/SWSH5/Carnivine_9.py | 11 +- .../scripts/cards/SWSH5/Centiskorch_30.py | 22 +- spirit/game/scripts/cards/SWSH5/Cherrim_8.py | 34 +- spirit/game/scripts/cards/SWSH5/Cheryl_123.py | 23 +- spirit/game/scripts/cards/SWSH5/Cheryl_159.py | 23 +- spirit/game/scripts/cards/SWSH5/Cheryl_173.py | 23 +- .../game/scripts/cards/SWSH5/Chimecho_59.py | 16 +- spirit/game/scripts/cards/SWSH5/Claydol_58.py | 32 +- .../game/scripts/cards/SWSH5/Coalossal_80.py | 7 +- .../game/scripts/cards/SWSH5/Conkeldurr_75.py | 14 +- .../game/scripts/cards/SWSH5/Corphish_38.py | 7 +- .../cards/SWSH5/CorviknightVMAX_110.py | 15 +- .../cards/SWSH5/CorviknightVMAX_171.py | 15 +- .../scripts/cards/SWSH5/CorviknightV_109.py | 7 +- .../scripts/cards/SWSH5/CorviknightV_156.py | 7 +- .../game/scripts/cards/SWSH5/Crawdaunt_39.py | 14 +- spirit/game/scripts/cards/SWSH5/Crobat_91.py | 12 +- spirit/game/scripts/cards/SWSH5/Dottler_64.py | 12 +- .../game/scripts/cards/SWSH5/Doublade_106.py | 16 +- spirit/game/scripts/cards/SWSH5/Drampa_119.py | 11 +- spirit/game/scripts/cards/SWSH5/Durant_10.py | 17 +- .../game/scripts/cards/SWSH5/Electabuzz_44.py | 4 +- .../game/scripts/cards/SWSH5/Electivire_45.py | 7 +- spirit/game/scripts/cards/SWSH5/Emboar_25.py | 9 +- .../game/scripts/cards/SWSH5/EmpoleonV_145.py | 38 +- .../game/scripts/cards/SWSH5/EmpoleonV_146.py | 38 +- .../game/scripts/cards/SWSH5/EmpoleonV_40.py | 38 +- spirit/game/scripts/cards/SWSH5/Entei_20.py | 26 +- spirit/game/scripts/cards/SWSH5/Espurr_60.py | 5 +- .../game/scripts/cards/SWSH5/ExpShare_126.py | 31 +- .../game/scripts/cards/SWSH5/ExpShare_180.py | 31 +- spirit/game/scripts/cards/SWSH5/Falinks_83.py | 8 +- .../scripts/cards/SWSH5/FlappleVMAX_164.py | 5 +- .../scripts/cards/SWSH5/FlappleVMAX_19.py | 5 +- .../game/scripts/cards/SWSH5/FlappleV_143.py | 5 +- .../game/scripts/cards/SWSH5/FlappleV_18.py | 5 +- .../game/scripts/cards/SWSH5/Fomantis_14.py | 5 +- .../game/scripts/cards/SWSH5/Frillish_41.py | 16 +- .../scripts/cards/SWSH5/GalarianMrMime_34.py | 6 +- .../scripts/cards/SWSH5/GalarianMrRime_35.py | 24 +- .../scripts/cards/SWSH5/GalarianSlowbro_92.py | 20 +- .../cards/SWSH5/GalarianSlowpoke_54.py | 23 +- spirit/game/scripts/cards/SWSH5/Gliscor_72.py | 5 +- spirit/game/scripts/cards/SWSH5/Golbat_90.py | 15 +- spirit/game/scripts/cards/SWSH5/Grumpig_56.py | 7 +- spirit/game/scripts/cards/SWSH5/Heatmor_26.py | 14 +- .../game/scripts/cards/SWSH5/Honchkrow_94.py | 25 +- .../game/scripts/cards/SWSH5/Honedge_105.py | 5 +- spirit/game/scripts/cards/SWSH5/Horsea_31.py | 5 +- .../game/scripts/cards/SWSH5/Houndoom_179.py | 43 +- .../game/scripts/cards/SWSH5/Houndoom_96.py | 43 +- .../game/scripts/cards/SWSH5/Indeedee_120.py | 8 +- .../game/scripts/cards/SWSH5/Jellicent_42.py | 11 +- spirit/game/scripts/cards/SWSH5/Kingdra_33.py | 29 +- .../scripts/cards/SWSH5/KorrinasFocus_128.py | 5 +- .../scripts/cards/SWSH5/KorrinasFocus_160.py | 5 +- .../scripts/cards/SWSH5/KorrinasFocus_174.py | 5 +- .../scripts/cards/SWSH5/KricketuneV_142.py | 25 +- .../game/scripts/cards/SWSH5/KricketuneV_6.py | 25 +- .../game/scripts/cards/SWSH5/LevelBall_129.py | 16 +- .../game/scripts/cards/SWSH5/LevelBall_181.py | 16 +- .../scripts/cards/SWSH5/Lickilicky_114.py | 26 +- .../game/scripts/cards/SWSH5/Lurantis_15.py | 5 +- spirit/game/scripts/cards/SWSH5/Luxio_47.py | 5 +- spirit/game/scripts/cards/SWSH5/Luxray_48.py | 16 +- spirit/game/scripts/cards/SWSH5/Mankey_66.py | 5 +- spirit/game/scripts/cards/SWSH5/Marowak_70.py | 8 +- spirit/game/scripts/cards/SWSH5/Mawile_100.py | 14 +- .../game/scripts/cards/SWSH5/Meowstic_61.py | 37 +- spirit/game/scripts/cards/SWSH5/Mienfoo_76.py | 5 +- .../game/scripts/cards/SWSH5/Mienshao_77.py | 25 +- .../game/scripts/cards/SWSH5/MimikyuV_148.py | 19 +- .../game/scripts/cards/SWSH5/MimikyuV_62.py | 19 +- spirit/game/scripts/cards/SWSH5/Morpeko_98.py | 14 +- spirit/game/scripts/cards/SWSH5/Murkrow_93.py | 9 +- .../game/scripts/cards/SWSH5/NecrozmaV_149.py | 13 +- .../game/scripts/cards/SWSH5/NecrozmaV_63.py | 13 +- .../game/scripts/cards/SWSH5/Octillery_178.py | 20 +- .../game/scripts/cards/SWSH5/Octillery_37.py | 20 +- spirit/game/scripts/cards/SWSH5/Onix_68.py | 5 +- .../game/scripts/cards/SWSH5/Orbeetle_65.py | 38 +- .../game/scripts/cards/SWSH5/Pachirisu_49.py | 11 +- .../game/scripts/cards/SWSH5/Pawniard_103.py | 5 +- spirit/game/scripts/cards/SWSH5/Phoebe_130.py | 14 +- spirit/game/scripts/cards/SWSH5/Phoebe_161.py | 14 +- spirit/game/scripts/cards/SWSH5/Phoebe_175.py | 14 +- .../game/scripts/cards/SWSH5/Primeape_67.py | 24 +- .../game/scripts/cards/SWSH5/Purugly_116.py | 14 +- .../SWSH5/RapidStrikeScrollofSwirls_131.py | 24 +- .../SWSH5/RapidStrikeStyleMustard_132.py | 31 +- .../SWSH5/RapidStrikeStyleMustard_162.py | 31 +- .../SWSH5/RapidStrikeStyleMustard_176.py | 31 +- .../cards/SWSH5/RapidStrikeUrshifuVMAX_169.py | 16 +- .../cards/SWSH5/RapidStrikeUrshifuVMAX_170.py | 22 +- .../cards/SWSH5/RapidStrikeUrshifuVMAX_88.py | 22 +- .../cards/SWSH5/RapidStrikeUrshifuV_152.py | 5 +- .../cards/SWSH5/RapidStrikeUrshifuV_153.py | 5 +- .../cards/SWSH5/RapidStrikeUrshifuV_87.py | 5 +- .../game/scripts/cards/SWSH5/Rolycoly_78.py | 5 +- .../game/scripts/cards/SWSH5/Salandit_27.py | 7 +- .../game/scripts/cards/SWSH5/Salazzle_28.py | 17 +- .../game/scripts/cards/SWSH5/Sandaconda_82.py | 14 +- .../game/scripts/cards/SWSH5/Scatterbug_11.py | 5 +- .../SWSH5/SingleStrikeScrollofScorn_133.py | 22 +- .../SWSH5/SingleStrikeStyleMustard_134.py | 24 +- .../SWSH5/SingleStrikeStyleMustard_163.py | 24 +- .../SWSH5/SingleStrikeStyleMustard_177.py | 24 +- .../SWSH5/SingleStrikeUrshifuVMAX_167.py | 12 +- .../SWSH5/SingleStrikeUrshifuVMAX_168.py | 12 +- .../cards/SWSH5/SingleStrikeUrshifuVMAX_86.py | 12 +- .../cards/SWSH5/SingleStrikeUrshifuV_150.py | 19 +- .../cards/SWSH5/SingleStrikeUrshifuV_151.py | 19 +- .../cards/SWSH5/SingleStrikeUrshifuV_85.py | 19 +- .../game/scripts/cards/SWSH5/Sizzlipede_29.py | 7 +- .../cards/SWSH5/SordwardShielbert_135.py | 26 +- spirit/game/scripts/cards/SWSH5/Spewpa_12.py | 5 +- spirit/game/scripts/cards/SWSH5/Steelix_99.py | 5 +- .../scripts/cards/SWSH5/Stonjourner_84.py | 7 +- .../scripts/cards/SWSH5/StoutlandV_157.py | 7 +- .../game/scripts/cards/SWSH5/TapuBulu_16.py | 20 +- .../scripts/cards/SWSH5/TapuKokoVMAX_166.py | 14 +- .../scripts/cards/SWSH5/TapuKokoVMAX_51.py | 14 +- .../game/scripts/cards/SWSH5/TapuKokoV_147.py | 5 +- .../game/scripts/cards/SWSH5/TapuKokoV_50.py | 5 +- .../scripts/cards/SWSH5/ToolJammer_136.py | 32 +- .../cards/SWSH5/TowerofDarkness_137.py | 33 +- .../scripts/cards/SWSH5/TowerofWaters_138.py | 7 +- .../scripts/cards/SWSH5/TyranitarV_154.py | 9 +- .../scripts/cards/SWSH5/TyranitarV_155.py | 9 +- .../game/scripts/cards/SWSH5/TyranitarV_97.py | 9 +- .../scripts/cards/SWSH5/UrnofVitality_139.py | 14 +- .../scripts/cards/SWSH5/VictiniVMAX_165.py | 32 +- .../scripts/cards/SWSH5/VictiniVMAX_22.py | 32 +- .../game/scripts/cards/SWSH5/VictiniV_144.py | 9 +- .../game/scripts/cards/SWSH5/VictiniV_21.py | 9 +- .../game/scripts/cards/SWSH5/Victreebel_3.py | 7 +- .../game/scripts/cards/SWSH5/Vivillon_13.py | 20 +- .../game/scripts/cards/SWSH5/Weepinbell_2.py | 15 +- spirit/game/scripts/cards/SWSH5/Yamper_52.py | 39 +- spirit/game/scripts/cards/SWSH5/Zubat_89.py | 5 +- .../game/scripts/cards/SWSH6/Abomasnow_10.py | 22 +- spirit/game/scripts/cards/SWSH6/Agatha_129.py | 14 +- spirit/game/scripts/cards/SWSH6/Agatha_186.py | 14 +- spirit/game/scripts/cards/SWSH6/Agatha_210.py | 14 +- spirit/game/scripts/cards/SWSH6/Aggron_111.py | 8 +- .../game/scripts/cards/SWSH6/Ampharos_49.py | 20 +- spirit/game/scripts/cards/SWSH6/Avery_130.py | 20 +- spirit/game/scripts/cards/SWSH6/Avery_187.py | 20 +- spirit/game/scripts/cards/SWSH6/Avery_211.py | 20 +- spirit/game/scripts/cards/SWSH6/Banette_63.py | 27 +- spirit/game/scripts/cards/SWSH6/Beedrill_3.py | 24 +- .../scripts/cards/SWSH6/BlazikenVMAX_200.py | 41 +- .../scripts/cards/SWSH6/BlazikenVMAX_201.py | 41 +- .../scripts/cards/SWSH6/BlazikenVMAX_21.py | 41 +- .../game/scripts/cards/SWSH6/BlazikenV_161.py | 9 +- .../game/scripts/cards/SWSH6/BlazikenV_20.py | 9 +- .../game/scripts/cards/SWSH6/BlisseyV_119.py | 33 +- .../game/scripts/cards/SWSH6/BlisseyV_182.py | 33 +- .../game/scripts/cards/SWSH6/BlisseyV_183.py | 33 +- spirit/game/scripts/cards/SWSH6/Blitzle_50.py | 9 +- spirit/game/scripts/cards/SWSH6/Brawly_131.py | 11 +- spirit/game/scripts/cards/SWSH6/Brawly_188.py | 11 +- spirit/game/scripts/cards/SWSH6/Brawly_212.py | 11 +- .../game/scripts/cards/SWSH6/Bronzong_223.py | 41 +- .../game/scripts/cards/SWSH6/Caitlin_132.py | 22 +- .../game/scripts/cards/SWSH6/Caitlin_189.py | 22 +- .../game/scripts/cards/SWSH6/Caitlin_213.py | 22 +- .../cards/SWSH6/CastformRainyForm_33.py | 31 +- .../cards/SWSH6/CastformSnowyForm_34.py | 30 +- .../cards/SWSH6/CastformSunnyForm_22.py | 32 +- .../game/scripts/cards/SWSH6/Castform_121.py | 31 +- .../scripts/cards/SWSH6/CelebiVMAX_199.py | 24 +- .../game/scripts/cards/SWSH6/CelebiVMAX_8.py | 24 +- .../game/scripts/cards/SWSH6/CelebiV_160.py | 21 +- spirit/game/scripts/cards/SWSH6/CelebiV_7.py | 21 +- .../game/scripts/cards/SWSH6/Cinderace_28.py | 32 +- .../game/scripts/cards/SWSH6/Cobalion_114.py | 4 +- .../scripts/cards/SWSH6/Crabominable_85.py | 5 +- .../game/scripts/cards/SWSH6/Crabrawler_84.py | 5 +- .../game/scripts/cards/SWSH6/Cresselia_64.py | 30 +- .../scripts/cards/SWSH6/CrushingGloves_133.py | 7 +- .../game/scripts/cards/SWSH6/Deerling_11.py | 5 +- .../game/scripts/cards/SWSH6/Delibird_32.py | 27 +- spirit/game/scripts/cards/SWSH6/Diglett_76.py | 5 +- spirit/game/scripts/cards/SWSH6/Doctor_134.py | 14 +- spirit/game/scripts/cards/SWSH6/Doctor_190.py | 14 +- spirit/game/scripts/cards/SWSH6/Doctor_214.py | 14 +- .../game/scripts/cards/SWSH6/Drizzile_42.py | 5 +- spirit/game/scripts/cards/SWSH6/Dugtrio_77.py | 15 +- .../scripts/cards/SWSH6/DynaTreeHill_135.py | 5 +- .../scripts/cards/SWSH6/EchoingHorn_136.py | 34 +- .../scripts/cards/SWSH6/EchoingHorn_225.py | 34 +- .../game/scripts/cards/SWSH6/Electrode_222.py | 38 +- .../cards/SWSH6/ExpeditionUniform_137.py | 19 +- .../cards/SWSH6/FireResistantGloves_138.py | 7 +- .../game/scripts/cards/SWSH6/Flannery_139.py | 40 +- .../game/scripts/cards/SWSH6/Flannery_191.py | 40 +- .../game/scripts/cards/SWSH6/Flannery_215.py | 40 +- .../scripts/cards/SWSH6/FogCrystal_140.py | 20 +- .../scripts/cards/SWSH6/FogCrystal_227.py | 20 +- .../game/scripts/cards/SWSH6/Froslass_36.py | 27 +- .../game/scripts/cards/SWSH6/Furfrou_126.py | 9 +- .../cards/SWSH6/GalarianArticunoV_169.py | 25 +- .../cards/SWSH6/GalarianArticunoV_170.py | 21 +- .../cards/SWSH6/GalarianArticunoV_58.py | 21 +- .../cards/SWSH6/GalarianChestplate_141.py | 30 +- .../cards/SWSH6/GalarianFarfetchd_78.py | 10 +- .../cards/SWSH6/GalarianMoltresV_176.py | 19 +- .../cards/SWSH6/GalarianMoltresV_177.py | 19 +- .../cards/SWSH6/GalarianMoltresV_97.py | 19 +- .../cards/SWSH6/GalarianRapidashV_167.py | 25 +- .../cards/SWSH6/GalarianRapidashV_168.py | 25 +- .../cards/SWSH6/GalarianRunerigus_83.py | 24 +- .../cards/SWSH6/GalarianSirfetchd_79.py | 11 +- .../cards/SWSH6/GalarianSlowkingVMAX_100.py | 11 +- .../cards/SWSH6/GalarianSlowkingVMAX_207.py | 11 +- .../cards/SWSH6/GalarianSlowkingV_178.py | 5 +- .../cards/SWSH6/GalarianSlowkingV_179.py | 5 +- .../cards/SWSH6/GalarianSlowkingV_99.py | 5 +- .../cards/SWSH6/GalarianSlowking_98.py | 23 +- .../scripts/cards/SWSH6/GalarianWeezing_96.py | 33 +- .../scripts/cards/SWSH6/GalarianYamask_82.py | 5 +- .../cards/SWSH6/GalarianZapdosV_173.py | 30 +- .../cards/SWSH6/GalarianZapdosV_174.py | 30 +- .../scripts/cards/SWSH6/GalarianZapdosV_80.py | 30 +- spirit/game/scripts/cards/SWSH6/Gallade_81.py | 16 +- .../game/scripts/cards/SWSH6/Gardevoir_61.py | 31 +- spirit/game/scripts/cards/SWSH6/Gastly_55.py | 7 +- spirit/game/scripts/cards/SWSH6/Gengar_57.py | 5 +- spirit/game/scripts/cards/SWSH6/Golurk_66.py | 12 +- .../game/scripts/cards/SWSH6/Grapploct_92.py | 21 +- .../game/scripts/cards/SWSH6/Greedent_128.py | 25 +- spirit/game/scripts/cards/SWSH6/Grookey_16.py | 5 +- .../game/scripts/cards/SWSH6/Hatterene_73.py | 37 +- spirit/game/scripts/cards/SWSH6/Hattrem_72.py | 5 +- .../game/scripts/cards/SWSH6/Heracross_6.py | 5 +- spirit/game/scripts/cards/SWSH6/Honey_142.py | 13 +- spirit/game/scripts/cards/SWSH6/Honey_192.py | 13 +- .../cards/SWSH6/IceRiderCalyrexVMAX_202.py | 19 +- .../cards/SWSH6/IceRiderCalyrexVMAX_203.py | 19 +- .../cards/SWSH6/IceRiderCalyrexVMAX_46.py | 19 +- .../cards/SWSH6/IceRiderCalyrexV_163.py | 5 +- .../cards/SWSH6/IceRiderCalyrexV_164.py | 5 +- .../cards/SWSH6/IceRiderCalyrexV_45.py | 5 +- .../game/scripts/cards/SWSH6/Inteleon_43.py | 22 +- .../cards/SWSH6/JustifiedGloves_143.py | 7 +- spirit/game/scripts/cards/SWSH6/Kakuna_2.py | 5 +- .../cards/SWSH6/KarensConviction_144.py | 23 +- .../cards/SWSH6/KarensConviction_193.py | 23 +- .../cards/SWSH6/KarensConviction_216.py | 23 +- .../game/scripts/cards/SWSH6/Kecleon_122.py | 24 +- spirit/game/scripts/cards/SWSH6/Kirlia_60.py | 11 +- spirit/game/scripts/cards/SWSH6/Klara_194.py | 5 +- spirit/game/scripts/cards/SWSH6/Klara_217.py | 5 +- spirit/game/scripts/cards/SWSH6/Koffing_94.py | 11 +- spirit/game/scripts/cards/SWSH6/Kubfu_93.py | 6 +- spirit/game/scripts/cards/SWSH6/Lapras_29.py | 23 +- .../game/scripts/cards/SWSH6/Larvesta_23.py | 14 +- spirit/game/scripts/cards/SWSH6/Ledian_5.py | 8 +- spirit/game/scripts/cards/SWSH6/Ledyba_4.py | 5 +- .../game/scripts/cards/SWSH6/LiepardV_104.py | 22 +- .../game/scripts/cards/SWSH6/LiepardV_180.py | 22 +- .../game/scripts/cards/SWSH6/Lycanroc_87.py | 10 +- spirit/game/scripts/cards/SWSH6/Malamar_70.py | 23 +- spirit/game/scripts/cards/SWSH6/Mareep_47.py | 5 +- spirit/game/scripts/cards/SWSH6/Melony_146.py | 36 +- spirit/game/scripts/cards/SWSH6/Melony_195.py | 36 +- spirit/game/scripts/cards/SWSH6/Melony_218.py | 36 +- .../scripts/cards/SWSH6/MetagrossVMAX_113.py | 11 +- .../scripts/cards/SWSH6/MetagrossVMAX_208.py | 11 +- .../scripts/cards/SWSH6/MetagrossV_112.py | 10 +- .../scripts/cards/SWSH6/MetagrossV_181.py | 10 +- .../scripts/cards/SWSH6/OldCemetery_147.py | 27 +- .../game/scripts/cards/SWSH6/Passimian_88.py | 24 +- spirit/game/scripts/cards/SWSH6/Peonia_149.py | 6 +- spirit/game/scripts/cards/SWSH6/Peonia_196.py | 6 +- spirit/game/scripts/cards/SWSH6/Peonia_219.py | 6 +- spirit/game/scripts/cards/SWSH6/Peony_150.py | 19 +- spirit/game/scripts/cards/SWSH6/Peony_197.py | 19 +- spirit/game/scripts/cards/SWSH6/Peony_220.py | 19 +- .../game/scripts/cards/SWSH6/Porygon2_117.py | 5 +- .../game/scripts/cards/SWSH6/PorygonZ_118.py | 20 +- .../game/scripts/cards/SWSH6/Qwilfish_101.py | 25 +- spirit/game/scripts/cards/SWSH6/Raboot_27.py | 12 +- spirit/game/scripts/cards/SWSH6/Ralts_59.py | 7 +- .../SWSH6/RapidStrikeScrolloftheSkies_151.py | 22 +- .../cards/SWSH6/RapidStrikeUrshifu_44.py | 10 +- .../game/scripts/cards/SWSH6/Rillaboom_18.py | 23 +- .../game/scripts/cards/SWSH6/Rockruff_86.py | 16 +- .../scripts/cards/SWSH6/RuggedHelmet_152.py | 30 +- .../scripts/cards/SWSH6/RuggedHelmet_228.py | 30 +- .../scripts/cards/SWSH6/SandacondaVMAX_206.py | 14 +- .../scripts/cards/SWSH6/SandacondaVMAX_90.py | 14 +- .../scripts/cards/SWSH6/SandacondaV_175.py | 5 +- .../scripts/cards/SWSH6/SandacondaV_89.py | 5 +- .../game/scripts/cards/SWSH6/Sawsbuck_12.py | 7 +- .../game/scripts/cards/SWSH6/Scolipede_107.py | 9 +- .../game/scripts/cards/SWSH6/Seviper_102.py | 16 +- .../cards/SWSH6/ShadowRiderCalyrexVMAX_204.py | 55 +- .../cards/SWSH6/ShadowRiderCalyrexVMAX_205.py | 55 +- .../cards/SWSH6/ShadowRiderCalyrexVMAX_75.py | 55 +- .../cards/SWSH6/ShadowRiderCalyrexV_171.py | 36 +- .../cards/SWSH6/ShadowRiderCalyrexV_172.py | 36 +- .../cards/SWSH6/ShadowRiderCalyrexV_74.py | 36 +- .../game/scripts/cards/SWSH6/Shaymin_123.py | 15 +- .../game/scripts/cards/SWSH6/Siebold_153.py | 24 +- .../game/scripts/cards/SWSH6/Siebold_198.py | 24 +- .../game/scripts/cards/SWSH6/Siebold_221.py | 24 +- .../SWSH6/SingleStrikeScrollofPiercing_154.py | 25 +- .../cards/SWSH6/SingleStrikeUrshifu_108.py | 16 +- .../game/scripts/cards/SWSH6/Skwovet_127.py | 20 +- .../game/scripts/cards/SWSH6/Slurpuff_68.py | 18 +- spirit/game/scripts/cards/SWSH6/Sneasel_30.py | 17 +- .../game/scripts/cards/SWSH6/Snorlax_224.py | 18 +- spirit/game/scripts/cards/SWSH6/Sobble_41.py | 13 +- .../game/scripts/cards/SWSH6/Spiritomb_103.py | 18 +- spirit/game/scripts/cards/SWSH6/Steenee_14.py | 5 +- spirit/game/scripts/cards/SWSH6/Swirlix_67.py | 5 +- .../game/scripts/cards/SWSH6/TapuFini_40.py | 18 +- spirit/game/scripts/cards/SWSH6/Tauros_115.py | 18 +- .../game/scripts/cards/SWSH6/Thundurus_52.py | 32 +- .../game/scripts/cards/SWSH6/Thwackey_17.py | 11 +- .../scripts/cards/SWSH6/TornadusVMAX_125.py | 14 +- .../scripts/cards/SWSH6/TornadusVMAX_209.py | 14 +- .../game/scripts/cards/SWSH6/TornadusV_124.py | 7 +- .../game/scripts/cards/SWSH6/TornadusV_184.py | 7 +- .../game/scripts/cards/SWSH6/TornadusV_185.py | 7 +- .../game/scripts/cards/SWSH6/Tsareena_15.py | 13 +- .../scripts/cards/SWSH6/UrnofVitality_229.py | 14 +- .../game/scripts/cards/SWSH6/Venipede_105.py | 7 +- .../scripts/cards/SWSH6/VolcanionV_162.py | 10 +- .../game/scripts/cards/SWSH6/VolcanionV_25.py | 10 +- .../game/scripts/cards/SWSH6/Volcarona_24.py | 5 +- spirit/game/scripts/cards/SWSH6/Walrein_39.py | 14 +- spirit/game/scripts/cards/SWSH6/Weavile_31.py | 39 +- .../scripts/cards/SWSH6/WeedingGloves_155.py | 11 +- spirit/game/scripts/cards/SWSH6/Weezing_95.py | 28 +- .../cards/SWSH6/WelcomingLantern_156.py | 15 +- .../cards/SWSH6/WelcomingLantern_230.py | 15 +- .../scripts/cards/SWSH6/Whirlipede_106.py | 7 +- .../game/scripts/cards/SWSH6/Zangoose_120.py | 29 +- spirit/game/scripts/cards/SWSH6/Zarude_19.py | 27 +- .../game/scripts/cards/SWSH6/Zebstrika_51.py | 18 +- .../game/scripts/cards/SWSH6/ZeraoraV_165.py | 30 +- .../game/scripts/cards/SWSH6/ZeraoraV_166.py | 30 +- .../game/scripts/cards/SWSH6/ZeraoraV_53.py | 30 +- .../game/scripts/cards/SWSH7/Altaria_106.py | 22 +- .../game/scripts/cards/SWSH7/Ampharos_56.py | 24 +- .../game/scripts/cards/SWSH7/Appletun_121.py | 17 +- .../scripts/cards/SWSH7/ArctovishV_176.py | 18 +- .../game/scripts/cards/SWSH7/ArctovishV_48.py | 18 +- .../game/scripts/cards/SWSH7/AromaLady_141.py | 14 +- .../game/scripts/cards/SWSH7/AromaLady_199.py | 14 +- .../game/scripts/cards/SWSH7/AromaLady_221.py | 14 +- spirit/game/scripts/cards/SWSH7/Avalugg_45.py | 5 +- spirit/game/scripts/cards/SWSH7/Boldore_87.py | 9 +- .../scripts/cards/SWSH7/BoostShake_142.py | 25 +- .../scripts/cards/SWSH7/BoostShake_229.py | 25 +- .../game/scripts/cards/SWSH7/Braviary_137.py | 8 +- .../game/scripts/cards/SWSH7/Copycat_143.py | 13 +- .../game/scripts/cards/SWSH7/Copycat_200.py | 13 +- .../game/scripts/cards/SWSH7/Copycat_222.py | 13 +- .../game/scripts/cards/SWSH7/Cresselia_228.py | 30 +- spirit/game/scripts/cards/SWSH7/Crustle_12.py | 5 +- .../game/scripts/cards/SWSH7/Cryogonal_43.py | 6 +- .../scripts/cards/SWSH7/CrystalCave_144.py | 35 +- .../scripts/cards/SWSH7/CrystalCave_230.py | 35 +- spirit/game/scripts/cards/SWSH7/Deino_113.py | 5 +- spirit/game/scripts/cards/SWSH7/Dialga_112.py | 14 +- .../scripts/cards/SWSH7/DiggingGloves_145.py | 7 +- .../scripts/cards/SWSH7/DracozoltVMAX_210.py | 22 +- .../scripts/cards/SWSH7/DracozoltVMAX_59.py | 22 +- .../scripts/cards/SWSH7/DracozoltV_178.py | 21 +- .../game/scripts/cards/SWSH7/DracozoltV_58.py | 21 +- .../scripts/cards/SWSH7/DragoniteV_191.py | 7 +- spirit/game/scripts/cards/SWSH7/Drampa_119.py | 12 +- .../game/scripts/cards/SWSH7/DreamBall_146.py | 16 +- .../scripts/cards/SWSH7/DuraludonVMAX_123.py | 23 +- .../scripts/cards/SWSH7/DuraludonVMAX_219.py | 23 +- .../scripts/cards/SWSH7/DuraludonVMAX_220.py | 23 +- .../scripts/cards/SWSH7/DuraludonV_122.py | 5 +- .../scripts/cards/SWSH7/DuraludonV_197.py | 5 +- .../scripts/cards/SWSH7/DuraludonV_198.py | 5 +- spirit/game/scripts/cards/SWSH7/Dwebble_11.py | 5 +- spirit/game/scripts/cards/SWSH7/Eevee_125.py | 11 +- spirit/game/scripts/cards/SWSH7/Eiscue_47.py | 28 +- .../game/scripts/cards/SWSH7/Eldegoss_16.py | 13 +- .../scripts/cards/SWSH7/ElementalBadge_147.py | 16 +- spirit/game/scripts/cards/SWSH7/Emolga_57.py | 7 +- spirit/game/scripts/cards/SWSH7/Entei_19.py | 17 +- .../game/scripts/cards/SWSH7/EspeonVMAX_65.py | 17 +- .../game/scripts/cards/SWSH7/EspeonV_179.py | 8 +- .../game/scripts/cards/SWSH7/EspeonV_180.py | 8 +- spirit/game/scripts/cards/SWSH7/EspeonV_64.py | 8 +- spirit/game/scripts/cards/SWSH7/Feebas_37.py | 5 +- spirit/game/scripts/cards/SWSH7/Flaaffy_55.py | 23 +- spirit/game/scripts/cards/SWSH7/Flabb_71.py | 7 +- .../game/scripts/cards/SWSH7/Flapple_120.py | 16 +- .../scripts/cards/SWSH7/FlareonVMAX_18.py | 6 +- .../game/scripts/cards/SWSH7/FlareonV_169.py | 14 +- .../scripts/cards/SWSH7/Fletchinder_139.py | 5 +- .../scripts/cards/SWSH7/Fletchling_138.py | 15 +- spirit/game/scripts/cards/SWSH7/Floette_72.py | 5 +- spirit/game/scripts/cards/SWSH7/Florges_73.py | 35 +- .../game/scripts/cards/SWSH7/Froslass_226.py | 27 +- .../scripts/cards/SWSH7/FullFaceGuard_148.py | 30 +- .../scripts/cards/SWSH7/FullFaceGuard_231.py | 30 +- .../cards/SWSH7/GalarianArticuno_63.py | 46 +- .../scripts/cards/SWSH7/GalarianMoltres_93.py | 27 +- .../scripts/cards/SWSH7/GalarianZapdos_82.py | 48 +- .../scripts/cards/SWSH7/GarbodorVMAX_101.py | 16 +- .../scripts/cards/SWSH7/GarbodorVMAX_216.py | 16 +- .../game/scripts/cards/SWSH7/GarbodorV_100.py | 7 +- .../game/scripts/cards/SWSH7/GarbodorV_190.py | 7 +- .../game/scripts/cards/SWSH7/Gigalith_88.py | 18 +- .../scripts/cards/SWSH7/GlaceonVMAX_208.py | 22 +- .../scripts/cards/SWSH7/GlaceonVMAX_209.py | 22 +- .../scripts/cards/SWSH7/GlaceonVMAX_41.py | 22 +- .../game/scripts/cards/SWSH7/GlaceonV_174.py | 28 +- .../game/scripts/cards/SWSH7/GlaceonV_175.py | 28 +- .../game/scripts/cards/SWSH7/GlaceonV_40.py | 28 +- spirit/game/scripts/cards/SWSH7/Golduck_25.py | 7 +- .../game/scripts/cards/SWSH7/GolurkV_181.py | 3 +- .../game/scripts/cards/SWSH7/GolurkV_182.py | 3 +- spirit/game/scripts/cards/SWSH7/GolurkV_70.py | 3 +- spirit/game/scripts/cards/SWSH7/Gordie_149.py | 22 +- spirit/game/scripts/cards/SWSH7/Gordie_201.py | 22 +- spirit/game/scripts/cards/SWSH7/Gordie_223.py | 22 +- .../game/scripts/cards/SWSH7/Gossifleur_15.py | 5 +- .../game/scripts/cards/SWSH7/Gourgeist_77.py | 29 +- .../scripts/cards/SWSH7/GyaradosVMAX_207.py | 5 +- .../scripts/cards/SWSH7/GyaradosVMAX_29.py | 5 +- .../game/scripts/cards/SWSH7/GyaradosV_171.py | 5 +- .../game/scripts/cards/SWSH7/GyaradosV_28.py | 5 +- .../game/scripts/cards/SWSH7/Herdier_134.py | 5 +- .../game/scripts/cards/SWSH7/Hippowdon_85.py | 5 +- .../game/scripts/cards/SWSH7/Hitmonchan_81.py | 13 +- spirit/game/scripts/cards/SWSH7/Hoppip_2.py | 5 +- .../game/scripts/cards/SWSH7/Hydreigon_115.py | 10 +- spirit/game/scripts/cards/SWSH7/Hypno_62.py | 26 +- .../game/scripts/cards/SWSH7/Inteleon_227.py | 22 +- .../scripts/cards/SWSH7/JolteonVMAX_51.py | 18 +- .../game/scripts/cards/SWSH7/JolteonV_177.py | 7 +- spirit/game/scripts/cards/SWSH7/Jumpluff_4.py | 3 +- spirit/game/scripts/cards/SWSH7/Kyurem_116.py | 26 +- spirit/game/scripts/cards/SWSH7/Lanturn_53.py | 5 +- .../scripts/cards/SWSH7/LeafeonVMAX_204.py | 15 +- .../scripts/cards/SWSH7/LeafeonVMAX_205.py | 15 +- .../game/scripts/cards/SWSH7/LeafeonVMAX_8.py | 15 +- .../game/scripts/cards/SWSH7/LeafeonV_166.py | 13 +- .../game/scripts/cards/SWSH7/LeafeonV_167.py | 13 +- spirit/game/scripts/cards/SWSH7/LeafeonV_7.py | 13 +- .../game/scripts/cards/SWSH7/Lilligant_10.py | 10 +- .../game/scripts/cards/SWSH7/Lillipup_133.py | 7 +- spirit/game/scripts/cards/SWSH7/Lotad_32.py | 5 +- .../scripts/cards/SWSH7/LuckyIcePop_150.py | 16 +- .../game/scripts/cards/SWSH7/Ludicolo_34.py | 18 +- spirit/game/scripts/cards/SWSH7/Luvdisc_39.py | 13 +- .../scripts/cards/SWSH7/LycanrocVMAX_213.py | 25 +- .../scripts/cards/SWSH7/LycanrocVMAX_92.py | 25 +- .../game/scripts/cards/SWSH7/LycanrocV_187.py | 4 +- .../game/scripts/cards/SWSH7/LycanrocV_91.py | 4 +- .../game/scripts/cards/SWSH7/Marshadow_80.py | 33 +- .../game/scripts/cards/SWSH7/MedichamV_185.py | 11 +- .../game/scripts/cards/SWSH7/MedichamV_186.py | 11 +- .../game/scripts/cards/SWSH7/MedichamV_83.py | 11 +- spirit/game/scripts/cards/SWSH7/Milotic_38.py | 19 +- .../scripts/cards/SWSH7/MoonSunBadge_151.py | 30 +- .../game/scripts/cards/SWSH7/NoivernV_117.py | 12 +- .../game/scripts/cards/SWSH7/NoivernV_195.py | 12 +- .../game/scripts/cards/SWSH7/NoivernV_196.py | 12 +- spirit/game/scripts/cards/SWSH7/Nuzleaf_96.py | 7 +- spirit/game/scripts/cards/SWSH7/Pikachu_49.py | 6 +- spirit/game/scripts/cards/SWSH7/Pinsir_1.py | 11 +- spirit/game/scripts/cards/SWSH7/Pyroar_23.py | 5 +- spirit/game/scripts/cards/SWSH7/Raichu_50.py | 20 +- spirit/game/scripts/cards/SWSH7/Raihan_152.py | 25 +- spirit/game/scripts/cards/SWSH7/Raihan_202.py | 25 +- spirit/game/scripts/cards/SWSH7/Raihan_224.py | 25 +- .../RapidStrikeScrolloftheFlyingDragon_153.py | 30 +- .../scripts/cards/SWSH7/RayquazaVMAX_111.py | 43 +- .../scripts/cards/SWSH7/RayquazaVMAX_217.py | 43 +- .../scripts/cards/SWSH7/RayquazaVMAX_218.py | 43 +- .../game/scripts/cards/SWSH7/RayquazaV_110.py | 41 +- .../game/scripts/cards/SWSH7/RayquazaV_193.py | 45 +- .../game/scripts/cards/SWSH7/RayquazaV_194.py | 45 +- .../scripts/cards/SWSH7/RescueCarrier_154.py | 17 +- .../scripts/cards/SWSH7/RibbonBadge_155.py | 25 +- .../game/scripts/cards/SWSH7/Ribombee_79.py | 28 +- .../scripts/cards/SWSH7/RubberGloves_156.py | 11 +- .../game/scripts/cards/SWSH7/Rufflet_136.py | 5 +- spirit/game/scripts/cards/SWSH7/Sableye_67.py | 19 +- .../game/scripts/cards/SWSH7/Salamence_109.py | 22 +- spirit/game/scripts/cards/SWSH7/Scrafty_99.py | 12 +- spirit/game/scripts/cards/SWSH7/Scraggy_98.py | 5 +- spirit/game/scripts/cards/SWSH7/Seedot_5.py | 19 +- .../game/scripts/cards/SWSH7/Seismitoad_90.py | 13 +- .../game/scripts/cards/SWSH7/Sharpedo_36.py | 15 +- .../game/scripts/cards/SWSH7/Shelgon_108.py | 5 +- spirit/game/scripts/cards/SWSH7/Shiftry_97.py | 40 +- .../scripts/cards/SWSH7/ShoppingCenter_157.py | 37 +- ...SingleStrikeScrolloftheFangedDragon_158.py | 25 +- spirit/game/scripts/cards/SWSH7/Skiploom_3.py | 27 +- .../game/scripts/cards/SWSH7/Slaking_131.py | 13 +- .../game/scripts/cards/SWSH7/Slakoth_129.py | 7 +- .../game/scripts/cards/SWSH7/Smeargle_128.py | 27 +- .../scripts/cards/SWSH7/SnowLeafBadge_159.py | 27 +- .../scripts/cards/SWSH7/SpiritMask_160.py | 25 +- .../cards/SWSH7/StormyMountains_161.py | 27 +- .../cards/SWSH7/StormyMountains_232.py | 27 +- .../game/scripts/cards/SWSH7/Stoutland_135.py | 19 +- .../game/scripts/cards/SWSH7/SuicuneV_173.py | 16 +- .../game/scripts/cards/SWSH7/SuicuneV_31.py | 16 +- spirit/game/scripts/cards/SWSH7/Swablu_132.py | 5 +- .../scripts/cards/SWSH7/SwitchingCups_162.py | 13 +- spirit/game/scripts/cards/SWSH7/Swoobat_69.py | 5 +- .../scripts/cards/SWSH7/SylveonVMAX_211.py | 34 +- .../scripts/cards/SWSH7/SylveonVMAX_212.py | 34 +- .../scripts/cards/SWSH7/SylveonVMAX_75.py | 34 +- .../game/scripts/cards/SWSH7/SylveonV_183.py | 8 +- .../game/scripts/cards/SWSH7/SylveonV_184.py | 8 +- .../game/scripts/cards/SWSH7/SylveonV_74.py | 8 +- .../scripts/cards/SWSH7/Talonflame_140.py | 17 +- .../game/scripts/cards/SWSH7/Tentacruel_27.py | 7 +- .../game/scripts/cards/SWSH7/Thievul_105.py | 19 +- .../scripts/cards/SWSH7/ToyCatcher_163.py | 38 +- .../scripts/cards/SWSH7/ToyCatcher_233.py | 38 +- .../scripts/cards/SWSH7/TrevenantVMAX_14.py | 8 +- .../scripts/cards/SWSH7/TrevenantVMAX_206.py | 8 +- .../game/scripts/cards/SWSH7/TrevenantV_13.py | 14 +- .../scripts/cards/SWSH7/TrevenantV_168.py | 14 +- spirit/game/scripts/cards/SWSH7/Tropius_6.py | 5 +- .../cards/SWSH7/TurffieldStadium_234.py | 31 +- .../scripts/cards/SWSH7/UmbreonVMAX_214.py | 23 +- .../scripts/cards/SWSH7/UmbreonVMAX_215.py | 23 +- .../scripts/cards/SWSH7/UmbreonVMAX_95.py | 23 +- .../game/scripts/cards/SWSH7/UmbreonV_188.py | 15 +- .../game/scripts/cards/SWSH7/UmbreonV_189.py | 15 +- .../game/scripts/cards/SWSH7/UmbreonV_94.py | 15 +- .../scripts/cards/SWSH7/VaporeonVMAX_30.py | 38 +- .../game/scripts/cards/SWSH7/VaporeonV_172.py | 7 +- spirit/game/scripts/cards/SWSH7/Victini_20.py | 5 +- .../game/scripts/cards/SWSH7/Vigoroth_130.py | 10 +- .../scripts/cards/SWSH7/VolcaronaV_170.py | 19 +- .../game/scripts/cards/SWSH7/VolcaronaV_21.py | 19 +- .../game/scripts/cards/SWSH7/Wishiwashi_46.py | 25 +- .../game/scripts/cards/SWSH7/Wobbuffet_66.py | 18 +- spirit/game/scripts/cards/SWSH7/Woobat_68.py | 5 +- .../scripts/cards/SWSH7/ZinniasResolve_164.py | 14 +- .../scripts/cards/SWSH7/ZinniasResolve_203.py | 14 +- .../scripts/cards/SWSH7/ZinniasResolve_225.py | 14 +- .../game/scripts/cards/SWSH7/Zoroark_103.py | 9 +- .../game/scripts/cards/SWSH7/Zygarde_118.py | 20 +- spirit/game/scripts/cards/SWSH8/Absol_164.py | 5 +- .../game/scripts/cards/SWSH8/Accelgor_14.py | 19 +- .../cards/SWSH8/AdventurersDiscovery_224.py | 8 +- .../game/scripts/cards/SWSH8/AppletunV_26.py | 5 +- .../game/scripts/cards/SWSH8/Araquanid_20.py | 23 +- .../game/scripts/cards/SWSH8/Arcanine_33.py | 5 +- .../game/scripts/cards/SWSH8/Azumarill_59.py | 12 +- .../game/scripts/cards/SWSH8/Basculin_70.py | 5 +- .../game/scripts/cards/SWSH8/Blissey_203.py | 26 +- .../scripts/cards/SWSH8/BoltundVMAX_104.py | 7 +- .../scripts/cards/SWSH8/BoltundVMAX_267.py | 7 +- spirit/game/scripts/cards/SWSH8/BreloomV_6.py | 15 +- spirit/game/scripts/cards/SWSH8/Breloom_5.py | 8 +- .../scripts/cards/SWSH8/BugCatcher_226.py | 13 +- .../game/scripts/cards/SWSH8/Buneary_212.py | 7 +- .../game/scripts/cards/SWSH8/Bunnelby_214.py | 12 +- .../game/scripts/cards/SWSH8/Butterfree_3.py | 24 +- spirit/game/scripts/cards/SWSH8/Caterpie_1.py | 11 +- .../game/scripts/cards/SWSH8/CelebiV_245.py | 21 +- .../scripts/cards/SWSH8/Centiskorch_48.py | 5 +- .../scripts/cards/SWSH8/ChandelureVMAX_265.py | 30 +- .../scripts/cards/SWSH8/ChandelureVMAX_40.py | 30 +- .../scripts/cards/SWSH8/ChandelureV_247.py | 18 +- .../scripts/cards/SWSH8/ChandelureV_39.py | 18 +- .../game/scripts/cards/SWSH8/Chansey_202.py | 5 +- .../cards/SWSH8/ChiliCilanCress_227.py | 14 +- .../cards/SWSH8/ChiliCilanCress_258.py | 14 +- .../cards/SWSH8/ChiliCilanCress_273.py | 14 +- .../scripts/cards/SWSH8/CinderaceVMAX_45.py | 8 +- .../game/scripts/cards/SWSH8/CinderaceV_43.py | 5 +- .../game/scripts/cards/SWSH8/CinderaceV_44.py | 4 +- .../game/scripts/cards/SWSH8/Clawitzer_75.py | 5 +- .../game/scripts/cards/SWSH8/Claydol_145.py | 38 +- .../game/scripts/cards/SWSH8/Cloyster_51.py | 8 +- spirit/game/scripts/cards/SWSH8/Cook_228.py | 5 +- .../scripts/cards/SWSH8/Copperajah_192.py | 5 +- .../scripts/cards/SWSH8/Corviknight_190.py | 26 +- .../scripts/cards/SWSH8/Corvisquire_220.py | 5 +- .../scripts/cards/SWSH8/CrabominableV_248.py | 7 +- .../scripts/cards/SWSH8/CrabominableV_76.py | 7 +- .../scripts/cards/SWSH8/CrossSwitcher_230.py | 48 +- .../scripts/cards/SWSH8/Crossceiver_231.py | 44 +- spirit/game/scripts/cards/SWSH8/Cufant_191.py | 5 +- spirit/game/scripts/cards/SWSH8/Dancer_232.py | 12 +- spirit/game/scripts/cards/SWSH8/Dancer_259.py | 12 +- spirit/game/scripts/cards/SWSH8/Dancer_274.py | 12 +- .../game/scripts/cards/SWSH8/Delcatty_211.py | 21 +- spirit/game/scripts/cards/SWSH8/Deoxys_120.py | 13 +- .../game/scripts/cards/SWSH8/Diggersby_215.py | 5 +- .../game/scripts/cards/SWSH8/DodrioV_201.py | 8 +- .../game/scripts/cards/SWSH8/Dragapult_130.py | 10 +- .../game/scripts/cards/SWSH8/Drakloak_129.py | 5 +- spirit/game/scripts/cards/SWSH8/Drednaw_81.py | 5 +- spirit/game/scripts/cards/SWSH8/Dreepy_128.py | 5 +- .../game/scripts/cards/SWSH8/Dubwool_223.py | 5 +- spirit/game/scripts/cards/SWSH8/Durant_184.py | 16 +- .../game/scripts/cards/SWSH8/Eelektrik_96.py | 5 +- .../game/scripts/cards/SWSH8/Eelektross_97.py | 21 +- spirit/game/scripts/cards/SWSH8/Eevee_205.py | 5 +- .../game/scripts/cards/SWSH8/Eldegoss_25.py | 5 +- .../game/scripts/cards/SWSH8/Electrode_88.py | 13 +- .../scripts/cards/SWSH8/ElesasSparkle_233.py | 33 +- .../scripts/cards/SWSH8/ElesasSparkle_260.py | 33 +- .../scripts/cards/SWSH8/ElesasSparkle_275.py | 33 +- .../scripts/cards/SWSH8/EspeonVMAX_270.py | 17 +- .../game/scripts/cards/SWSH8/Excadrill_183.py | 5 +- .../game/scripts/cards/SWSH8/Falinks_154.py | 9 +- .../scripts/cards/SWSH8/FarewellBell_234.py | 23 +- .../game/scripts/cards/SWSH8/Feraligatr_57.py | 14 +- .../game/scripts/cards/SWSH8/Flaaffy_280.py | 23 +- .../game/scripts/cards/SWSH8/Frosmoth_85.py | 25 +- .../cards/SWSH8/GalarianCursola_118.py | 22 +- .../cards/SWSH8/GalarianDarmanitan_72.py | 31 +- .../cards/SWSH8/GalarianDarumaka_71.py | 5 +- .../scripts/cards/SWSH8/GalarianMeowth_180.py | 5 +- .../cards/SWSH8/GalarianObstagoon_161.py | 29 +- .../cards/SWSH8/GalarianPerrserker_181.py | 6 +- .../cards/SWSH8/GalarianZigzagoon_159.py | 7 +- .../game/scripts/cards/SWSH8/Garbodor_169.py | 7 +- .../scripts/cards/SWSH8/GengarVMAX_157.py | 21 +- .../scripts/cards/SWSH8/GengarVMAX_271.py | 21 +- .../game/scripts/cards/SWSH8/GengarV_156.py | 16 +- spirit/game/scripts/cards/SWSH8/Gligar_140.py | 7 +- .../game/scripts/cards/SWSH8/Gliscor_141.py | 9 +- spirit/game/scripts/cards/SWSH8/Golem_137.py | 21 +- spirit/game/scripts/cards/SWSH8/Goodra_197.py | 5 +- .../game/scripts/cards/SWSH8/Gorebyss_67.py | 12 +- .../game/scripts/cards/SWSH8/Granbull_116.py | 26 +- .../scripts/cards/SWSH8/GreedentVMAX_218.py | 22 +- .../scripts/cards/SWSH8/GreedentVMAX_272.py | 22 +- .../game/scripts/cards/SWSH8/GreedentV_217.py | 10 +- .../game/scripts/cards/SWSH8/GreedentV_256.py | 10 +- .../game/scripts/cards/SWSH8/GreedentV_257.py | 10 +- .../game/scripts/cards/SWSH8/GreninjaV_73.py | 5 +- .../scripts/cards/SWSH8/Grimmsnarl_178.py | 10 +- .../game/scripts/cards/SWSH8/Growlithe_32.py | 11 +- spirit/game/scripts/cards/SWSH8/Grubbin_18.py | 12 +- .../game/scripts/cards/SWSH8/Hariyama_143.py | 5 +- .../game/scripts/cards/SWSH8/Hawlucha_216.py | 5 +- spirit/game/scripts/cards/SWSH8/Heatmor_41.py | 31 +- .../game/scripts/cards/SWSH8/Heliolisk_99.py | 5 +- spirit/game/scripts/cards/SWSH8/HoopaV_253.py | 28 +- spirit/game/scripts/cards/SWSH8/Huntail_66.py | 8 +- .../game/scripts/cards/SWSH8/Impidimp_176.py | 5 +- .../game/scripts/cards/SWSH8/Indeedee_127.py | 5 +- .../scripts/cards/SWSH8/InteleonVMAX_266.py | 61 +- .../scripts/cards/SWSH8/InteleonVMAX_79.py | 67 +- .../game/scripts/cards/SWSH8/InteleonV_78.py | 5 +- .../scripts/cards/SWSH8/Jigglypuff_110.py | 5 +- spirit/game/scripts/cards/SWSH8/Jynx_112.py | 14 +- .../scripts/cards/SWSH8/Kangaskhan_204.py | 5 +- spirit/game/scripts/cards/SWSH8/Klefki_186.py | 5 +- .../game/scripts/cards/SWSH8/Landorus_148.py | 12 +- spirit/game/scripts/cards/SWSH8/Lapras_54.py | 25 +- spirit/game/scripts/cards/SWSH8/Latias_193.py | 73 +- spirit/game/scripts/cards/SWSH8/Latios_194.py | 58 +- .../game/scripts/cards/SWSH8/Leavanny_11.py | 28 +- .../game/scripts/cards/SWSH8/Magcargo_35.py | 13 +- .../game/scripts/cards/SWSH8/Mandibuzz_173.py | 19 +- spirit/game/scripts/cards/SWSH8/Mantine_61.py | 5 +- .../game/scripts/cards/SWSH8/Maractus_12.py | 21 +- spirit/game/scripts/cards/SWSH8/Marill_58.py | 11 +- .../game/scripts/cards/SWSH8/Marshtomp_63.py | 19 +- spirit/game/scripts/cards/SWSH8/Mawile_119.py | 16 +- .../game/scripts/cards/SWSH8/Melmetal_189.py | 31 +- .../game/scripts/cards/SWSH8/Meloetta_124.py | 15 +- spirit/game/scripts/cards/SWSH8/Meltan_188.py | 5 +- spirit/game/scripts/cards/SWSH8/Meowth_199.py | 5 +- spirit/game/scripts/cards/SWSH8/Metapod_2.py | 5 +- spirit/game/scripts/cards/SWSH8/Minun_90.py | 5 +- .../game/scripts/cards/SWSH8/Morgrem_177.py | 14 +- .../game/scripts/cards/SWSH8/Morpeko_109.py | 9 +- .../game/scripts/cards/SWSH8/Morpeko_179.py | 5 +- .../game/scripts/cards/SWSH8/Musharna_122.py | 15 +- .../game/scripts/cards/SWSH8/Ninetales_31.py | 16 +- spirit/game/scripts/cards/SWSH8/Onix_138.py | 5 +- .../game/scripts/cards/SWSH8/Palossand_126.py | 13 +- .../game/scripts/cards/SWSH8/Pancham_149.py | 15 +- .../game/scripts/cards/SWSH8/Pangoro_174.py | 13 +- spirit/game/scripts/cards/SWSH8/Panpour_68.py | 11 +- spirit/game/scripts/cards/SWSH8/Pansear_37.py | 5 +- .../game/scripts/cards/SWSH8/Persian_200.py | 7 +- spirit/game/scripts/cards/SWSH8/Plusle_89.py | 15 +- .../game/scripts/cards/SWSH8/Primeape_134.py | 5 +- .../game/scripts/cards/SWSH8/Pyukumuku_77.py | 18 +- .../game/scripts/cards/SWSH8/Qwilfish_60.py | 5 +- .../scripts/cards/SWSH8/RillaboomVMAX_23.py | 5 +- .../game/scripts/cards/SWSH8/RillaboomV_22.py | 5 +- .../game/scripts/cards/SWSH8/Rookidee_219.py | 5 +- spirit/game/scripts/cards/SWSH8/Rotom_94.py | 13 +- .../scripts/cards/SWSH8/SandacondaV_252.py | 5 +- .../game/scripts/cards/SWSH8/Sandshrew_131.py | 20 +- .../game/scripts/cards/SWSH8/Sandslash_132.py | 5 +- .../game/scripts/cards/SWSH8/Schoolboy_238.py | 14 +- .../game/scripts/cards/SWSH8/Schoolboy_261.py | 14 +- .../game/scripts/cards/SWSH8/Schoolboy_276.py | 14 +- .../scripts/cards/SWSH8/Schoolgirl_239.py | 14 +- .../scripts/cards/SWSH8/Schoolgirl_262.py | 14 +- .../scripts/cards/SWSH8/Schoolgirl_277.py | 14 +- spirit/game/scripts/cards/SWSH8/Sewaddle_9.py | 17 +- spirit/game/scripts/cards/SWSH8/Shauna_240.py | 5 +- spirit/game/scripts/cards/SWSH8/Shauna_263.py | 5 +- spirit/game/scripts/cards/SWSH8/Shauna_278.py | 5 +- spirit/game/scripts/cards/SWSH8/Sidney_241.py | 26 +- spirit/game/scripts/cards/SWSH8/Sidney_264.py | 26 +- spirit/game/scripts/cards/SWSH8/Sidney_279.py | 26 +- .../game/scripts/cards/SWSH8/Sigilyph_123.py | 30 +- .../game/scripts/cards/SWSH8/Simipour_69.py | 18 +- spirit/game/scripts/cards/SWSH8/Simisage_8.py | 12 +- .../game/scripts/cards/SWSH8/Simisear_38.py | 14 +- .../game/scripts/cards/SWSH8/Sizzlipede_47.py | 7 +- .../game/scripts/cards/SWSH8/Skarmory_182.py | 5 +- .../scripts/cards/SWSH8/SkatersPark_242.py | 5 +- spirit/game/scripts/cards/SWSH8/Skitty_210.py | 5 +- .../game/scripts/cards/SWSH8/Sliggoo_196.py | 7 +- .../game/scripts/cards/SWSH8/Smeargle_209.py | 17 +- spirit/game/scripts/cards/SWSH8/Snom_84.py | 16 +- .../scripts/cards/SWSH8/SpongyGloves_243.py | 7 +- .../game/scripts/cards/SWSH8/Stantler_208.py | 5 +- spirit/game/scripts/cards/SWSH8/Starmie_53.py | 34 +- spirit/game/scripts/cards/SWSH8/Staryu_52.py | 16 +- .../game/scripts/cards/SWSH8/Steelix_139.py | 7 +- .../game/scripts/cards/SWSH8/Swadloon_10.py | 5 +- .../game/scripts/cards/SWSH8/Swampert_64.py | 38 +- .../scripts/cards/SWSH8/Togedemaru_187.py | 5 +- spirit/game/scripts/cards/SWSH8/Toxel_106.py | 5 +- .../game/scripts/cards/SWSH8/Toxicroak_166.py | 7 +- .../scripts/cards/SWSH8/Toxtricity_107.py | 18 +- .../scripts/cards/SWSH8/Toxtricity_108.py | 41 +- .../scripts/cards/SWSH8/TrainingCourt_282.py | 5 +- .../game/scripts/cards/SWSH8/Trubbish_168.py | 10 +- .../game/scripts/cards/SWSH8/TsareenaV_21.py | 21 +- .../game/scripts/cards/SWSH8/TsareenaV_246.py | 21 +- .../scripts/cards/SWSH8/Turtonator_198.py | 55 +- spirit/game/scripts/cards/SWSH8/Tynamo_95.py | 7 +- spirit/game/scripts/cards/SWSH8/Victini_36.py | 15 +- .../game/scripts/cards/SWSH8/Vikavolt_101.py | 22 +- .../game/scripts/cards/SWSH8/Virizion_15.py | 6 +- spirit/game/scripts/cards/SWSH8/Voltorb_87.py | 5 +- .../game/scripts/cards/SWSH8/Vullaby_172.py | 5 +- .../scripts/cards/SWSH8/Wigglytuff_111.py | 6 +- spirit/game/scripts/cards/SWSH8/Wooloo_221.py | 15 +- spirit/game/scripts/cards/SWSH8/Wooloo_222.py | 5 +- spirit/game/scripts/cards/SWSH8/Zarude_27.py | 5 +- .../game/scripts/cards/SWSH8/Zeraora_102.py | 5 +- .../game/scripts/cards/SWSH8/Zoroark_171.py | 5 +- .../cards/SWSH9/AcerolasPremonition_129.py | 14 +- .../game/scripts/cards/SWSH9/Alcremie_71.py | 19 +- .../scripts/cards/SWSH9/ArceusVSTAR_123.py | 23 +- .../scripts/cards/SWSH9/ArceusVSTAR_176.py | 23 +- .../scripts/cards/SWSH9/ArceusVSTAR_184.py | 23 +- spirit/game/scripts/cards/SWSH9/Axew_110.py | 24 +- spirit/game/scripts/cards/SWSH9/Baltoy_58.py | 5 +- spirit/game/scripts/cards/SWSH9/Barry_130.py | 5 +- spirit/game/scripts/cards/SWSH9/Barry_167.py | 5 +- spirit/game/scripts/cards/SWSH9/Beartic_43.py | 11 +- .../game/scripts/cards/SWSH9/Bibarel_121.py | 22 +- .../scripts/cards/SWSH9/BlunderPolicy_131.py | 22 +- spirit/game/scripts/cards/SWSH9/Breloom_4.py | 17 +- spirit/game/scripts/cards/SWSH9/Buizel_38.py | 9 +- .../game/scripts/cards/SWSH9/CafMaster_133.py | 57 +- .../game/scripts/cards/SWSH9/Castform_116.py | 19 +- .../scripts/cards/SWSH9/CharizardVSTAR_174.py | 8 +- .../scripts/cards/SWSH9/CharizardVSTAR_18.py | 8 +- .../scripts/cards/SWSH9/CharizardV_153.py | 15 +- .../scripts/cards/SWSH9/CharizardV_154.py | 15 +- .../game/scripts/cards/SWSH9/CharizardV_17.py | 15 +- .../scripts/cards/SWSH9/CherensCare_134.py | 44 +- .../scripts/cards/SWSH9/CherensCare_168.py | 44 +- .../scripts/cards/SWSH9/CherensCare_177.py | 44 +- spirit/game/scripts/cards/SWSH9/Cherubi_12.py | 5 +- .../game/scripts/cards/SWSH9/Chimchar_24.py | 5 +- .../game/scripts/cards/SWSH9/Chimecho_63.py | 9 +- .../game/scripts/cards/SWSH9/Cinccino_125.py | 7 +- spirit/game/scripts/cards/SWSH9/Claydol_59.py | 10 +- .../cards/SWSH9/CleansingGloves_136.py | 7 +- .../game/scripts/cards/SWSH9/Clefable_54.py | 39 +- .../cards/SWSH9/CollapsedStadium_137.py | 7 +- .../game/scripts/cards/SWSH9/Crawdaunt_33.py | 9 +- .../cards/SWSH9/CynthiasAmbition_138.py | 11 +- .../cards/SWSH9/CynthiasAmbition_169.py | 11 +- .../cards/SWSH9/CynthiasAmbition_178.py | 11 +- spirit/game/scripts/cards/SWSH9/Dedenne_67.py | 15 +- .../scripts/cards/SWSH9/DracovishV_114.py | 25 +- .../game/scripts/cards/SWSH9/DrampaV_128.py | 8 +- .../game/scripts/cards/SWSH9/Druddigon_113.py | 10 +- .../game/scripts/cards/SWSH9/Dusclops_61.py | 7 +- .../game/scripts/cards/SWSH9/Dusknoir_62.py | 38 +- spirit/game/scripts/cards/SWSH9/Duskull_60.py | 7 +- spirit/game/scripts/cards/SWSH9/Eiscue_44.py | 26 +- .../game/scripts/cards/SWSH9/Electabuzz_46.py | 7 +- .../game/scripts/cards/SWSH9/Electivire_47.py | 17 +- .../game/scripts/cards/SWSH9/Empoleon_37.py | 30 +- spirit/game/scripts/cards/SWSH9/EnteiV_22.py | 16 +- .../scripts/cards/SWSH9/Escavalier_101.py | 12 +- .../game/scripts/cards/SWSH9/Exeggutor_2.py | 5 +- .../game/scripts/cards/SWSH9/Farfetchd_115.py | 5 +- .../game/scripts/cards/SWSH9/Floatzel_39.py | 6 +- .../game/scripts/cards/SWSH9/FlygonV_106.py | 14 +- .../game/scripts/cards/SWSH9/FlygonV_164.py | 14 +- spirit/game/scripts/cards/SWSH9/Flygon_76.py | 13 +- .../scripts/cards/SWSH9/FreshWaterSet_139.py | 6 +- .../scripts/cards/SWSH9/FriendsinGalar_140.py | 5 +- .../cards/SWSH9/GalarianArticunoV_181.py | 21 +- .../cards/SWSH9/GalarianMoltresV_183.py | 19 +- .../cards/SWSH9/GalarianZapdosV_182.py | 30 +- .../game/scripts/cards/SWSH9/Garchomp_109.py | 18 +- spirit/game/scripts/cards/SWSH9/Gloria_141.py | 14 +- spirit/game/scripts/cards/SWSH9/Golurk_83.py | 5 +- .../game/scripts/cards/SWSH9/GranbullV_159.py | 9 +- .../game/scripts/cards/SWSH9/GranbullV_57.py | 9 +- spirit/game/scripts/cards/SWSH9/Grimer_84.py | 7 +- .../game/scripts/cards/SWSH9/Grimmsnarl_94.py | 19 +- spirit/game/scripts/cards/SWSH9/Grotle_7.py | 18 +- .../game/scripts/cards/SWSH9/Hawlucha_127.py | 21 +- .../game/scripts/cards/SWSH9/Haxorus_112.py | 5 +- .../game/scripts/cards/SWSH9/Heatran_100.py | 15 +- .../game/scripts/cards/SWSH9/Hitmontop_72.py | 5 +- .../scripts/cards/SWSH9/HonchkrowV_161.py | 19 +- .../scripts/cards/SWSH9/HonchkrowV_162.py | 19 +- .../game/scripts/cards/SWSH9/HonchkrowV_88.py | 19 +- .../scripts/cards/SWSH9/HuntingGloves_142.py | 7 +- .../game/scripts/cards/SWSH9/Impidimp_92.py | 5 +- .../game/scripts/cards/SWSH9/Infernape_26.py | 23 +- .../game/scripts/cards/SWSH9/Kindler_143.py | 30 +- .../game/scripts/cards/SWSH9/Kindler_170.py | 30 +- .../game/scripts/cards/SWSH9/Kindler_179.py | 30 +- .../scripts/cards/SWSH9/KinglerVMAX_29.py | 16 +- .../game/scripts/cards/SWSH9/KinglerV_28.py | 13 +- spirit/game/scripts/cards/SWSH9/Klang_103.py | 5 +- .../game/scripts/cards/SWSH9/Klinklang_104.py | 13 +- spirit/game/scripts/cards/SWSH9/Lapras_31.py | 15 +- spirit/game/scripts/cards/SWSH9/Liepard_91.py | 7 +- spirit/game/scripts/cards/SWSH9/Lucario_79.py | 37 +- .../game/scripts/cards/SWSH9/LumineonV_155.py | 8 +- .../game/scripts/cards/SWSH9/LumineonV_156.py | 8 +- spirit/game/scripts/cards/SWSH9/Luxio_50.py | 7 +- spirit/game/scripts/cards/SWSH9/Luxray_51.py | 7 +- .../scripts/cards/SWSH9/MagmaBasin_144.py | 54 +- .../scripts/cards/SWSH9/MagmaBasin_185.py | 54 +- .../game/scripts/cards/SWSH9/Magmortar_20.py | 13 +- .../scripts/cards/SWSH9/MarniesPride_145.py | 34 +- .../scripts/cards/SWSH9/MarniesPride_171.py | 34 +- spirit/game/scripts/cards/SWSH9/Mewtwo_56.py | 5 +- spirit/game/scripts/cards/SWSH9/Milcery_70.py | 9 +- .../scripts/cards/SWSH9/MimikyuVMAX_69.py | 16 +- .../game/scripts/cards/SWSH9/MimikyuV_68.py | 19 +- .../game/scripts/cards/SWSH9/Minccino_124.py | 5 +- spirit/game/scripts/cards/SWSH9/Moltres_21.py | 14 +- .../game/scripts/cards/SWSH9/Monferno_25.py | 5 +- .../game/scripts/cards/SWSH9/MorpekoV_95.py | 8 +- spirit/game/scripts/cards/SWSH9/Mothim_11.py | 11 +- spirit/game/scripts/cards/SWSH9/Muk_85.py | 26 +- .../game/scripts/cards/SWSH9/Pachirisu_52.py | 12 +- .../game/scripts/cards/SWSH9/PikachuV_157.py | 24 +- spirit/game/scripts/cards/SWSH9/Piplup_35.py | 7 +- .../game/scripts/cards/SWSH9/PotHelmet_146.py | 15 +- .../game/scripts/cards/SWSH9/Probopass_99.py | 8 +- .../game/scripts/cards/SWSH9/Purrloin_90.py | 5 +- .../game/scripts/cards/SWSH9/RaichuV_158.py | 28 +- spirit/game/scripts/cards/SWSH9/RaichuV_45.py | 28 +- spirit/game/scripts/cards/SWSH9/RaikouV_48.py | 17 +- .../cards/SWSH9/RoseannesBackup_148.py | 52 +- .../cards/SWSH9/RoseannesBackup_172.py | 52 +- .../cards/SWSH9/RoseannesBackup_180.py | 52 +- spirit/game/scripts/cards/SWSH9/Sawk_81.py | 16 +- .../scripts/cards/SWSH9/ShayminVSTAR_14.py | 32 +- .../scripts/cards/SWSH9/ShayminVSTAR_173.py | 32 +- .../game/scripts/cards/SWSH9/ShayminV_13.py | 5 +- .../game/scripts/cards/SWSH9/ShayminV_152.py | 5 +- .../game/scripts/cards/SWSH9/Sigilyph_66.py | 24 +- .../game/scripts/cards/SWSH9/SimisearV_27.py | 23 +- .../game/scripts/cards/SWSH9/Spiritomb_89.py | 22 +- .../game/scripts/cards/SWSH9/Staraptor_119.py | 26 +- spirit/game/scripts/cards/SWSH9/Starly_117.py | 5 +- spirit/game/scripts/cards/SWSH9/Starmie_55.py | 5 +- spirit/game/scripts/cards/SWSH9/Staryu_30.py | 5 +- .../scripts/cards/SWSH9/TeamYellsCheer_149.py | 18 +- spirit/game/scripts/cards/SWSH9/Torkoal_23.py | 8 +- .../game/scripts/cards/SWSH9/Tornadus_126.py | 17 +- spirit/game/scripts/cards/SWSH9/Torterra_8.py | 6 +- .../game/scripts/cards/SWSH9/Trapinch_74.py | 5 +- spirit/game/scripts/cards/SWSH9/Tropius_5.py | 35 +- spirit/game/scripts/cards/SWSH9/Weavile_87.py | 23 +- .../cards/SWSH9/WhimsicottVSTAR_175.py | 39 +- .../scripts/cards/SWSH9/WhimsicottVSTAR_65.py | 39 +- .../scripts/cards/SWSH9/WhimsicottV_160.py | 17 +- .../scripts/cards/SWSH9/WhimsicottV_64.py | 17 +- .../game/scripts/cards/SWSH9/Wormadam_10.py | 9 +- .../game/scripts/cards/SWSH9/Wormadam_77.py | 16 +- .../game/scripts/cards/SWSH9/Wormadam_98.py | 6 +- .../scripts/cards/SWSH9/ZamazentaV_105.py | 16 +- .../scripts/cards/SWSH9/ZamazentaV_163.py | 16 +- spirit/game/scripts/cards/SWSH9/ZarudeV_16.py | 7 +- spirit/game/session/constants.py | 13 + spirit/game/session/effects.py | 411 ++++++++- spirit/game/session/game_session.py | 861 ++++++++++++++++-- spirit/game/session/legal_actions.py | 79 +- spirit/game/session/passives.py | 242 ++++- spirit/tools/effect_coverage.py | 1 + 2327 files changed, 33324 insertions(+), 6696 deletions(-) diff --git a/spirit/game/card_effects/attacks_common.py b/spirit/game/card_effects/attacks_common.py index dfd7f18..8dccc8a 100644 --- a/spirit/game/card_effects/attacks_common.py +++ b/spirit/game/card_effects/attacks_common.py @@ -676,6 +676,21 @@ def discard_for_bonus(source: str = "hand", predicate=None, max_count: int = 1, return effect +def smokescreen_attack(damage: Optional[int] = None, also=None): + """Printed (or damage) damage; "during your opponent's next turn, if the + Defending Pokemon tries to attack, your opponent flips a coin. If tails, + that attack doesn't happen" (Smokescreen / Sand Attack / Blinding Beam). + The rider no-ops versus an attack-effect-shielded target.""" + async def effect(ctx): + await ctx.deal_damage(damage) + defender = ctx.defender + if defender is not None and not ctx.effects_blocked(defender): + ctx.require_attack_flip(defender) + if also is not None: + await also(ctx) + return effect + + async def discard_random_from_hand(ctx, player_id: Optional[str] = None, count: int = 1) -> list: """Discards `count` random cards from a player's hand (reveals via the diff --git a/spirit/game/card_effects/passives_common.py b/spirit/game/card_effects/passives_common.py index 51a1678..b061a9f 100644 --- a/spirit/game/card_effects/passives_common.py +++ b/spirit/game/card_effects/passives_common.py @@ -14,11 +14,12 @@ preds (no_retreat / ability_lock / healing_block / retreat_free) take from typing import Any, Callable, Optional -from spirit.game.attributes import AttrID +from spirit.game.attributes import AttrID, TrainerType from spirit.game.session.passives import ( Passive, TurnDamageModifier, carrier_pokemon, + effective_max_hp, ) @@ -207,7 +208,7 @@ class RetreatDiscountPassive(Passive): self.amount = amount self.protects = _protects_pred(target_pred) - def modify_retreat_cost(self, cost, pokemon, carrier): + def modify_retreat_cost(self, cost, pokemon, carrier, board): if not self.protects(pokemon, carrier): return cost return max(0, cost - self.amount) @@ -225,7 +226,7 @@ class RetreatFreeWhenPassive(Passive): def __init__(self, pred): self.pred = pred - def modify_retreat_cost(self, cost, pokemon, carrier): + def modify_retreat_cost(self, cost, pokemon, carrier, board): return 0 if self.pred(pokemon, carrier) else cost @@ -435,6 +436,158 @@ def attack_effect_shield_passive(protects="carrier") -> Passive: return AttackEffectShieldPassive(protects) +class FlipPreventDamagePassive(Passive): + """"If any damage is done to this Pokemon by attacks, flip a coin. If + heads, prevent that damage" (Infiltrator / Primate Dexterity).""" + + def __init__(self, title="", protects="carrier"): + self.title = title + self.protects = _protects_pred(protects) + + async def damage_interceptor(self, ctx, calc, target, carrier): + if not (calc.is_attack and calc.is_opposing and calc.amount > 0): + return None + if not self.protects(target, carrier): + return None + heads = await ctx.flip_coins(1, self.title, + source=carrier_pokemon(carrier) or carrier) + return 0 if heads and heads[0] else None + + +def flip_prevent_damage_passive(title="") -> Passive: + """Flip-to-prevent shield: heads prevents the whole attack hit; the flip + choreographs inside the attack bracket (queued on the attack ctx).""" + return FlipPreventDamagePassive(title) + + +class GutsSurvivePassive(Passive): + """"If this Pokemon would be Knocked Out by damage from an attack, [flip a + coin -- if heads,] it is not Knocked Out and its remaining HP becomes + `hp_floor`" (Guts; Sturdy with flip=False + require_full_hp=True).""" + + def __init__(self, hp_floor=10, title="Guts", flip=True, + require_full_hp=False, protects="carrier"): + self.hp_floor = hp_floor + self.title = title + self.flip = flip + self.require_full_hp = require_full_hp + self.protects = _protects_pred(protects) + + async def damage_interceptor(self, ctx, calc, target, carrier): + if not (calc.is_attack and calc.is_opposing and calc.amount > 0): + return None + if not self.protects(target, carrier): + return None + current = target.get_attribute(AttrID.HP, 0) + if calc.amount < current: + return None # would not Knock Out + if self.require_full_hp and current < effective_max_hp(calc.board, target): + return None + if self.flip: + heads = await ctx.flip_coins(1, self.title, + source=carrier_pokemon(carrier) or carrier) + if not (heads and heads[0]): + return None + return max(0, current - self.hp_floor) + + +def guts_survive_passive(hp_floor=10, title="Guts", flip=True, + require_full_hp=False) -> Passive: + """KO-survive interceptor: only when the hit would KO; heads (or always + with flip=False) rewrites the dealt amount so remaining HP = hp_floor.""" + return GutsSurvivePassive(hp_floor, title, flip, require_full_hp) + + +class TrainerEffectShieldPassive(Passive): + """Shields the carrier's owner from opposing trainer-card effects + (Dew Guard: "prevent all effects of that card done to you or your hand").""" + + def __init__(self, supporters_only=True, protects=None, while_active=False, + condition=None): + self.supporters_only = supporters_only + # protects(affected_entity_or_None, carrier): scopes the shield to + # matching direct objects (None = the whole side, legacy behavior). + self.protects = protects + self.while_active = while_active + self.condition = condition + + def blocks_trainer_effects(self, affected_player_id, trainer_card, + trainer_type, carrier, affected_entity=None, + board=None): + if self.supporters_only and trainer_type != TrainerType.SUPPORTER.value: + return False + if affected_player_id != carrier.owning_player_id: + return False + if self.while_active \ + and not is_in_active_spot(carrier_pokemon(carrier) or carrier): + return False + if self.condition is not None and not self.condition(board, carrier): + return False + if self.protects is not None: + return bool(self.protects(affected_entity, carrier)) + return True + + +def trainer_effect_shield_passive(supporters_only=True, protects=None, + while_active=False, condition=None) -> Passive: + """Dew Guard shape; the engine already scopes it to effects a DIFFERENT + player's trainer card does to the shielded side. protects/while_active/ + condition(board, carrier) narrow the shield (Princess's Curtain, Baffling).""" + return TrainerEffectShieldPassive(supporters_only, protects, while_active, + condition) + + +class SupporterReplacementPassive(Passive): + """Opposing Supporter cards resolve `replacement` instead of their own + effect (Shifty Substitution: "each Supporter card in your opponent's hand + has the effect 'Draw 3 cards.'").""" + + def __init__(self, replacement, opponents_only=True, while_active=True): + self.replacement = replacement + self.opponents_only = opponents_only + self.while_active = while_active + + def replace_supporter_effect(self, card, player_id, carrier): + if self.opponents_only and player_id == carrier.owning_player_id: + return None + if self.while_active: + holder = carrier_pokemon(carrier) or carrier + if not is_in_active_spot(holder): + return None + return self.replacement + + +def replace_opponent_supporters(replacement, while_active=True) -> Passive: + """Shifty Substitution shape: `replacement` is an async def effect(ctx) + run as the opponent's Supporter effect (trainer ctx, shields apply).""" + return SupporterReplacementPassive(replacement, while_active=while_active) + + +class EnergyAttachTaxPassive(Passive): + """Opposing manual energy attaches flip a coin; tails discards the energy + instead of attaching (Slimy Room; per text the turn attachment is NOT + used up).""" + + def __init__(self, opponents_only=True, while_active=True): + self.opponents_only = opponents_only + self.while_active = while_active + + def taxes_energy_attach(self, attaching_player_id, energy, target, carrier): + if self.opponents_only \ + and attaching_player_id == carrier.owning_player_id: + return False + if self.while_active: + holder = carrier_pokemon(carrier) or carrier + if not is_in_active_spot(holder): + return False + return True + + +def energy_attach_tax_passive(opponents_only=True, while_active=True) -> Passive: + """Slimy Room shape: taxes the opponent's manual attach with a coin flip.""" + return EnergyAttachTaxPassive(opponents_only, while_active) + + # ====================================================================== # GROUP B -- temporary-shield effect factories (expiring temp passives) # ====================================================================== @@ -602,7 +755,7 @@ class SelfRetreatCostRaisePassive(Passive): def __init__(self, extra): self.extra = extra - def modify_retreat_cost(self, cost, pokemon, carrier): + def modify_retreat_cost(self, cost, pokemon, carrier, board): if carrier_pokemon(carrier) is not pokemon: return cost return cost + self.extra diff --git a/spirit/game/card_effects/pokemon.py b/spirit/game/card_effects/pokemon.py index 6b89125..2378b3f 100644 --- a/spirit/game/card_effects/pokemon.py +++ b/spirit/game/card_effects/pokemon.py @@ -9,19 +9,22 @@ from spirit.game.attributes import ( SpecialConditions, ) from spirit.game.data_utils import ( - ABILITIES_BY_ID, Ability, Attack, ability_id_for, def_for, is_pokemon_v, - subtypes_for, + ABILITIES_BY_ID, Ability, Attack, ability_id_for, def_for, has_rule_box, + is_pokemon_v, subtypes_for, ) from spirit.game.session.constants import BENCH_CAPACITY from spirit.game.session.effects import ( full_stack, is_basic_pokemon, is_colorless_no_rule_box, + is_pokemon_card, is_special_energy, is_supporter_card, is_trainer_card, ) -from spirit.game.session.passives import Passive, carrier_pokemon +from spirit.game.session.passives import ( + Passive, carrier_pokemon, effective_bench_capacity, +) from spirit.game.models.board import PokemonEntity @@ -40,7 +43,8 @@ class MysteriousNestPassive(Passive): async def read_the_wind(ctx): """Discard a card from your hand. If you do, draw 3 cards.""" - if await ctx.discard_from_hand(1, minimum=0, prompt="Discard a card from your hand"): + # No "you may": with a card in hand the discard is mandatory. + if await ctx.discard_from_hand(1, minimum=1, prompt="Discard a card from your hand"): await ctx.draw_cards(3) @@ -193,6 +197,26 @@ class ExcitedHeartPassive(Passive): return cost +# --- Ditto (PGO): Sudden Transformation ------------------------------------ + +class SuddenTransformationPassive(Passive): + """May use the attacks of Basic non-Rule-Box Pokemon in the owner's + discard pile (energy costs still apply).""" + + def granted_attacks(self, board, pokemon, carrier): + if carrier is not pokemon: + return [] + discard = board.find_player_area(pokemon.owning_player_id, "discard") + attacks = [] + for card in (discard.children if discard else []): + if not is_basic_pokemon(card) or has_rule_box(card.archetype_id): + continue + for ability in getattr(def_for(card.archetype_id), "abilities", None) or []: + if isinstance(ability, Attack): + attacks.append(ability) + return attacks + + # --- Raikou (VIV): Amazing Shot ------------------------------------------- async def amazing_shot(ctx): @@ -902,3 +926,239 @@ async def teraspark(ctx): ctx, ctx.opponent_bench(), 40, "Choose a Benched Pokémon to take 40 damage", ) + + +# --- Word of Ruin / Doom Curse (delayed Knock Out) ------------------------ + +async def delayed_knockout(ctx): + """At the end of your opponent's next turn, the Defending Pokemon will + be Knocked Out (dropped if it leaves the Active spot or evolves).""" + target = ctx.defender + if target is None or ctx.effects_blocked(target): + return + ctx.visual_targets = [target.entity_id] + owner_id = target.owning_player_id + target_id = target.entity_id + + def _guard(board): + active = board.active_pokemon(owner_id) if owner_id else None + return active is not None and active.entity_id == target_id + + async def _fire(session): + pokemon = session.board_state.get_entity(target_id) + if isinstance(pokemon, PokemonEntity): + await session._resolve_raw_knockout(pokemon) + + ctx.schedule_at_checkup(1, _fire, guard=_guard) + + +# --- Top Entry / Emergency Entry (bench straight from the turn draw) ------ + +def top_entry(draw_count=0): + """If drawn at the beginning of your turn and your Bench isn't full, you + may put this Pokemon onto your Bench (Emergency Entry also draws).""" + async def effect(ctx): + source = ctx.source + owner = source.owning_player_id or ctx.player_id + bench = ctx.board.find_player_area(owner, "bench") + if not bench \ + or len(bench.children) >= effective_bench_capacity(ctx.board, owner): + return + if not await ctx.ask_yes_no("Put this Pokémon onto your Bench?"): + return + if await ctx.bench_pokemon(source) and draw_count: + await ctx.draw_cards(draw_count) + return effect + + +# --- Origin Forme Dialga VSTAR (BRS): Star Chronos ------------------------ + +async def star_chronos(ctx): + """220. Take another turn after this one. (Skip Pokemon Checkup.)""" + await ctx.deal_damage() + ctx.take_extra_turn() + + +# --- Medicham V (EVS): Yoga Loop ------------------------------------------ + +def yoga_loop_condition(board, player_id, pokemon): + """Unusable if any of your Pokemon used Yoga Loop during your last turn.""" + prev = board.turn_state.attack_titles_prev_turn_by_player.get(player_id) or [] + return "Yoga Loop" not in prev + + +async def yoga_loop(ctx): + """2 damage counters on 1 opposing Pokemon; a KO grants an extra turn.""" + target = await ctx.choose_pokemon( + ctx.opponent_pokemon_in_play(), "Choose 1 of your opponent's Pokémon" + ) + if target is None: + return + await ctx.deal_damage(20, target=target, as_counters=True) + if target in ctx.knockouts: + ctx.take_extra_turn() + + +# --- Jumpluff (EVS): Fluffy Barrage --------------------------------------- + +class FluffyBarragePassive(Passive): + """May attack twice each turn (the printed KO sentence is timing reminder + text, not a condition): the first attack keeps the turn, and legal + actions collapse to attacks-only afterwards, so the player either attacks + again or clicks End Turn.""" + + def attack_keeps_turn(self, attacker, ability, ctx, carrier): + if attacker is not carrier: + return False + uses = [e for e in ctx.session.turn_state.attacks_used + if e[0] == carrier.entity_id] + return len(uses) == 1 + + +# --- Devolution (Rewind Beam / Downgrading Beam / Curse of Devolution) ---- + +def devolvable(pokemon) -> bool: + """An evolved in-play Pokemon with its previous stage tucked underneath.""" + evolves_from = pokemon.get_attribute(AttrID.EVOLUTION_LOGIC_FROM) + return bool(evolves_from) and any( + isinstance(c, PokemonEntity) + and c.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) == evolves_from + for c in pokemon.children + ) + + +def devolve_depth(pokemon) -> int: + """How many evolution cards can be peeled off the stack.""" + depth, current = 0, pokemon + while True: + evolves_from = current.get_attribute(AttrID.EVOLUTION_LOGIC_FROM) + nxt = next( + (c for c in current.children + if isinstance(c, PokemonEntity) + and c.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) == evolves_from), + None, + ) if evolves_from else None + if nxt is None: + return depth + depth, current = depth + 1, nxt + + +async def rewind_beam(ctx): + """180. Devolve the opposing evolved Active: highest Stage card to hand.""" + await ctx.deal_damage() + defender = ctx.defender + if defender is not None and devolvable(defender) and not ctx.effects_blocked(defender): + await ctx.devolve_pokemon(defender, steps=1, destination="hand") + + +async def downgrading_beam(ctx): + """Devolve 1 opposing evolved Pokemon, removing any number of Evolution + cards; the opponent shuffles them into their deck.""" + candidates = [p for p in ctx.opponent_pokemon_in_play() + if devolvable(p) and not ctx.effects_blocked(p)] + if not candidates: + return + target = await ctx.choose_pokemon( + candidates, "Choose 1 of your opponent's evolved Pokémon" + ) + if target is None: + return + owner_id = target.owning_player_id + depth = devolve_depth(target) + steps = 1 + if depth > 1: + steps = 1 + await ctx.choose( + "How many Evolution cards will you remove?", + [str(n + 1) for n in range(depth)], + ) + removed = await ctx.devolve_pokemon(target, steps=steps, destination="deck") + if removed: + await ctx.shuffle_deck(owner_id) + + +async def curse_of_devolution(ctx): + """On evolve: you may devolve 1 opposing Benched evolved Pokemon.""" + candidates = [p for p in ctx.opponent_bench() + if devolvable(p) and not ctx.effects_blocked(p)] + if not candidates: + return + if not await ctx.ask_yes_no("Devolve 1 of your opponent's Benched Pokémon?"): + return + target = await ctx.choose_pokemon( + candidates, "Choose 1 of your opponent's Benched evolved Pokémon" + ) + if target is not None: + await ctx.devolve_pokemon(target, steps=1, destination="hand") + + +# --- Identity swaps (Stance Change / V Transformation / Phantom Transformation) + + +def _same_name(entity, other) -> bool: + return entity.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) \ + == other.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) + + +def stance_change_condition(board, player_id, pokemon): + """A same-named card (an Aegislash) sits in the hand.""" + hand = board.find_player_area(player_id, "hand") + return bool(hand) and any(_same_name(c, pokemon) for c in hand.children) + + +async def stance_change(ctx): + """Switch this Pokemon with an Aegislash in your hand; everything remains.""" + candidates = [c for c in ctx.hand() if _same_name(c, ctx.source)] + picks = await ctx.choose_cards( + candidates, 1, minimum=1, + prompt="Choose an Aegislash to switch with this Pokémon", + ) + if picks: + await ctx.identity_swap(ctx.source, picks[0], destination="hand") + + +def _is_basic_pokemon_v(card) -> bool: + return is_basic_pokemon(card) and is_pokemon_v(card.archetype_id) + + +def v_transformation_condition(board, player_id, pokemon): + discard = board.find_player_area(player_id, "discard") + return bool(discard) and any(_is_basic_pokemon_v(c) for c in discard.children) + + +async def v_transformation(ctx): + """Switch this Pokemon with a Basic Pokemon V from the discard pile.""" + candidates = [c for c in ctx.discard_pile() if _is_basic_pokemon_v(c)] + picks = await ctx.choose_cards( + candidates, 1, minimum=1, + prompt="Choose a Basic Pokémon V to switch with this Pokémon", + ) + if picks: + await ctx.identity_swap(ctx.source, picks[0], destination="discard") + + +def _phantom_candidate(card, zoroark) -> bool: + return ( + is_pokemon_card(card) + and card.get_attribute(AttrID.STAGE) == PokemonStage.STAGE1.value + and not _same_name(card, zoroark) + ) + + +def phantom_transformation_condition(board, player_id, pokemon): + discard = board.find_player_area(player_id, "discard") + return bool(discard) and any( + _phantom_candidate(c, pokemon) for c in discard.children) + + +async def phantom_transformation(ctx): + """Discard this Pokemon and all attached cards; a Stage 1 (non-Zoroark) + from the discard pile takes its place as a fresh Pokemon.""" + candidates = [c for c in ctx.discard_pile() + if _phantom_candidate(c, ctx.source)] + picks = await ctx.choose_cards( + candidates, 1, minimum=1, + prompt="Choose a Stage 1 Pokémon to put in this Pokémon's place", + ) + if picks: + await ctx.identity_swap(ctx.source, picks[0], destination="discard", + transfer=False) diff --git a/spirit/game/card_effects/trainers.py b/spirit/game/card_effects/trainers.py index 1f0233d..02c9942 100644 --- a/spirit/game/card_effects/trainers.py +++ b/spirit/game/card_effects/trainers.py @@ -5,7 +5,8 @@ from spirit.game.attributes import ( ) from spirit.game.models.board import PokemonEntity from spirit.game.data_utils import ( - Ability, Activations, has_rule_box, is_pokemon_v, subtypes_for, + Ability, Activations, Attack, def_for, has_rule_box, is_pokemon_v, + subtypes_for, ) from spirit.game.session.constants import BENCH_CAPACITY from spirit.game.session.effects import ( @@ -22,7 +23,9 @@ from spirit.game.session.passives import ( Passive, TurnDamageModifier, carrier_pokemon, + effective_bench_capacity, effective_max_hp, + trainer_play_blocked, ) @@ -584,6 +587,39 @@ async def battle_vip_pass(ctx): await ctx.shuffle_deck() +def dream_ball_playable(board, player_id): + """Dream Ball never plays from hand: its window is the prize-take prompt.""" + return False + + +async def dream_ball(ctx): + """Search your deck for a Pokemon and put it onto your Bench.""" + picks = await ctx.search_deck( + is_pokemon_card, count=1, minimum=0, + prompt="Choose a Pokémon to put onto your Bench.", + ) + for card in picks: + await ctx.bench_pokemon(card) + await ctx.shuffle_deck() + + +async def dream_ball_prize_window(ctx): + """Taken as a face-down Prize: you may play it before it settles in hand.""" + ctx.suppress_announce = True + board, pid = ctx.board, ctx.player_id + bench = board.find_player_area(pid, "bench") + if not bench or len(bench.children) >= effective_bench_capacity(board, pid): + return + if not ctx.deck(pid): + return + if trainer_play_blocked(board, pid, ctx.source): + return + if not await ctx.ask_yes_no("Play Dream Ball? Search your deck for a " + "Pokémon and put it onto your Bench."): + return + await ctx.session._execute_play_trainer(pid, ctx.source) + + async def power_tablet(ctx): """This turn, your Fusion Strike Pokemon's attacks do 30 more damage to the opponent's Active Pokemon (before Weakness and Resistance).""" @@ -681,6 +717,30 @@ class LostCityPassive(Passive): return "lostZone" +class CollapsedStadiumPassive(Passive): + """Each player can't have more than 4 Benched Pokemon (excess is discarded + by enforce_bench_capacity when this comes into play).""" + + def bench_capacity(self, player_id, carrier): + return 4 + + +async def gapejaw_bog_watch(ctx): + """Gapejaw Bog: 2 damage counters on any Basic just benched from hand.""" + pokemon = ctx.benched_pokemon + if pokemon is None: + return + await ctx.deal_damage(20, target=pokemon, apply_modifiers=False, + as_counters=True) + + +class SkatersParkPassive(Passive): + """Basic Energy paid for either player's retreat goes to hand instead.""" + + def retreat_cost_destination(self, pokemon, energy, carrier): + return "hand" if is_basic_energy_card(energy) else None + + # ====================================================================== # Lost Zone Box + Regigigas decks # ====================================================================== @@ -897,17 +957,56 @@ async def hisuian_heavy_ball(ctx): await ctx.look_at_prizes_take_basic() +# --- Peonia (CRE, Supporter) ---------------------------------------------- + +def peonia_playable(board, player_id) -> bool: + prizes = board.find_player_area(player_id, "prizePile") + return bool(prizes and prizes.children) + + +async def peonia(ctx): + """Put up to 3 Prize cards into your hand. Then, for each Prize card put + into your hand this way, put a card from your hand face down as a Prize.""" + taken = await ctx.take_prizes(3, minimum=1, check_win=False) + if not taken: + return + picks = await ctx.choose_cards( + list(ctx.hand()), len(taken), minimum=len(taken), + prompt=f"Choose {len(taken)} card(s) to put face down as Prize cards", + ) + await ctx.put_in_prizes(picks) + + # --- Air Balloon (SSH, Pokemon Tool) ------------------------------------- class AirBalloonPassive(Passive): """The Retreat Cost of the holder is [C][C] less.""" - def modify_retreat_cost(self, cost, pokemon, carrier): + def modify_retreat_cost(self, cost, pokemon, carrier, board): if carrier_pokemon(carrier) is pokemon: return cost - 2 return cost +# --- Memory Capsule (SWSH4) ------------------------------------------------ + +class MemoryCapsulePassive(Passive): + """The holder can use any attack from its tucked previous Evolutions + (energy costs still apply).""" + + def granted_attacks(self, board, pokemon, carrier): + if carrier_pokemon(carrier) is not pokemon: + return [] + attacks = [] + for child in pokemon.children: + if not isinstance(child, PokemonEntity): + continue + for ability in getattr(def_for(child.archetype_id), "abilities", None) or []: + if isinstance(ability, Attack): + attacks.append(ability) + return attacks + + # --- Training Court (RCL, Stadium) --------------------------------------- def training_court_condition(board, player_id, stadium): @@ -927,6 +1026,44 @@ async def training_court(ctx): await ctx.put_in_hand(picks, reveal=False) +# --- Thorton (SWSH11) ------------------------------------------------------- + +def _basic_pokemon_in_play(board, player_id): + """In-play Basics; fossils count (Basic Pokemon in play, Trainer CARD_TYPE).""" + from spirit.game.attributes import AttrID, PokemonStage + from spirit.game.models.board import PokemonEntity + return [ + p for p in board.pokemon_in_play(player_id) + if isinstance(p, PokemonEntity) + and p.get_attribute(AttrID.STAGE) == PokemonStage.BASIC.value + ] + + +def thorton_condition(board, player_id): + if not any(is_basic_pokemon(c) for c in _discard(board, player_id)): + return False + return bool(_basic_pokemon_in_play(board, player_id)) + + +async def thorton(ctx): + """Choose a Basic Pokemon in your discard pile and switch it with 1 of + your Basic Pokemon in play; everything remains on the new Pokemon.""" + candidates = [c for c in ctx.discard_pile() if is_basic_pokemon(c)] + in_play = _basic_pokemon_in_play(ctx.board, ctx.player_id) + if not candidates or not in_play: + return + picks = await ctx.choose_cards( + candidates, 1, minimum=1, + prompt="Choose a Basic Pokémon from your discard pile", + ) + if not picks: + return + target = await ctx.choose_pokemon( + in_play, "Choose 1 of your Basic Pokémon in play to switch it with") + if target is not None: + await ctx.identity_swap(target, picks[0], destination="discard") + + TRAINING_COURT_ABILITY = Ability( title="Training Court", game_text="Once during each player's turn, that player may put a basic Energy card from their discard pile into their hand.", @@ -934,3 +1071,80 @@ TRAINING_COURT_ABILITY = Ability( effect=training_court, condition=training_court_condition, ) + + +# --- Fossils (Unidentified Fossil / Rare Fossil) --------------------------- + +class FossilBodyPassive(Passive): + """Fossil rules text: this card can't retreat; Rare Fossil additionally + can't be affected by Special Conditions.""" + + def __init__(self, blocks_conditions: bool = False): + self.condition_immune = blocks_conditions + + def blocks_retreat(self, pokemon, carrier): + return pokemon is carrier + + def blocks_special_conditions(self, target, condition, carrier): + return self.condition_immune and target is carrier + + +async def fossil_discard(ctx): + """Discard this fossil from play; attached cards go to the discard too.""" + fossil = ctx.source + was_active = fossil is ctx.my_active() + discard = ctx.board.find_player_area(ctx.player_id, "discard") + if discard is not None and discard.entity_id not in ctx.visual_targets: + ctx.visual_targets.append(discard.entity_id) + await ctx.discard_cards([c for c in full_stack(fossil) if c is not fossil]) + await ctx.discard_cards([fossil]) + if was_active: + async def _promote(): + if not await ctx.session._promote_new_active(ctx.player_id): + screen_name = ctx.session.players[ctx.player_id].screen_name + await ctx.session.end_game( + ctx.opponent_id, f"{screen_name} has no Pokémon left" + ) + ctx.deferred_actions.append(_promote) + + +def fossil_discard_ability() -> Ability: + """"At any time during your turn, you may discard this card from play." + Fresh instance per print: ability_id derives from the owning card GUID.""" + return Ability( + "Discard", + "At any time during your turn, you may discard this card from play.", + activation=Activations.ONCE_PER_TURN, + effect=fossil_discard, + ) + + +def is_rare_fossil(card) -> bool: + return getattr(def_for(card.archetype_id), "display_name", None) == "Rare Fossil" + + +def bench_has_room(board, player_id): + bench = board.find_player_area(player_id, "bench") + return bench is not None \ + and len(bench.children) < effective_bench_capacity(board, player_id) + + +def fossil_search(fossil_predicate, count: int = 2, + label: str = "Rare Fossil"): + """Search your deck for up to `count` matching fossil cards and put them + onto your Bench; then shuffle (Relicanth's Fossil Search, Cara Liss).""" + async def effect(ctx): + bench = ctx.board.find_player_area(ctx.player_id, "bench") + space = effective_bench_capacity(ctx.board, ctx.player_id) \ + - len(bench.children) if bench else 0 + if space <= 0: + return + picks = await ctx.search_deck( + fossil_predicate, count=min(count, space), minimum=0, + prompt=f"Choose up to {min(count, space)} {label} cards to put " + f"onto your Bench.", + ) + for card in picks: + await ctx.bench_pokemon(card) + await ctx.shuffle_deck() + return effect diff --git a/spirit/game/data_utils.py b/spirit/game/data_utils.py index 8274936..b413c36 100644 --- a/spirit/game/data_utils.py +++ b/spirit/game/data_utils.py @@ -123,6 +123,25 @@ class Triggers: # BEFORE the KO'd stack moves (energies still attached); ctx carries # ko_pokemon / ko_from_attack / ko_attacker. ON_ALLY_KNOCKED_OUT = "on_ally_knocked_out" + # Either player manually put a Basic from hand onto their Bench (Gapejaw + # Bog); ctx carries benching_player_id / benched_pokemon. + ON_POKEMON_BENCHED = "on_pokemon_benched" + # This card was drawn by the beginning-of-turn draw (Lombre "Top Entry"); + # fires after the card lands in hand (approximation of "before you put it + # into your hand"). + ON_TURN_DRAWN = "on_turn_drawn" + # This card was just taken as a face-down Prize into hand (Dream Ball). + ON_TAKEN_AS_PRIZE = "on_taken_as_prize" + # Another of the owner's Pokemon evolved via a hand-played evolution card + # (Eevee "Resonant Evolution"); ctx carries evolved_pokemon / evolved_from. + ON_ALLY_EVOLVED = "on_ally_evolved" + # Fires for each of the turn player's in-play Pokemon after their turn + # ends, before the checkup (Radiant Venusaur "Sunny Bloom"). + END_OF_TURN = "end_of_turn" + # This card was discarded from its owner's hand by an OPPOSING player's + # effect (Amoonguss "Surprise Spores"); fires via the acting ctx's + # deferred_actions, after that effect's choreography flushes. + ON_DISCARDED_FROM_HAND = "on_discarded_from_hand" class Activations: @@ -241,7 +260,8 @@ class Attack(Ability): vstar: bool = False, locks_next_turn: bool = False, condition: Optional[Callable] = None, - usable_first_turn: bool = False + usable_first_turn: bool = False, + usable_despite_conditions: bool = False ): super().__init__(title, game_text, ability_type, effect, vstar=vstar, condition=condition) @@ -252,6 +272,8 @@ class Attack(Ability): self.locks_next_turn = locks_next_turn # Exempt from the "going first can't attack on turn 1" rule (Indeedee). self.usable_first_turn = usable_first_turn + # Offered even while the user is Asleep/Paralyzed (Windup Arm-style). + self.usable_despite_conditions = usable_despite_conditions def to_dict(self) -> dict: d = super().to_dict() @@ -431,7 +453,8 @@ class TrainerCardDef(CardDefinition): the `unimplemented` marker, or None for cards with no effect scripted. `condition(board, player_id) -> bool` gates when the card may be played - at all (e.g. Ultra Ball needs 2 other cards in hand to discard). + at all (e.g. Ultra Ball needs 2 other cards in hand to discard); a + 3-arg `condition(board, player_id, card)` also gets the hand copy (Nugget). """ def __init__( self, @@ -444,6 +467,7 @@ class TrainerCardDef(CardDefinition): trainer_type: TrainerType, effect: Optional[Any] = None, condition: Optional[Callable] = None, + abilities: Optional[List[Ability]] = None, display_name: Optional[str] = None, searchable_by: Optional[List[str]] = None, subtypes: Optional[List[str]] = None, @@ -452,6 +476,13 @@ class TrainerCardDef(CardDefinition): super().__init__(guid, key, name, collector_number, set_code, rarity, display_name, searchable_by, subtypes, attributes) self.effect = effect self.condition = condition + # Trainers have no PIE_ABILITIES slot; declared abilities register for + # the session's trigger scans only (Dream Ball's ON_TAKEN_AS_PRIZE). + self.abilities: List[Ability] = abilities or [] + for idx, a in enumerate(self.abilities): + if not a.ability_id: + a.ability_id = ability_id_for(guid, idx + 100) # slot 0 = StadiumCardDef.ability + ABILITIES_BY_ID[a.ability_id] = a if effect is not None: TRAINER_EFFECTS_BY_GUID[guid.lower()] = effect self.extra_attributes.update({ @@ -464,6 +495,33 @@ class ItemCardDef(TrainerCardDef): kwargs['trainer_type'] = TrainerType.ITEM super().__init__(**kwargs) +class FossilItemCardDef(ItemCardDef): + """Item played as if it were a Basic Colorless Pokemon (fossils). + + The archetype stays a Trainer-Item (real PTCGO shape: deck-builder + grouping, Item locks, mulligan/setup exclusion); plays_as_pokemon makes + the board entity a PokemonEntity so it benches, evolves, takes damage and + offers its PIE abilities like a Basic while in play. + """ + plays_as_pokemon = True + + def __init__(self, hp: int, passive: Optional[Any] = None, **kwargs): + super().__init__(**kwargs) + self.passive = passive + self.extra_attributes.update({ + str(AttrID.HP.value): {"type": "int", "value": hp}, + str(AttrID.STAGE.value): {"type": "int", "value": PokemonStage.BASIC.value}, + str(AttrID.POKEMON_TYPES.value): { + "type": "json", "value": json.dumps([PokemonTypes.COLORLESS.value]) + }, + str(AttrID.RETREAT_COST.value): {"type": "int", "value": 0}, + str(AttrID.PIE_ABILITIES.value): { + "type": "json", + "value": json.dumps([a.to_dict() for a in self.abilities]), + }, + }) + + class SupporterCardDef(TrainerCardDef): def __init__(self, **kwargs): kwargs['trainer_type'] = TrainerType.SUPPORTER diff --git a/spirit/game/models/board.py b/spirit/game/models/board.py index 5f10be2..8ca6055 100644 --- a/spirit/game/models/board.py +++ b/spirit/game/models/board.py @@ -256,6 +256,10 @@ def create_card_entity(card_obj: Card, owning_player_id: Optional[str] = None, e elif c_type == CardType.ENERGY.value: return EnergyEntity(card_obj, owning_player_id, entity_id) else: + from spirit.game.data_utils import def_for # circular-import guard + if getattr(def_for(card_obj.guid), "plays_as_pokemon", False): + # Fossils: Trainer archetype, Pokemon entity on the board. + return PokemonEntity(card_obj, owning_player_id, entity_id) return TrainerEntity(card_obj, owning_player_id, entity_id) @@ -452,8 +456,11 @@ class BoardState: @staticmethod def _is_basic_pokemon(entity: BoardEntity) -> bool: + # Fossils (Trainer archetypes played as Pokemon) don't count for + # mulligans or setup placement. return ( isinstance(entity, PokemonEntity) + and entity.card_obj.get_attribute_value(AttrID.CARD_TYPE) == CardType.POKEMON.value and entity.card_obj.get_attribute_value(AttrID.STAGE) == PokemonStage.BASIC.value ) @@ -643,9 +650,10 @@ class BoardState: continue bench.children.sort(key=self.bench_slot_of) occupied = {self.bench_slot_of(c) for c in bench.children} + slots = bench.get_attribute(AttrID.AREA_SLOTS) or BENCH_SLOT_COUNT bench.set_attribute( AttrID.AREA_EMPTY_SLOTS, - [s for s in range(BENCH_SLOT_COUNT) if s not in occupied], + [s for s in range(slots) if s not in occupied], ) def serialize(self, viewer_id: Optional[str] = None) -> Dict[str, Any]: diff --git a/spirit/game/scripts/cards/CEL25/Cosmoem_14.py b/spirit/game/scripts/cards/CEL25/Cosmoem_14.py index 8111de1..630cb0e 100644 --- a/spirit/game/scripts/cards/CEL25/Cosmoem_14.py +++ b/spirit/game/scripts/cards/CEL25/Cosmoem_14.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.passives_common import flip_protection +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -24,7 +25,7 @@ card = PokemonCardDef( title="Star Protection", game_text="Flip a coin. If heads, during your opponent's next turn, prevent all damage done to this Pok\u00e9mon by attacks.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=flip_protection(prevent=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/CEL25/Cosmog_13.py b/spirit/game/scripts/cards/CEL25/Cosmog_13.py index 9b2e657..2266f50 100644 --- a/spirit/game/scripts/cards/CEL25/Cosmog_13.py +++ b/spirit/game/scripts/cards/CEL25/Cosmog_13.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.passives_common import flip_protection +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -23,7 +24,7 @@ card = PokemonCardDef( title="Star Protection", game_text="Flip a coin. If heads, during your opponent's next turn, prevent all damage done to this Pok\u00e9mon by attacks.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=flip_protection(prevent=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/CEL25/Dialga_20.py b/spirit/game/scripts/cards/CEL25/Dialga_20.py index 834f49b..9c86b3a 100644 --- a/spirit/game/scripts/cards/CEL25/Dialga_20.py +++ b/spirit/game/scripts/cards/CEL25/Dialga_20.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import recover_from_discard, requires_discard +from spirit.game.card_effects.attacks_common import damage_per, count_energy card = PokemonCardDef( guid="03fc082d-f894-5f17-96af-f266f3ae3d82", @@ -23,7 +25,8 @@ card = PokemonCardDef( title="Temporal Backflow", game_text="Put a card from your discard pile into your hand.", cost={PokemonTypes.METAL: 1}, - effect=unimplemented, + condition=requires_discard(), + effect=recover_from_discard(count=1, minimum=1, reveal=False, to="hand"), ), Attack( title="Metal Blast", @@ -31,7 +34,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=60, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("self", energy_type=PokemonTypes.METAL), 20, base=60), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/CEL25/FlyingPikachuVMAX_7.py b/spirit/game/scripts/cards/CEL25/FlyingPikachuVMAX_7.py index a00c6f0..06c5bc6 100644 --- a/spirit/game/scripts/cards/CEL25/FlyingPikachuVMAX_7.py +++ b/spirit/game/scripts/cards/CEL25/FlyingPikachuVMAX_7.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.passives_common import prevent_damage_when + + +def _max_balloon_shield(calc, carrier): + if calc.target is not carrier: + return False + attacker = calc.attacker + return attacker is not None and \ + attacker.get_attribute(AttrID.STAGE) == PokemonStage.BASIC.value + + +async def _max_balloon(ctx): + await ctx.deal_damage() + ctx.add_passive_through_opponents_turn( + ctx.attacker, prevent_damage_when(_max_balloon_shield) + ) + card = PokemonCardDef( guid="a3f7a335-9942-5930-9395-5f6d4d26754e", @@ -25,7 +42,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, prevent all damage done to this Pok\u00e9mon by attacks from Basic Pok\u00e9mon.", cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 2}, damage=160, - effect=unimplemented, + effect=_max_balloon, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/CEL25/FlyingPikachuV_6.py b/spirit/game/scripts/cards/CEL25/FlyingPikachuV_6.py index 7087d28..52d106f 100644 --- a/spirit/game/scripts/cards/CEL25/FlyingPikachuV_6.py +++ b/spirit/game/scripts/cards/CEL25/FlyingPikachuV_6.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack, flip_or_nothing +from spirit.game.card_effects.passives_common import apply_protection + + +async def _fly_success(ctx): + await ctx.deal_damage() + await apply_protection(ctx, prevent=True, effects_too=True) + card = PokemonCardDef( guid="f13a80b5-f37a-54ae-8277-1ed8c8cc6e15", @@ -21,17 +29,17 @@ card = PokemonCardDef( abilities=[ Attack( title="Thunder Shock", - game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", + game_text="Flip a coin. If heads, your opponent's Active Pokémon is now Paralyzed.", cost={PokemonTypes.LIGHTNING: 1}, damage=20, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), Attack( title="Fly", - game_text="Flip a coin. If tails, this attack does nothing. If heads, during your opponent's next turn, prevent all damage from and effects of attacks done to this Pok\u00e9mon.", + game_text="Flip a coin. If tails, this attack does nothing. If heads, during your opponent's next turn, prevent all damage from and effects of attacks done to this Pokémon.", cost={PokemonTypes.COLORLESS: 3}, damage=120, - effect=unimplemented, + effect=flip_or_nothing(then=_fly_success), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/CEL25/Groudon_17.py b/spirit/game/scripts/cards/CEL25/Groudon_17.py index 1fced1a..70d9e2e 100644 --- a/spirit/game/scripts/cards/CEL25/Groudon_17.py +++ b/spirit/game/scripts/cards/CEL25/Groudon_17.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import mill_scaled_damage +from spirit.game.card_effects.support_common import is_energy card = PokemonCardDef( guid="9affb786-a38e-5429-84b8-37ebec853940", @@ -24,7 +26,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=80, damage_operator="x", - effect=unimplemented, + effect=mill_scaled_damage(5, 80, pred=is_energy), ), Attack( title="Massive Rend", diff --git a/spirit/game/scripts/cards/CEL25/HoOh_1.py b/spirit/game/scripts/cards/CEL25/HoOh_1.py index 8ded44d..265d01d 100644 --- a/spirit/game/scripts/cards/CEL25/HoOh_1.py +++ b/spirit/game/scripts/cards/CEL25/HoOh_1.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import self_energy_discard_attack, snipe_attack card = PokemonCardDef( guid="24eeb34e-c276-5fec-b384-33e4d9670532", @@ -23,14 +24,14 @@ card = PokemonCardDef( title="Sacred Fire", game_text="This attack does 50 damage to 1 of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=snipe_attack(50, pool="any", count=1), ), Attack( title="Fire Blast", game_text="Discard an Energy from this Pok\u00e9mon.", cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + effect=self_energy_discard_attack(count=1), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/CEL25/Kyogre_3.py b/spirit/game/scripts/cards/CEL25/Kyogre_3.py index 6331615..fa1303d 100644 --- a/spirit/game/scripts/cards/CEL25/Kyogre_3.py +++ b/spirit/game/scripts/cards/CEL25/Kyogre_3.py @@ -1,5 +1,26 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import is_energy_card + + +async def aqua_storm(ctx): + """Discard the top 5 of your deck; 50 damage per Energy card discarded + this way to each of 2 chosen opposing Benched Pokémon (no W/R).""" + milled = ctx.deck_top(5) + await ctx.discard_cards(milled) + energy_count = sum(1 for c in milled if is_energy_card(c)) + bench = ctx.opponent_bench() + if not bench: + return + targets = await ctx.choose_cards( + bench, 2, prompt="Choose 2 of your opponent's Benched Pokémon", + ) + if energy_count <= 0: + return + amount = 50 * energy_count + for target in targets: + await ctx.deal_damage(amount, target=target, apply_modifiers=False) + card = PokemonCardDef( guid="b6921130-84f9-5a4b-b273-47fba72812f9", @@ -22,7 +43,7 @@ card = PokemonCardDef( title="Aqua Storm", game_text="Discard the top 5 cards of your deck, and then choose 2 of your opponent's Benched Pok\u00e9mon. This attack does 50 damage for each Energy card you discarded in this way to each of those Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=aqua_storm, ), Attack( title="Surf", diff --git a/spirit/game/scripts/cards/CEL25/Lugia_22.py b/spirit/game/scripts/cards/CEL25/Lugia_22.py index 2ffcfce..7236aa1 100644 --- a/spirit/game/scripts/cards/CEL25/Lugia_22.py +++ b/spirit/game/scripts/cards/CEL25/Lugia_22.py @@ -1,5 +1,23 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, lock_all_attacks +from spirit.game.session.legal_actions import energy_provided_count + + +def _both_actives_energy(ctx): + total = 0 + for pokemon in (ctx.my_active(), ctx.opponent_active()): + if pokemon is None: + continue + for energy in ctx.attached_energies(pokemon): + total += energy_provided_count(energy) + return total + + +async def _deep_crush(ctx): + await ctx.deal_damage() + lock_all_attacks(ctx, ctx.attacker) + card = PokemonCardDef( guid="2b73e21f-77dd-5462-8012-bea5eb1a13f1", @@ -25,14 +43,14 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=20, damage_operator="x", - effect=unimplemented, + effect=damage_per(_both_actives_energy, 20), ), Attack( title="Deep Crush", game_text="During your next turn, this Pok\u00e9mon can't attack.", cost={PokemonTypes.COLORLESS: 4}, damage=160, - effect=unimplemented, + effect=_deep_crush, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/CEL25/Lunala_15.py b/spirit/game/scripts/cards/CEL25/Lunala_15.py index 9b9c36e..6fa5ab9 100644 --- a/spirit/game/scripts/cards/CEL25/Lunala_15.py +++ b/spirit/game/scripts/cards/CEL25/Lunala_15.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.attacks_common import snipe_attack + + +async def lunar_pain(ctx): + """Double the number of damage counters on each of your opponent's Pokemon.""" + for pokemon in ctx.opponent_pokemon_in_play(): + current = (ctx.max_hp(pokemon) - pokemon.get_attribute(AttrID.HP, 0)) // 10 + if current > 0: + await ctx.deal_damage(current * 10, target=pokemon, as_counters=True) card = PokemonCardDef( guid="4fd81eeb-9609-5689-b7c6-1c7892dd1f28", @@ -24,14 +33,14 @@ card = PokemonCardDef( title="Lunar Pain", game_text="Double the number of damage counters on each of your opponent's Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + effect=lunar_pain, ), Attack( title="Psychic Shot", game_text="This attack also does 30 damage to 1 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 2}, damage=130, - effect=unimplemented, + effect=snipe_attack(30, also_base=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/CEL25/Mew_11.py b/spirit/game/scripts/cards/CEL25/Mew_11.py index 49056a5..c50f1b5 100644 --- a/spirit/game/scripts/cards/CEL25/Mew_11.py +++ b/spirit/game/scripts/cards/CEL25/Mew_11.py @@ -1,5 +1,26 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_item_card +from spirit.game.card_effects.pokemon import in_active_spot + + +async def mysterious_tail(ctx): + """You may look at the top 6 cards of your deck, reveal an Item card you + find there, and put it into your hand. Shuffle the rest back.""" + if not await ctx.ask_yes_no("Look at the top 6 cards of your deck?"): + return + top = ctx.deck_top(6) + candidates = [c for c in top if is_item_card(c)] + picks = [] + if candidates: + picks = await ctx.choose_cards( + candidates, 1, minimum=0, + prompt="Choose an Item card to put into your hand.", + display_cards=top, + ) + await ctx.put_in_hand(picks, reveal=True) + await ctx.shuffle_deck() + card = PokemonCardDef( guid="637c92b7-bbaa-5279-b62d-0dc204ee4e6c", @@ -22,7 +43,9 @@ card = PokemonCardDef( Ability( title="Mysterious Tail", game_text="Once during your turn, if this Pok\u00e9mon is in the Active Spot, you may look at the top 6 cards of your deck, reveal an Item card you find there, and put it into your hand. Shuffle the other cards back into your deck.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=in_active_spot, + effect=mysterious_tail, ), Attack( title="Psyshot", diff --git a/spirit/game/scripts/cards/CEL25/Mew_25.py b/spirit/game/scripts/cards/CEL25/Mew_25.py index a0daac6..44f38c8 100644 --- a/spirit/game/scripts/cards/CEL25/Mew_25.py +++ b/spirit/game/scripts/cards/CEL25/Mew_25.py @@ -1,5 +1,26 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_item_card +from spirit.game.card_effects.pokemon import in_active_spot + + +async def mysterious_tail(ctx): + """You may look at the top 6 cards of your deck, reveal an Item card you + find there, and put it into your hand. Shuffle the rest back.""" + if not await ctx.ask_yes_no("Look at the top 6 cards of your deck?"): + return + top = ctx.deck_top(6) + candidates = [c for c in top if is_item_card(c)] + picks = [] + if candidates: + picks = await ctx.choose_cards( + candidates, 1, minimum=0, + prompt="Choose an Item card to put into your hand.", + display_cards=top, + ) + await ctx.put_in_hand(picks, reveal=True) + await ctx.shuffle_deck() + card = PokemonCardDef( guid="37a0065c-8bb7-5076-aa62-100bb1c1896e", @@ -22,7 +43,9 @@ card = PokemonCardDef( Ability( title="Mysterious Tail", game_text="Once during your turn, if this Pok\u00e9mon is in the Active Spot, you may look at the top 6 cards of your deck, reveal an Item card you find there, and put it into your hand. Shuffle the other cards back into your deck.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=in_active_spot, + effect=mysterious_tail, ), Attack( title="Psyshot", diff --git a/spirit/game/scripts/cards/CEL25/Palkia_4.py b/spirit/game/scripts/cards/CEL25/Palkia_4.py index a1350d1..484f076 100644 --- a/spirit/game/scripts/cards/CEL25/Palkia_4.py +++ b/spirit/game/scripts/cards/CEL25/Palkia_4.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID, TrainerType +from spirit.game.session.passives import Passive +from spirit.game.card_effects.passives_common import is_in_active_spot, boost_own_next_turn + + +class AbsoluteSpacePassive(Passive): + def blocks_trainer_play(self, card, player_id, carrier): + if player_id == carrier.owning_player_id: + return False + if not is_in_active_spot(carrier): + return False + return card.get_attribute(AttrID.TRAINER_TYPE) == TrainerType.STADIUM.value + card = PokemonCardDef( guid="70959369-220f-5338-bdef-cfebe8044717", @@ -21,7 +33,7 @@ card = PokemonCardDef( Ability( title="Absolute Space", game_text="As long as this Pok\u00e9mon is in the Active Spot, your opponent can't play any Stadium cards from their hand.", - effect=unimplemented, + passive=AbsoluteSpacePassive(), ), Attack( title="Overdrive Smash", @@ -29,7 +41,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=80, damage_operator="+", - effect=unimplemented, + effect=boost_own_next_turn(80, attack_title="Overdrive Smash"), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/CEL25/Pikachu_5.py b/spirit/game/scripts/cards/CEL25/Pikachu_5.py index 9c964ad..c41643d 100644 --- a/spirit/game/scripts/cards/CEL25/Pikachu_5.py +++ b/spirit/game/scripts/cards/CEL25/Pikachu_5.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="3966cba0-7b69-5294-a19d-ed9721139381", @@ -28,7 +29,7 @@ card = PokemonCardDef( game_text="Flip a coin. If tails, this Pok\u00e9mon also does 10 damage to itself.", cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=flip_damage(tails_self_damage=10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/CEL25/ProfessorsResearchProfessorOak_23.py b/spirit/game/scripts/cards/CEL25/ProfessorsResearchProfessorOak_23.py index 8ee79aa..f654ea0 100644 --- a/spirit/game/scripts/cards/CEL25/ProfessorsResearchProfessorOak_23.py +++ b/spirit/game/scripts/cards/CEL25/ProfessorsResearchProfessorOak_23.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import professors_research card = SupporterCardDef( guid="86d26aad-0a8b-5aa8-bec2-82c4ec9104a2", @@ -11,5 +12,5 @@ card = SupporterCardDef( collector_number=23, set_code="CEL25", rarity=Rarities.RareHolo, - effect=unimplemented + effect=professors_research ) diff --git a/spirit/game/scripts/cards/CEL25/Reshiram_2.py b/spirit/game/scripts/cards/CEL25/Reshiram_2.py index 653d934..c24de3e 100644 --- a/spirit/game/scripts/cards/CEL25/Reshiram_2.py +++ b/spirit/game/scripts/cards/CEL25/Reshiram_2.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import spread_damage, bonus_if, named_in_play card = PokemonCardDef( guid="1134864a-2e23-5bd8-ae2b-16b9f2c992d9", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Scorching Wind", game_text="This attack does 20 damage to each of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + effect=spread_damage(20, side="opponent"), ), Attack( title="Black Flame", @@ -30,7 +31,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=80, damage_operator="+", - effect=unimplemented, + effect=bonus_if(named_in_play("Zekrom"), 80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/CEL25/Solgaleo_21.py b/spirit/game/scripts/cards/CEL25/Solgaleo_21.py index 073edf1..9a7b11d 100644 --- a/spirit/game/scripts/cards/CEL25/Solgaleo_21.py +++ b/spirit/game/scripts/cards/CEL25/Solgaleo_21.py @@ -1,5 +1,36 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.trainers import is_basic_energy_card + + +def rush_in_condition(board, player_id, pokemon): + return pokemon is not board.active_pokemon(player_id) + + +async def rush_in(ctx): + """Once during your turn, if this Pokémon is on your Bench, you may + switch it with your Active Pokémon.""" + await ctx.switch_active(ctx.player_id, ctx.source) + + +async def solar_geyser(ctx): + """Printed damage, then attach up to 2 basic Energy cards from your + discard pile to 1 of your Benched Pokémon.""" + await ctx.deal_damage() + cards = [c for c in ctx.discard_pile() if is_basic_energy_card(c)] + bench = ctx.my_bench() + if not cards or not bench: + return + picks = await ctx.choose_cards( + cards, 2, minimum=1, + prompt="Choose up to 2 basic Energy cards from your discard pile to attach", + ) + if not picks: + return + target = await ctx.choose_pokemon(bench, "Choose a Benched Pokémon") or bench[0] + for card in picks: + await ctx.attach_energy(card, target) + card = PokemonCardDef( guid="9f4f4d4d-f28f-532a-a1da-09ec3186a1a5", @@ -23,14 +54,16 @@ card = PokemonCardDef( Ability( title="Rush In", game_text="Once during your turn, if this Pok\u00e9mon is on your Bench, you may switch it with your Active Pok\u00e9mon.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=rush_in_condition, + effect=rush_in, ), Attack( title="Solar Geyser", game_text="Attach up to 2 basic Energy cards from your discard pile to 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 2}, damage=100, - effect=unimplemented, + effect=solar_geyser, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/CEL25/SurfingPikachuVMAX_9.py b/spirit/game/scripts/cards/CEL25/SurfingPikachuVMAX_9.py index 24ec1e2..170f047 100644 --- a/spirit/game/scripts/cards/CEL25/SurfingPikachuVMAX_9.py +++ b/spirit/game/scripts/cards/CEL25/SurfingPikachuVMAX_9.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import spread_damage card = PokemonCardDef( guid="fa92a5b1-0306-59dd-b692-cdebfc9985c9", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="This attack also does 30 damage to each of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.WATER: 3}, damage=160, - effect=unimplemented, + effect=spread_damage(30, side="opponent", also_base=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/CEL25/Xerneas_12.py b/spirit/game/scripts/cards/CEL25/Xerneas_12.py index 2ccebfe..3147c20 100644 --- a/spirit/game/scripts/cards/CEL25/Xerneas_12.py +++ b/spirit/game/scripts/cards/CEL25/Xerneas_12.py @@ -1,5 +1,45 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import ( + AttrID, CLIENT_POKEMON_TYPE_NAMES, PokemonTypes, PokemonStage, Rarities, +) +from spirit.game.card_effects.trainers import is_basic_energy_card + + +async def breath_of_life(ctx): + """Search up to 3 basic Energy of different types and attach them to your + Pokémon in any way you like. Then, shuffle your deck.""" + deck_cards = list(ctx.deck(ctx.player_id)) + reps = [] + labels = {} + seen_types = [] + for card in deck_cards: + if not is_basic_energy_card(card): + continue + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + if not types or types[0] in seen_types: + continue + seen_types.append(types[0]) + reps.append(card) + labels[card.entity_id] = f"{CLIENT_POKEMON_TYPE_NAMES[PokemonTypes(types[0])]} Energy" + + if not reps: + await ctx.shuffle_deck() + return + + picks = await ctx.choose_cards( + reps, 3, minimum=0, + prompt="Choose up to 3 basic Energy cards of different types.", + display_cards=deck_cards, + ) + for energy in picks: + label = labels[energy.entity_id] + target = await ctx.choose_pokemon( + ctx.my_pokemon_in_play(), f"Choose a Pokémon to attach {label} to" + ) + if target is not None: + await ctx.attach_energy(energy, target) + await ctx.shuffle_deck() + card = PokemonCardDef( guid="bf180f7a-320b-507f-80e6-dede7ace3b13", @@ -22,7 +62,7 @@ card = PokemonCardDef( title="Breath of Life", game_text="Search your deck for up to 3 basic Energy cards of different types and attach them to your Pok\u00e9mon in any way you like. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + effect=breath_of_life, ), Attack( title="Aurora Horns", diff --git a/spirit/game/scripts/cards/CEL25/Yveltal_19.py b/spirit/game/scripts/cards/CEL25/Yveltal_19.py index fbd82bb..f925515 100644 --- a/spirit/game/scripts/cards/CEL25/Yveltal_19.py +++ b/spirit/game/scripts/cards/CEL25/Yveltal_19.py @@ -1,5 +1,23 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_special_energy + + +async def cry_of_destruction(ctx): + """Discard up to 3 Special Energy from your opponent's Pokemon.""" + energies = [] + for pokemon in ctx.opponent_pokemon_in_play(): + if ctx.effects_blocked(pokemon): + continue + energies.extend(e for e in ctx.attached_energies(pokemon) if is_special_energy(e)) + if not energies: + return + picks = await ctx.choose_cards( + energies, min(3, len(energies)), minimum=0, + prompt="Discard up to 3 Special Energy from your opponent's Pokémon.", + ) + await ctx.discard_cards(picks) + card = PokemonCardDef( guid="904e53b8-7d9f-56b2-9258-ddd922439997", @@ -23,7 +41,7 @@ card = PokemonCardDef( title="Cry of Destruction", game_text="Discard up to 3 Special Energy from your opponent's Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + effect=cry_of_destruction, ), Attack( title="Dark Feather", diff --git a/spirit/game/scripts/cards/CEL25/ZacianV_16.py b/spirit/game/scripts/cards/CEL25/ZacianV_16.py index be2c4de..03b5a7a 100644 --- a/spirit/game/scripts/cards/CEL25/ZacianV_16.py +++ b/spirit/game/scripts/cards/CEL25/ZacianV_16.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import is_energy_card +from spirit.game.card_effects.support_common import search_attach_energy +from spirit.game.card_effects.attacks_common import damage_per, count_energy + + +def _is_psychic_energy_card(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_energy_card(card) and PokemonTypes.PSYCHIC.value in types + card = PokemonCardDef( guid="a61f827e-fa94-56e2-9dab-cf53202828b1", @@ -21,7 +30,9 @@ card = PokemonCardDef( Ability( title="Roar of the Sword", game_text="Once during your turn, you may search your deck for a Psychic Energy card and attach it to 1 of your Pok\u00e9mon. Then, shuffle your deck. If you use this Ability, your turn ends.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + ends_turn=True, + effect=search_attach_energy(predicate=_is_psychic_energy_card, count=1, distribute=False), ), Attack( title="Storm Slash", @@ -29,7 +40,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=60, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("self", energy_type=PokemonTypes.PSYCHIC), 30, base=60), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/CEL25/ZamazentaV_18.py b/spirit/game/scripts/cards/CEL25/ZamazentaV_18.py index 7b50721..45f62b4 100644 --- a/spirit/game/scripts/cards/CEL25/ZamazentaV_18.py +++ b/spirit/game/scripts/cards/CEL25/ZamazentaV_18.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.passives_common import takes_less_passive +from spirit.game.card_effects.pokemon import is_pokemon_vmax + +_is_fighting = lambda p: PokemonTypes.FIGHTING.value in (p.get_attribute(AttrID.POKEMON_TYPES) or []) +_protects_fighting_team = lambda target, carrier: ( + target.owning_player_id == carrier.owning_player_id and _is_fighting(target) +) +_attacker_is_vmax = lambda attacker: is_pokemon_vmax(attacker.archetype_id) card = PokemonCardDef( guid="f4b957a8-0f26-5aeb-b968-294fdf0bee7e", @@ -21,7 +29,10 @@ card = PokemonCardDef( Ability( title="Growl of the Shield", game_text="All of your Fighting Pok\u00e9mon take 20 less damage from attacks from your opponent's Pok\u00e9mon VMAX (after applying Weakness and Resistance). You can't apply more than 1 Growl of the Shield Ability at a time.", - effect=unimplemented, + passive=takes_less_passive( + 20, protects=_protects_fighting_team, + attacker_pred=_attacker_is_vmax, stack_key="GrowlOfTheShield", + ), ), Attack( title="Heavy Impact", diff --git a/spirit/game/scripts/cards/CEL25/Zekrom_10.py b/spirit/game/scripts/cards/CEL25/Zekrom_10.py index 34eab03..3a46352 100644 --- a/spirit/game/scripts/cards/CEL25/Zekrom_10.py +++ b/spirit/game/scripts/cards/CEL25/Zekrom_10.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, named_in_play + + +async def field_crush(ctx): + """30 damage; if your opponent has a Stadium in play, discard it.""" + await ctx.deal_damage() + stadium = ctx.stadium_in_play() + if stadium is not None and stadium.owning_player_id == ctx.opponent_id: + await ctx.discard_stadium() + card = PokemonCardDef( guid="09b5c42b-c34c-54dc-a807-f4049b33d6dc", @@ -23,7 +33,7 @@ card = PokemonCardDef( game_text="If your opponent has a Stadium in play, discard it.", cost={PokemonTypes.COLORLESS: 2}, damage=30, - effect=unimplemented, + effect=field_crush, ), Attack( title="White Thunder", @@ -31,7 +41,7 @@ card = PokemonCardDef( cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 1}, damage=80, damage_operator="+", - effect=unimplemented, + effect=bonus_if(named_in_play("Reshiram"), 80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/Aipom_56.py b/spirit/game/scripts/cards/PGO/Aipom_56.py index 1a730b4..c18886c 100644 --- a/spirit/game/scripts/cards/PGO/Aipom_56.py +++ b/spirit/game/scripts/cards/PGO/Aipom_56.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import flip_protection card = PokemonCardDef( guid="3e3bc7ff-4b50-57ff-9cd1-fbdfd61ee1fc", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Bustle", game_text="Flip a coin. If heads, during your opponent's next turn, prevent all damage from and effects of attacks done to this Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=flip_protection(prevent=True, effects_too=True), ), Attack( title="Slap", diff --git a/spirit/game/scripts/cards/PGO/AlolanExeggutorV_5.py b/spirit/game/scripts/cards/PGO/AlolanExeggutorV_5.py index aabda3b..1ca1242 100644 --- a/spirit/game/scripts/cards/PGO/AlolanExeggutorV_5.py +++ b/spirit/game/scripts/cards/PGO/AlolanExeggutorV_5.py @@ -1,5 +1,27 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import count_energy, flip_or_nothing +from spirit.game.card_effects.support_common import search_attach_energy +from spirit.game.card_effects.trainers import is_grass_energy_card + +_grass_attached = count_energy("self", energy_type=PokemonTypes.GRASS) + +growing_tall = flip_or_nothing(then=search_attach_energy( + is_grass_energy_card, count=5, + prompt="Choose up to 5 Grass Energy cards to attach to your Pokémon.", +)) + + +async def head_swing(ctx): + """30 to 1 opposing Pokemon per Grass Energy attached (no W/R on Bench).""" + amount = 30 * _grass_attached(ctx) + if amount <= 0: + return + target = await ctx.choose_pokemon( + ctx.opponent_pokemon_in_play(), "Choose 1 of your opponent's Pokémon" + ) + if target is not None: + await ctx.deal_damage(amount, target=target) card = PokemonCardDef( guid="010594a7-0853-5833-933f-009a6aea83f6", @@ -22,13 +44,13 @@ card = PokemonCardDef( title="Growing Tall", game_text="Flip a coin. If heads, search your deck for up to 5 Grass Energy cards and attach them to your Pok\u00e9mon in any way you like. Then, shuffle your deck.", cost={PokemonTypes.GRASS: 1}, - effect=unimplemented, + effect=growing_tall, ), Attack( title="Head Swing", game_text="This attack does 30 damage to 1 of your opponent's Pok\u00e9mon for each Grass Energy attached to this Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.COLORLESS: 3}, - effect=unimplemented, + effect=head_swing, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/AlolanExeggutorV_71.py b/spirit/game/scripts/cards/PGO/AlolanExeggutorV_71.py index 00caa2a..4b23b01 100644 --- a/spirit/game/scripts/cards/PGO/AlolanExeggutorV_71.py +++ b/spirit/game/scripts/cards/PGO/AlolanExeggutorV_71.py @@ -1,5 +1,27 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import count_energy, flip_or_nothing +from spirit.game.card_effects.support_common import search_attach_energy +from spirit.game.card_effects.trainers import is_grass_energy_card + +_grass_attached = count_energy("self", energy_type=PokemonTypes.GRASS) + +growing_tall = flip_or_nothing(then=search_attach_energy( + is_grass_energy_card, count=5, + prompt="Choose up to 5 Grass Energy cards to attach to your Pokémon.", +)) + + +async def head_swing(ctx): + """30 to 1 opposing Pokemon per Grass Energy attached (no W/R on Bench).""" + amount = 30 * _grass_attached(ctx) + if amount <= 0: + return + target = await ctx.choose_pokemon( + ctx.opponent_pokemon_in_play(), "Choose 1 of your opponent's Pokémon" + ) + if target is not None: + await ctx.deal_damage(amount, target=target) card = PokemonCardDef( guid="4076648a-3fa3-560e-8533-22af1c518294", @@ -22,13 +44,13 @@ card = PokemonCardDef( title="Growing Tall", game_text="Flip a coin. If heads, search your deck for up to 5 Grass Energy cards and attach them to your Pok\u00e9mon in any way you like. Then, shuffle your deck.", cost={PokemonTypes.GRASS: 1}, - effect=unimplemented, + effect=growing_tall, ), Attack( title="Head Swing", game_text="This attack does 30 damage to 1 of your opponent's Pok\u00e9mon for each Grass Energy attached to this Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.COLORLESS: 3}, - effect=unimplemented, + effect=head_swing, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/AlolanRaticate_42.py b/spirit/game/scripts/cards/PGO/AlolanRaticate_42.py index df409ca..0905364 100644 --- a/spirit/game/scripts/cards/PGO/AlolanRaticate_42.py +++ b/spirit/game/scripts/cards/PGO/AlolanRaticate_42.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities + + +async def super_fang(ctx): + """Damage counters on the opponent's Active until its remaining HP is 10.""" + target = ctx.defender + if target is None: + return + hp = target.get_attribute(AttrID.HP, 0) + if hp > 10: + await ctx.deal_damage(hp - 10, target=target, as_counters=True) + card = PokemonCardDef( guid="2d7be742-d405-5c3c-befb-5ef93a4db3ea", @@ -23,13 +35,16 @@ card = PokemonCardDef( title="Chase Up", game_text="Search your deck for a card and put it into your hand. Then, shuffle your deck.", cost={PokemonTypes.DARKNESS: 1}, - effect=unimplemented, + effect=search_to_hand( + None, count=1, reveal=False, + prompt="Choose a card to put into your hand.", + ), ), Attack( title="Super Fang", - game_text="Put damage counters on your opponent's Active Pok\u00e9mon until its remaining HP is 10.", + game_text="Put damage counters on your opponent's Active Pokémon until its remaining HP is 10.", cost={PokemonTypes.COLORLESS: 3}, - effect=unimplemented, + effect=super_fang, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/PGO/AlolanRattata_41.py b/spirit/game/scripts/cards/PGO/AlolanRattata_41.py index b4b08fd..e6af218 100644 --- a/spirit/game/scripts/cards/PGO/AlolanRattata_41.py +++ b/spirit/game/scripts/cards/PGO/AlolanRattata_41.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_or_nothing card = PokemonCardDef( guid="5f958813-ec88-5319-99da-07def6d246ce", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Flip a coin. If tails, this attack does nothing.", cost={PokemonTypes.COLORLESS: 2}, damage=50, - effect=unimplemented, + effect=flip_or_nothing(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/Ambipom_57.py b/spirit/game/scripts/cards/PGO/Ambipom_57.py index c98af00..32ce470 100644 --- a/spirit/game/scripts/cards/PGO/Ambipom_57.py +++ b/spirit/game/scripts/cards/PGO/Ambipom_57.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import count_energy, flip_damage +from spirit.game.card_effects.passives_common import flip_prevent_damage_passive card = PokemonCardDef( guid="822d008a-f144-56e1-ae7e-3779ccfd515c", @@ -22,7 +24,7 @@ card = PokemonCardDef( Ability( title="Primate Dexterity", game_text="If any damage is done to this Pok\u00e9mon by attacks, flip a coin. If heads, prevent that damage.", - effect=unimplemented, + passive=flip_prevent_damage_passive("Primate Dexterity"), ), Attack( title="Full Tilt Fling", @@ -30,7 +32,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 1}, damage=60, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins_from=count_energy("self"), per_heads=60), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/Ariados_7.py b/spirit/game/scripts/cards/PGO/Ariados_7.py index 3ed2c43..fb9d1da 100644 --- a/spirit/game/scripts/cards/PGO/Ariados_7.py +++ b/spirit/game/scripts/cards/PGO/Ariados_7.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.support_common import heal_attack card = PokemonCardDef( guid="f26bde5e-3a31-57e4-9922-90109bcac0ab", @@ -24,14 +26,15 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed and Poisoned.", cost={PokemonTypes.COLORLESS: 1}, damage=20, - effect=unimplemented, + effect=condition_attack( + SpecialConditions.PARALYZED, SpecialConditions.POISONED, flip=True), ), Attack( title="Absorb", game_text="Heal 50 damage from this Pok\u00e9mon.", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=50, - effect=unimplemented, + effect=heal_attack(amount=50), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/Articuno_24.py b/spirit/game/scripts/cards/PGO/Articuno_24.py index de15b3d..1e1da49 100644 --- a/spirit/game/scripts/cards/PGO/Articuno_24.py +++ b/spirit/game/scripts/cards/PGO/Articuno_24.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, def_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import team_damage_boost_passive +from spirit.game.session.effects import is_basic_pokemon, is_water_pokemon + + +def _ice_symbol_attacker(pokemon): + if not (is_basic_pokemon(pokemon) and is_water_pokemon(pokemon)): + return False + definition = def_for(pokemon.archetype_id) + return getattr(definition, "display_name", "") != "Articuno" card = PokemonCardDef( guid="cde48857-1d2a-5303-8d56-23de5aba41b2", @@ -21,7 +30,7 @@ card = PokemonCardDef( Ability( title="Ice Symbol", game_text="Your Basic Water Pok\u00e9mon's attacks, except any Articuno, do 10 more damage to your opponent's Active Pok\u00e9mon (before applying Weakness and Resistance).", - effect=unimplemented, + passive=team_damage_boost_passive(10, attacker_pred=_ice_symbol_attacker), ), Attack( title="Freezing Wind", diff --git a/spirit/game/scripts/cards/PGO/Bibarel_60.py b/spirit/game/scripts/cards/PGO/Bibarel_60.py index a478bbf..f96a855 100644 --- a/spirit/game/scripts/cards/PGO/Bibarel_60.py +++ b/spirit/game/scripts/cards/PGO/Bibarel_60.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.passives_common import Passive, carrier_pokemon, is_in_active_spot + + +class ReassuringDamPassive(Passive): + def blocks_discard(self, card, carrier): + pokemon = carrier_pokemon(carrier) + if pokemon is None or is_in_active_spot(pokemon): + return False + if card.owning_player_id != pokemon.owning_player_id: + return False + parent = getattr(card, "parent", None) + return bool(parent) and parent.get_attribute(AttrID.NAME) == "deck" + card = PokemonCardDef( guid="b836a69f-4559-5d3c-aba3-bdd242fdcf57", @@ -21,8 +34,8 @@ card = PokemonCardDef( abilities=[ Ability( title="Reassuring Dam", - game_text="As long as this Pok\u00e9mon is on your Bench, cards in your deck can't be discarded by effects of your opponent's attacks, Abilities, Item cards, or Supporter cards.", - effect=unimplemented, + game_text="As long as this Pokémon is on your Bench, cards in your deck can't be discarded by effects of your opponent's attacks, Abilities, Item cards, or Supporter cards.", + passive=ReassuringDamPassive(), ), Attack( title="Hammer In", @@ -30,4 +43,4 @@ card = PokemonCardDef( damage=80, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/PGO/Bidoof_59.py b/spirit/game/scripts/cards/PGO/Bidoof_59.py index a23b8a1..7d59203 100644 --- a/spirit/game/scripts/cards/PGO/Bidoof_59.py +++ b/spirit/game/scripts/cards/PGO/Bidoof_59.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack card = PokemonCardDef( guid="f1d171c0-781c-56dc-a3af-4701a785ac44", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="This Pok\u00e9mon also does 10 damage to itself.", cost={PokemonTypes.COLORLESS: 3}, damage=50, - effect=unimplemented, + effect=recoil_attack(10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/Blanche_64.py b/spirit/game/scripts/cards/PGO/Blanche_64.py index d8e6aca..dbffc82 100644 --- a/spirit/game/scripts/cards/PGO/Blanche_64.py +++ b/spirit/game/scripts/cards/PGO/Blanche_64.py @@ -1,5 +1,36 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import SupporterCardDef +from spirit.game.attributes import AttrID, PokemonTypes, Rarities +from spirit.game.card_effects.trainers import is_energy_card + + +def _is_water_energy_card(card) -> bool: + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_energy_card(card) and PokemonTypes.WATER.value in types + + +async def blanche(ctx): + """Draw 2. If you drew any, flip a coin; heads attaches a Water Energy from your discard to a Benched Pokemon.""" + drawn = await ctx.draw_cards(2) + if not drawn: + return + flips = await ctx.flip_coins(1, "Blanche") + if not flips[0]: + return + bench = ctx.my_bench() + if not bench: + return + energies = [c for c in ctx.discard_pile() if _is_water_energy_card(c)] + if not energies: + return + picks = await ctx.choose_cards( + energies, 1, minimum=1, + prompt="Choose a Water Energy card to attach", + ) + if not picks: + return + target = await ctx.choose_pokemon(bench, "Choose a Benched Pokémon") or bench[0] + await ctx.attach_energy(picks[0], target) + card = SupporterCardDef( guid="b89d3384-9761-5044-b628-1bf669a74f9b", @@ -11,5 +42,5 @@ card = SupporterCardDef( collector_number=64, set_code="PGO", rarity=Rarities.Uncommon, - effect=unimplemented + effect=blanche, ) diff --git a/spirit/game/scripts/cards/PGO/Blanche_82.py b/spirit/game/scripts/cards/PGO/Blanche_82.py index 336ce9d..9b140ca 100644 --- a/spirit/game/scripts/cards/PGO/Blanche_82.py +++ b/spirit/game/scripts/cards/PGO/Blanche_82.py @@ -1,5 +1,36 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import SupporterCardDef +from spirit.game.attributes import AttrID, PokemonTypes, Rarities +from spirit.game.card_effects.trainers import is_energy_card + + +def _is_water_energy_card(card) -> bool: + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_energy_card(card) and PokemonTypes.WATER.value in types + + +async def blanche(ctx): + """Draw 2. If you drew any, flip a coin; heads attaches a Water Energy from your discard to a Benched Pokemon.""" + drawn = await ctx.draw_cards(2) + if not drawn: + return + flips = await ctx.flip_coins(1, "Blanche") + if not flips[0]: + return + bench = ctx.my_bench() + if not bench: + return + energies = [c for c in ctx.discard_pile() if _is_water_energy_card(c)] + if not energies: + return + picks = await ctx.choose_cards( + energies, 1, minimum=1, + prompt="Choose a Water Energy card to attach", + ) + if not picks: + return + target = await ctx.choose_pokemon(bench, "Choose a Benched Pokémon") or bench[0] + await ctx.attach_energy(picks[0], target) + card = SupporterCardDef( guid="cd61417a-2231-5a88-bf24-355dcb158eae", @@ -11,5 +42,5 @@ card = SupporterCardDef( collector_number=82, set_code="PGO", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=blanche, ) diff --git a/spirit/game/scripts/cards/PGO/Blastoise_17.py b/spirit/game/scripts/cards/PGO/Blastoise_17.py index cac8df3..d743393 100644 --- a/spirit/game/scripts/cards/PGO/Blastoise_17.py +++ b/spirit/game/scripts/cards/PGO/Blastoise_17.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_attach_energy +from spirit.game.card_effects.attacks_common import damage_per, count_energy + +_vitality_spring_search = search_attach_energy(count=6, distribute=True, shuffle=True) + + +async def vitality_spring(ctx): + """Once during your turn, you may search up to 6 Energy and attach them + in any way you like. If you do, your turn ends.""" + if await ctx.ask_yes_no( + "Search your deck for up to 6 Energy cards and attach them to " + "your Pokémon in any way you like?" + ): + await _vitality_spring_search(ctx) + ctx.ends_turn = True + card = PokemonCardDef( guid="49dab7f1-a23f-5747-954d-9305cf47d5df", @@ -22,7 +38,8 @@ card = PokemonCardDef( Ability( title="Vitality Spring", game_text="Once during your turn, you may search your deck for up to 6 Energy cards and attach them to your Pok\u00e9mon in any way you like. Then, shuffle your deck. If you use this Ability, your turn ends.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + effect=vitality_spring, ), Attack( title="Hydro Pump", @@ -30,7 +47,9 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 4}, damage=90, damage_operator="+", - effect=unimplemented, + effect=damage_per( + count_energy("self", energy_type=PokemonTypes.WATER), 30, base=90 + ), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/Blissey_52.py b/spirit/game/scripts/cards/PGO/Blissey_52.py index 64560b9..462376b 100644 --- a/spirit/game/scripts/cards/PGO/Blissey_52.py +++ b/spirit/game/scripts/cards/PGO/Blissey_52.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID + + +async def enriching_egg(ctx): + """Heal all damage from 1 of your Benched Pokemon.""" + bench = [p for p in ctx.my_bench() if p.get_attribute(AttrID.HP, 0) < ctx.max_hp(p)] + if not bench: + return + target = await ctx.choose_pokemon(bench, "Choose a Benched Pokémon to heal") + if target is not None: + await ctx.heal(ctx.max_hp(target) - target.get_attribute(AttrID.HP, 0), target) + card = PokemonCardDef( guid="a1534f88-20e1-5912-88f1-0642734752be", @@ -23,7 +34,7 @@ card = PokemonCardDef( title="Enriching Egg", game_text="Heal all damage from 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=enriching_egg, ), Attack( title="Zen Headbutt", diff --git a/spirit/game/scripts/cards/PGO/Camerupt_14.py b/spirit/game/scripts/cards/PGO/Camerupt_14.py index 8566cea..87d20b6 100644 --- a/spirit/game/scripts/cards/PGO/Camerupt_14.py +++ b/spirit/game/scripts/cards/PGO/Camerupt_14.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import snipe_attack card = PokemonCardDef( guid="d9bf59c3-dd42-583a-9a26-c2dd2eb69d91", @@ -23,7 +24,7 @@ card = PokemonCardDef( title="Split Bomb", game_text="This attack does 50 damage to 2 of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=snipe_attack(50, pool="any", count=2), ), Attack( title="Heat Blast", diff --git a/spirit/game/scripts/cards/PGO/Candela_65.py b/spirit/game/scripts/cards/PGO/Candela_65.py index 51d6199..7e36d4f 100644 --- a/spirit/game/scripts/cards/PGO/Candela_65.py +++ b/spirit/game/scripts/cards/PGO/Candela_65.py @@ -1,5 +1,37 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import SupporterCardDef +from spirit.game.attributes import Rarities, PokemonTypes, AttrID +from spirit.game.card_effects.trainers import is_basic_energy_card + + +async def candela(ctx): + """Draw 2. If you drew any cards, flip a coin; heads attaches a Fire + Energy card from your discard pile to 1 of your Benched Pokemon.""" + drawn = await ctx.draw_cards(2) + if drawn <= 0: + return + heads = (await ctx.flip_coins(1, "Candela"))[0] + if not heads: + return + bench = ctx.my_bench() + energies = [ + c for c in ctx.discard_pile() + if is_basic_energy_card(c) + and PokemonTypes.FIRE.value in (c.get_attribute(AttrID.POKEMON_TYPES) or []) + ] + if not bench or not energies: + return + picks = await ctx.choose_cards( + energies, 1, minimum=1, + prompt="Choose a Fire Energy card from your discard pile.", + ) + if not picks: + return + target = await ctx.choose_pokemon( + bench, "Choose a Benched Pokémon to attach Fire Energy to" + ) + if target is not None: + await ctx.attach_energy(picks[0], target) + card = SupporterCardDef( guid="e4edecca-d6f5-5615-95ff-18f243103caf", @@ -11,5 +43,5 @@ card = SupporterCardDef( collector_number=65, set_code="PGO", rarity=Rarities.Uncommon, - effect=unimplemented + effect=candela ) diff --git a/spirit/game/scripts/cards/PGO/Candela_83.py b/spirit/game/scripts/cards/PGO/Candela_83.py index 0f0df6c..947e3c3 100644 --- a/spirit/game/scripts/cards/PGO/Candela_83.py +++ b/spirit/game/scripts/cards/PGO/Candela_83.py @@ -1,5 +1,37 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import SupporterCardDef +from spirit.game.attributes import Rarities, PokemonTypes, AttrID +from spirit.game.card_effects.trainers import is_basic_energy_card + + +async def candela(ctx): + """Draw 2. If you drew any cards, flip a coin; heads attaches a Fire + Energy card from your discard pile to 1 of your Benched Pokemon.""" + drawn = await ctx.draw_cards(2) + if drawn <= 0: + return + heads = (await ctx.flip_coins(1, "Candela"))[0] + if not heads: + return + bench = ctx.my_bench() + energies = [ + c for c in ctx.discard_pile() + if is_basic_energy_card(c) + and PokemonTypes.FIRE.value in (c.get_attribute(AttrID.POKEMON_TYPES) or []) + ] + if not bench or not energies: + return + picks = await ctx.choose_cards( + energies, 1, minimum=1, + prompt="Choose a Fire Energy card from your discard pile.", + ) + if not picks: + return + target = await ctx.choose_pokemon( + bench, "Choose a Benched Pokémon to attach Fire Energy to" + ) + if target is not None: + await ctx.attach_energy(picks[0], target) + card = SupporterCardDef( guid="71160c61-af09-5d56-aafd-b1886d76a0d4", @@ -11,5 +43,5 @@ card = SupporterCardDef( collector_number=83, set_code="PGO", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=candela ) diff --git a/spirit/game/scripts/cards/PGO/Chansey_51.py b/spirit/game/scripts/cards/PGO/Chansey_51.py index f2766f5..230da47 100644 --- a/spirit/game/scripts/cards/PGO/Chansey_51.py +++ b/spirit/game/scripts/cards/PGO/Chansey_51.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import heal_targets card = PokemonCardDef( guid="8506860a-4fe4-5cf8-ae12-ed16c472b45b", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Delicious Egg", game_text="Heal 30 damage from 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=heal_targets(30, "bench_choice"), ), Attack( title="Gentle Slap", diff --git a/spirit/game/scripts/cards/PGO/Charizard_10.py b/spirit/game/scripts/cards/PGO/Charizard_10.py index fe3b3ed..d20ed84 100644 --- a/spirit/game/scripts/cards/PGO/Charizard_10.py +++ b/spirit/game/scripts/cards/PGO/Charizard_10.py @@ -1,5 +1,26 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import self_energy_discard_attack +from spirit.game.card_effects.pokemon import energy_provides_type +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.session.passives import Passive, active_passives + + +class BurnBrightlyPassive(Passive): + def modify_energy_provided(self, options, energy, holder, board): + if holder is None or energy.get_attribute(AttrID.IS_SPECIAL_ENERGY): + return options + if not energy_provides_type(energy, PokemonTypes.FIRE.value): + return options + if any(len(option) >= 2 for option in options): + return options + active_here = any( + isinstance(p, BurnBrightlyPassive) and c.owning_player_id == holder.owning_player_id + for p, c in active_passives(board) + ) + if not active_here: + return options + return [list(option) * 2 for option in options] + card = PokemonCardDef( guid="0102ad8f-c8f6-5a87-ad03-66c7fe4282b3", @@ -22,14 +43,14 @@ card = PokemonCardDef( Ability( title="Burn Brightly", game_text="Each basic Fire Energy attached to your Pok\u00e9mon provides FireFire Energy. You can't apply more than 1 Burn Brightly Ability at a time.", - effect=unimplemented, + passive=BurnBrightlyPassive(), ), Attack( title="Flare Blitz", game_text="Discard all Fire Energy from this Pok\u00e9mon.", cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 2}, damage=170, - effect=unimplemented, + effect=self_energy_discard_attack(all_energy=True, energy_type=PokemonTypes.FIRE), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/Charmander_8.py b/spirit/game/scripts/cards/PGO/Charmander_8.py index 606db8b..22e96ec 100644 --- a/spirit/game/scripts/cards/PGO/Charmander_8.py +++ b/spirit/game/scripts/cards/PGO/Charmander_8.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import energy_provides_type +from spirit.game.card_effects.support_common import search_attach_energy card = PokemonCardDef( guid="83d1e4c7-0f9e-54ea-abc8-e4abe32cabdd", @@ -23,7 +25,11 @@ card = PokemonCardDef( game_text="Search your deck for a Fire Energy card and attach it to this Pok\u00e9mon. Then, shuffle your deck.", cost={PokemonTypes.FIRE: 1}, damage=10, - effect=unimplemented, + effect=search_attach_energy( + predicate=lambda c: energy_provides_type(c, PokemonTypes.FIRE.value), + count=1, to_self=True, + prompt="Choose a Fire Energy card to attach.", + ), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/Charmeleon_9.py b/spirit/game/scripts/cards/PGO/Charmeleon_9.py index c438998..f8ea238 100644 --- a/spirit/game/scripts/cards/PGO/Charmeleon_9.py +++ b/spirit/game/scripts/cards/PGO/Charmeleon_9.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import self_energy_discard_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -29,7 +30,7 @@ card = PokemonCardDef( game_text="Discard a Fire Energy from this Pok\u00e9mon.", cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 2}, damage=100, - effect=unimplemented, + effect=self_energy_discard_attack(count=1, energy_type=PokemonTypes.FIRE), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/ConkeldurrV_40.py b/spirit/game/scripts/cards/PGO/ConkeldurrV_40.py index c3281ee..0b53700 100644 --- a/spirit/game/scripts/cards/PGO/ConkeldurrV_40.py +++ b/spirit/game/scripts/cards/PGO/ConkeldurrV_40.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions + + +async def counter(ctx): + """20 damage, +that much more if this Pokemon was damaged by an attack during the opponent's last turn.""" + bonus = ctx.damage_taken_last_turn(ctx.attacker) + await ctx.deal_damage(20 + bonus) + card = PokemonCardDef( guid="a636cb92-fb8b-563d-a978-eb976e5123a6", @@ -24,7 +32,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=counter, ), Attack( title="Dynamic Punch", @@ -32,7 +40,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 2}, damage=90, damage_operator="+", - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED, flip=True, heads_bonus_damage=90), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/ConkeldurrV_73.py b/spirit/game/scripts/cards/PGO/ConkeldurrV_73.py index c8cc118..95fa7f9 100644 --- a/spirit/game/scripts/cards/PGO/ConkeldurrV_73.py +++ b/spirit/game/scripts/cards/PGO/ConkeldurrV_73.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions + + +async def counter(ctx): + """20 damage, +that much more if this Pokemon was damaged by an attack during the opponent's last turn.""" + bonus = ctx.damage_taken_last_turn(ctx.attacker) + await ctx.deal_damage(20 + bonus) + card = PokemonCardDef( guid="b840bfb9-657c-5d73-88ad-6fce80654bd6", @@ -24,7 +32,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=counter, ), Attack( title="Dynamic Punch", @@ -32,7 +40,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 2}, damage=90, damage_operator="+", - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED, flip=True, heads_bonus_damage=90), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/ConkeldurrV_74.py b/spirit/game/scripts/cards/PGO/ConkeldurrV_74.py index 4f1fe12..8c4e0bf 100644 --- a/spirit/game/scripts/cards/PGO/ConkeldurrV_74.py +++ b/spirit/game/scripts/cards/PGO/ConkeldurrV_74.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions + + +async def counter(ctx): + """20 damage, +that much more if this Pokemon was damaged by an attack during the opponent's last turn.""" + bonus = ctx.damage_taken_last_turn(ctx.attacker) + await ctx.deal_damage(20 + bonus) + card = PokemonCardDef( guid="a6cded83-22e8-57ab-bae6-f51a1a627c0e", @@ -24,7 +32,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=counter, ), Attack( title="Dynamic Punch", @@ -32,7 +40,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 2}, damage=90, damage_operator="+", - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED, flip=True, heads_bonus_damage=90), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/Ditto_53.py b/spirit/game/scripts/cards/PGO/Ditto_53.py index 16ca20e..844a690 100644 --- a/spirit/game/scripts/cards/PGO/Ditto_53.py +++ b/spirit/game/scripts/cards/PGO/Ditto_53.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import SuddenTransformationPassive card = PokemonCardDef( guid="b37b1ba4-3f8a-555a-bf98-caaccf0e8dfe", @@ -21,7 +22,7 @@ card = PokemonCardDef( Ability( title="Sudden Transformation", game_text="This Pok\u00e9mon can use the attacks of any Basic Pok\u00e9mon in your discard pile, except for Pok\u00e9mon with a Rule Box (Pok\u00e9mon V, Pok\u00e9mon-GX, etc. have Rule Boxes). (You still need the necessary Energy to use each attack.)", - effect=unimplemented, + passive=SuddenTransformationPassive(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/DragoniteVSTAR_50.py b/spirit/game/scripts/cards/PGO/DragoniteVSTAR_50.py index ac4db4a..1840d2e 100644 --- a/spirit/game/scripts/cards/PGO/DragoniteVSTAR_50.py +++ b/spirit/game/scripts/cards/PGO/DragoniteVSTAR_50.py @@ -1,5 +1,24 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import lock_all_attacks +from spirit.game.card_effects.pokemon import energy_provides_type +from spirit.game.card_effects.support_common import look_top_attach_energy + + +def _is_water_or_lightning_energy(card): + return ( + energy_provides_type(card, PokemonTypes.WATER.value) + or energy_provides_type(card, PokemonTypes.LIGHTNING.value) + ) + + +async def giga_impact(ctx): + """250 damage. During your next turn, this Pokemon can't attack.""" + await ctx.deal_damage() + lock_all_attacks(ctx, ctx.attacker) + + +draconic_star = look_top_attach_energy(12, predicate=_is_water_or_lightning_energy) card = PokemonCardDef( guid="58fa5388-ecc3-5215-ae28-39216771ceae", @@ -23,13 +42,14 @@ card = PokemonCardDef( game_text="During your next turn, this Pok\u00e9mon can't attack.", cost={PokemonTypes.WATER: 1, PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 2}, damage=250, - effect=unimplemented, + effect=giga_impact, ), Attack( title="Draconic Star", game_text="Look at the top 12 cards of your deck and attach any number of Water or Lightning Energy cards you find there to your Pok\u00e9mon in any way you like. Shuffle the other cards back into your deck. (You can't use more than 1 VSTAR Power in a game.)", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + vstar=True, + effect=draconic_star, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/DragoniteVSTAR_81.py b/spirit/game/scripts/cards/PGO/DragoniteVSTAR_81.py index 02b46a5..46bb23e 100644 --- a/spirit/game/scripts/cards/PGO/DragoniteVSTAR_81.py +++ b/spirit/game/scripts/cards/PGO/DragoniteVSTAR_81.py @@ -1,5 +1,24 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import lock_all_attacks +from spirit.game.card_effects.pokemon import energy_provides_type +from spirit.game.card_effects.support_common import look_top_attach_energy + + +def _is_water_or_lightning_energy(card): + return ( + energy_provides_type(card, PokemonTypes.WATER.value) + or energy_provides_type(card, PokemonTypes.LIGHTNING.value) + ) + + +async def giga_impact(ctx): + """250 damage. During your next turn, this Pokemon can't attack.""" + await ctx.deal_damage() + lock_all_attacks(ctx, ctx.attacker) + + +draconic_star = look_top_attach_energy(12, predicate=_is_water_or_lightning_energy) card = PokemonCardDef( guid="a56db22e-4b35-56f3-ba94-732e42148400", @@ -23,13 +42,14 @@ card = PokemonCardDef( game_text="During your next turn, this Pok\u00e9mon can't attack.", cost={PokemonTypes.WATER: 1, PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 2}, damage=250, - effect=unimplemented, + effect=giga_impact, ), Attack( title="Draconic Star", game_text="Look at the top 12 cards of your deck and attach any number of Water or Lightning Energy cards you find there to your Pok\u00e9mon in any way you like. Shuffle the other cards back into your deck. (You can't use more than 1 VSTAR Power in a game.)", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + vstar=True, + effect=draconic_star, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/DragoniteV_49.py b/spirit/game/scripts/cards/PGO/DragoniteV_49.py index 9f34cb5..0849e63 100644 --- a/spirit/game/scripts/cards/PGO/DragoniteV_49.py +++ b/spirit/game/scripts/cards/PGO/DragoniteV_49.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import discard_opponent_energy_attack card = PokemonCardDef( guid="f56edb89-68c2-555a-844a-fb0460efae15", @@ -19,10 +20,10 @@ card = PokemonCardDef( abilities=[ Attack( title="Hyper Beam", - game_text="Discard an Energy from your opponent's Active Pok\u00e9mon.", + game_text="Discard an Energy from your opponent's Active Pokémon.", cost={PokemonTypes.WATER: 1, PokemonTypes.LIGHTNING: 1}, damage=60, - effect=unimplemented, + effect=discard_opponent_energy_attack(count=1), ), Attack( title="Buster Tail", @@ -30,4 +31,4 @@ card = PokemonCardDef( damage=160, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/PGO/DragoniteV_76.py b/spirit/game/scripts/cards/PGO/DragoniteV_76.py index 8335254..59415b4 100644 --- a/spirit/game/scripts/cards/PGO/DragoniteV_76.py +++ b/spirit/game/scripts/cards/PGO/DragoniteV_76.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import discard_opponent_energy_attack card = PokemonCardDef( guid="f9dbadd3-d1f8-5518-b84b-4ce04911567c", @@ -19,10 +20,10 @@ card = PokemonCardDef( abilities=[ Attack( title="Hyper Beam", - game_text="Discard an Energy from your opponent's Active Pok\u00e9mon.", + game_text="Discard an Energy from your opponent's Active Pokémon.", cost={PokemonTypes.WATER: 1, PokemonTypes.LIGHTNING: 1}, damage=60, - effect=unimplemented, + effect=discard_opponent_energy_attack(count=1), ), Attack( title="Buster Tail", @@ -30,4 +31,4 @@ card = PokemonCardDef( damage=160, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/PGO/Eevee_54.py b/spirit/game/scripts/cards/PGO/Eevee_54.py index b748fd0..61c5064 100644 --- a/spirit/game/scripts/cards/PGO/Eevee_54.py +++ b/spirit/game/scripts/cards/PGO/Eevee_54.py @@ -1,6 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +import random + +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def whiny_voice(ctx): + """Choose a random card from your opponent's hand. Your opponent reveals + that card and shuffles it into their deck.""" + hand = ctx.hand(ctx.opponent_id) + if not hand: + return + card = random.choice(hand) + await ctx.reveal_cards([card]) + await ctx.shuffle_into_deck([card], ctx.opponent_id) + + card = PokemonCardDef( guid="d61727a9-46c1-5dfc-b296-1b53d72a7352", key="PGO", @@ -22,7 +36,7 @@ card = PokemonCardDef( title="Whiny Voice", game_text="Choose a random card from your opponent's hand. Your opponent reveals that card and shuffles it into their deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=whiny_voice, ), Attack( title="Tackle", diff --git a/spirit/game/scripts/cards/PGO/EggIncubator_66.py b/spirit/game/scripts/cards/PGO/EggIncubator_66.py index 0296de4..b7e685d 100644 --- a/spirit/game/scripts/cards/PGO/EggIncubator_66.py +++ b/spirit/game/scripts/cards/PGO/EggIncubator_66.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.support_common import search_to_bench +from spirit.game.session.effects import is_basic_pokemon + + +async def egg_incubator(ctx): + """Flip a coin. Heads: search a Basic Pokemon onto your Bench, shuffle. + Tails: put this card on the bottom of your deck instead of the discard pile.""" + heads = (await ctx.flip_coins(1, "Egg Incubator"))[0] + if heads: + await search_to_bench( + predicate=is_basic_pokemon, count=1, + prompt="Choose a Basic Pokémon to put onto your Bench.", + )(ctx) + else: + await ctx.put_on_bottom_of_deck(ctx.source) + card = ItemCardDef( guid="10bee995-3c57-5f12-98d3-3f1b45c62180", @@ -11,5 +27,5 @@ card = ItemCardDef( collector_number=66, set_code="PGO", rarity=Rarities.Uncommon, - effect=unimplemented + effect=egg_incubator, ) diff --git a/spirit/game/scripts/cards/PGO/EggIncubator_87.py b/spirit/game/scripts/cards/PGO/EggIncubator_87.py index d3a280b..8ead5e4 100644 --- a/spirit/game/scripts/cards/PGO/EggIncubator_87.py +++ b/spirit/game/scripts/cards/PGO/EggIncubator_87.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.support_common import search_to_bench +from spirit.game.session.effects import is_basic_pokemon + + +async def egg_incubator(ctx): + """Flip a coin. Heads: search a Basic Pokemon onto your Bench, shuffle. + Tails: put this card on the bottom of your deck instead of the discard pile.""" + heads = (await ctx.flip_coins(1, "Egg Incubator"))[0] + if heads: + await search_to_bench( + predicate=is_basic_pokemon, count=1, + prompt="Choose a Basic Pokémon to put onto your Bench.", + )(ctx) + else: + await ctx.put_on_bottom_of_deck(ctx.source) + card = ItemCardDef( guid="440d3bd9-bc70-5512-ba16-a634259fc77d", @@ -11,5 +27,5 @@ card = ItemCardDef( collector_number=87, set_code="PGO", rarity=Rarities.RareSecret, - effect=unimplemented + effect=egg_incubator, ) diff --git a/spirit/game/scripts/cards/PGO/Golisopod_26.py b/spirit/game/scripts/cards/PGO/Golisopod_26.py index bac24c8..0c4369e 100644 --- a/spirit/game/scripts/cards/PGO/Golisopod_26.py +++ b/spirit/game/scripts/cards/PGO/Golisopod_26.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if + + +def _entered_active_this_turn(ctx): + return ctx.entered_active_this_turn(ctx.attacker) + card = PokemonCardDef( guid="c5904577-7950-5bbb-b720-30dc82474c01", @@ -25,7 +31,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_entered_active_this_turn, 90), ), Attack( title="Slash", diff --git a/spirit/game/scripts/cards/PGO/Gyarados_22.py b/spirit/game/scripts/cards/PGO/Gyarados_22.py index c20cfe6..436308f 100644 --- a/spirit/game/scripts/cards/PGO/Gyarados_22.py +++ b/spirit/game/scripts/cards/PGO/Gyarados_22.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import mill_attack + + +async def wreak_havoc(ctx): + """Flip a coin until you get tails. For each heads, discard the top 2 + cards of your opponent's deck.""" + heads = await ctx.flip_until_tails("Wreak Havoc") + if heads: + await ctx.discard_cards(ctx.deck_top(heads * 2, player_id=ctx.opponent_id)) card = PokemonCardDef( guid="d9f36c0f-95d4-5b0b-90c7-f3e460535e53", @@ -23,14 +32,14 @@ card = PokemonCardDef( title="Wreak Havoc", game_text="Flip a coin until you get tails. For each heads, discard the top 2 cards of your opponent's deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=wreak_havoc, ), Attack( title="Wild Splash", game_text="Discard the top 5 cards of your deck.", cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 2}, damage=230, - effect=unimplemented, + effect=mill_attack(5, opponent=False), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/Ivysaur_2.py b/spirit/game/scripts/cards/PGO/Ivysaur_2.py index e60097d..a581f4a 100644 --- a/spirit/game/scripts/cards/PGO/Ivysaur_2.py +++ b/spirit/game/scripts/cards/PGO/Ivysaur_2.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.session.effects import is_pokemon_card card = PokemonCardDef( guid="cc24f629-0c9d-51da-9bbb-0bd69465899f", @@ -23,7 +25,10 @@ card = PokemonCardDef( title="Summoning Aroma", game_text="Search your deck for up to 2 Pok\u00e9mon, reveal them, and put them into your hand. Then, shuffle your deck.", cost={PokemonTypes.GRASS: 1}, - effect=unimplemented, + effect=search_to_hand( + is_pokemon_card, count=2, reveal=True, + prompt="Choose up to 2 Pokémon to put into your hand.", + ), ), Attack( title="Razor Leaf", diff --git a/spirit/game/scripts/cards/PGO/Lapras_23.py b/spirit/game/scripts/cards/PGO/Lapras_23.py index 7fbac39..8fa02df 100644 --- a/spirit/game/scripts/cards/PGO/Lapras_23.py +++ b/spirit/game/scripts/cards/PGO/Lapras_23.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="8a31d277-f608-586a-81b1-4c44d9a63f12", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.WATER: 1}, damage=20, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), Attack( title="Surf", diff --git a/spirit/game/scripts/cards/PGO/Larvitar_37.py b/spirit/game/scripts/cards/PGO/Larvitar_37.py index 5307db4..a59cb75 100644 --- a/spirit/game/scripts/cards/PGO/Larvitar_37.py +++ b/spirit/game/scripts/cards/PGO/Larvitar_37.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus card = PokemonCardDef( guid="8771ac8b-2fbd-51ca-a017-543027ff58b7", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 1}, damage=10, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/Lunatone_34.py b/spirit/game/scripts/cards/PGO/Lunatone_34.py index 332d313..f8cd857 100644 --- a/spirit/game/scripts/cards/PGO/Lunatone_34.py +++ b/spirit/game/scripts/cards/PGO/Lunatone_34.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import read_the_wind +from spirit.game.card_effects.attacks_common import damage_per, count_energy card = PokemonCardDef( guid="81288712-7261-5003-9462-a3e56043a648", @@ -23,7 +25,7 @@ card = PokemonCardDef( title="Cycle Draw", game_text="Discard a card from your hand. If you do, draw 3 cards.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=read_the_wind, ), Attack( title="Moon Kinesis", @@ -31,7 +33,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=30, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("self", energy_type=PokemonTypes.PSYCHIC.value), 30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/LureModule_67.py b/spirit/game/scripts/cards/PGO/LureModule_67.py index 71c4ed1..bf48eee 100644 --- a/spirit/game/scripts/cards/PGO/LureModule_67.py +++ b/spirit/game/scripts/cards/PGO/LureModule_67.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef from spirit.game.attributes import Rarities +from spirit.game.session.effects import is_pokemon_card + + +async def lure_module_effect(ctx): + """Each player reveals the top 3 of their deck; Pokemon found go to hand, + the rest is shuffled back.""" + for pid in (ctx.player_id, ctx.opponent_id): + top = ctx.deck_top(3, player_id=pid) + if not top: + continue + await ctx.reveal_cards(top) + matches = [c for c in top if is_pokemon_card(c)] + if matches: + await ctx.put_in_hand(matches, reveal=False) + await ctx.shuffle_deck(player_id=pid) + card = ItemCardDef( guid="d17d2b10-6400-5d79-9ae6-63504e64447f", @@ -11,5 +27,5 @@ card = ItemCardDef( collector_number=67, set_code="PGO", rarity=Rarities.Uncommon, - effect=unimplemented + effect=lure_module_effect ) diff --git a/spirit/game/scripts/cards/PGO/LureModule_88.py b/spirit/game/scripts/cards/PGO/LureModule_88.py index a4efbfb..745882b 100644 --- a/spirit/game/scripts/cards/PGO/LureModule_88.py +++ b/spirit/game/scripts/cards/PGO/LureModule_88.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef from spirit.game.attributes import Rarities +from spirit.game.session.effects import is_pokemon_card + + +async def lure_module_effect(ctx): + """Each player reveals the top 3 of their deck; Pokemon found go to hand, + the rest is shuffled back.""" + for pid in (ctx.player_id, ctx.opponent_id): + top = ctx.deck_top(3, player_id=pid) + if not top: + continue + await ctx.reveal_cards(top) + matches = [c for c in top if is_pokemon_card(c)] + if matches: + await ctx.put_in_hand(matches, reveal=False) + await ctx.shuffle_deck(player_id=pid) + card = ItemCardDef( guid="132ad4d9-8492-544c-afd9-f8899fd7ac12", @@ -11,5 +27,5 @@ card = ItemCardDef( collector_number=88, set_code="PGO", rarity=Rarities.RareSecret, - effect=unimplemented + effect=lure_module_effect ) diff --git a/spirit/game/scripts/cards/PGO/Magikarp_21.py b/spirit/game/scripts/cards/PGO/Magikarp_21.py index 8df66aa..5d0ea80 100644 --- a/spirit/game/scripts/cards/PGO/Magikarp_21.py +++ b/spirit/game/scripts/cards/PGO/Magikarp_21.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.card_effects.attacks_common import damage_per, count_discard + + +def _is_magikarp(card): + return card.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) == "Magikarp" + + +def _is_magikarp_or_gyarados(card): + return card.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) in ("Magikarp", "Gyarados") card = PokemonCardDef( guid="9c7cea7e-3a5f-51fd-9b32-4edaa5613811", @@ -22,7 +32,7 @@ card = PokemonCardDef( title="Lively Grouping", game_text="Search your deck for any number of Magikarp, reveal them, and put them into your hand. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_to_hand(_is_magikarp, count=60, minimum=0, reveal=True), ), Attack( title="Raging Fin", @@ -30,7 +40,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=10, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_discard("mine", pred=_is_magikarp_or_gyarados), 30, base=10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/MelmetalVMAX_48.py b/spirit/game/scripts/cards/PGO/MelmetalVMAX_48.py index c36b63f..7c3da7a 100644 --- a/spirit/game/scripts/cards/PGO/MelmetalVMAX_48.py +++ b/spirit/game/scripts/cards/PGO/MelmetalVMAX_48.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy + + +def _extra_metal_energy(ctx) -> int: + return max(0, count_energy("self", energy_type=PokemonTypes.METAL)(ctx) - 3) + card = PokemonCardDef( guid="a5db3f2a-bd2a-5f4c-bd92-e5639bb833a5", @@ -26,7 +32,7 @@ card = PokemonCardDef( cost={PokemonTypes.METAL: 3}, damage=160, damage_operator="+", - effect=unimplemented, + effect=damage_per(_extra_metal_energy, 60, base=160, cap=280), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/MelmetalVMAX_80.py b/spirit/game/scripts/cards/PGO/MelmetalVMAX_80.py index 49ab729..bab2610 100644 --- a/spirit/game/scripts/cards/PGO/MelmetalVMAX_80.py +++ b/spirit/game/scripts/cards/PGO/MelmetalVMAX_80.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy + + +def _extra_metal_energy(ctx) -> int: + return max(0, count_energy("self", energy_type=PokemonTypes.METAL)(ctx) - 3) + card = PokemonCardDef( guid="28f9a8c6-31ad-5713-9fe3-6d6a54c5e278", @@ -26,7 +32,7 @@ card = PokemonCardDef( cost={PokemonTypes.METAL: 3}, damage=160, damage_operator="+", - effect=unimplemented, + effect=damage_per(_extra_metal_energy, 60, base=160, cap=280), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/MelmetalV_47.py b/spirit/game/scripts/cards/PGO/MelmetalV_47.py index 9bf236b..b9ce19b 100644 --- a/spirit/game/scripts/cards/PGO/MelmetalV_47.py +++ b/spirit/game/scripts/cards/PGO/MelmetalV_47.py @@ -1,5 +1,23 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.trainers import is_metal_energy_card + + +async def _arm_charge(ctx): + await ctx.deal_damage() + energies = [c for c in ctx.hand() if is_metal_energy_card(c)] + if not energies: + return + if not await ctx.ask_yes_no( + "Attach a Metal Energy card from your hand to this Pokémon?" + ): + return + picked = await ctx.choose_cards( + energies, 1, minimum=1, prompt="Choose a Metal Energy card to attach" + ) + if picked: + await ctx.attach_energy(picked[0], ctx.attacker) + card = PokemonCardDef( guid="b77472f2-c926-5367-b469-8e680bc0e647", @@ -24,7 +42,7 @@ card = PokemonCardDef( game_text="You may attach a Metal Energy card from your hand to this Pok\u00e9mon.", cost={PokemonTypes.METAL: 2}, damage=50, - effect=unimplemented, + effect=_arm_charge, ), Attack( title="Mega Punch", diff --git a/spirit/game/scripts/cards/PGO/MelmetalV_75.py b/spirit/game/scripts/cards/PGO/MelmetalV_75.py index ae9afda..a18c91c 100644 --- a/spirit/game/scripts/cards/PGO/MelmetalV_75.py +++ b/spirit/game/scripts/cards/PGO/MelmetalV_75.py @@ -1,5 +1,23 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.trainers import is_metal_energy_card + + +async def _arm_charge(ctx): + await ctx.deal_damage() + energies = [c for c in ctx.hand() if is_metal_energy_card(c)] + if not energies: + return + if not await ctx.ask_yes_no( + "Attach a Metal Energy card from your hand to this Pokémon?" + ): + return + picked = await ctx.choose_cards( + energies, 1, minimum=1, prompt="Choose a Metal Energy card to attach" + ) + if picked: + await ctx.attach_energy(picked[0], ctx.attacker) + card = PokemonCardDef( guid="415f4d10-3d01-5d2e-a550-fde9f4534c5b", @@ -24,7 +42,7 @@ card = PokemonCardDef( game_text="You may attach a Metal Energy card from your hand to this Pok\u00e9mon.", cost={PokemonTypes.METAL: 2}, damage=50, - effect=unimplemented, + effect=_arm_charge, ), Attack( title="Mega Punch", diff --git a/spirit/game/scripts/cards/PGO/Melmetal_46.py b/spirit/game/scripts/cards/PGO/Melmetal_46.py index 4aa79df..91205f3 100644 --- a/spirit/game/scripts/cards/PGO/Melmetal_46.py +++ b/spirit/game/scripts/cards/PGO/Melmetal_46.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="1eb170c9-255f-5716-af07-873244bb18c4", @@ -31,7 +32,7 @@ card = PokemonCardDef( cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 2}, damage=30, damage_operator="+", - effect=unimplemented, + effect=flip_damage(coins=2, bonus_per_heads=90), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/Meltan_45.py b/spirit/game/scripts/cards/PGO/Meltan_45.py index a647566..39058ea 100644 --- a/spirit/game/scripts/cards/PGO/Meltan_45.py +++ b/spirit/game/scripts/cards/PGO/Meltan_45.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="2112d529-f8b5-5117-948d-fb1c17a49bf7", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.COLORLESS: 2}, damage=20, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/MewtwoVSTAR_31.py b/spirit/game/scripts/cards/PGO/MewtwoVSTAR_31.py index c6522c7..7723794 100644 --- a/spirit/game/scripts/cards/PGO/MewtwoVSTAR_31.py +++ b/spirit/game/scripts/cards/PGO/MewtwoVSTAR_31.py @@ -1,5 +1,27 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, is_pokemon_v from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import energy_provides_type + + +async def psy_purge(ctx): + energies = [ + e for p in ctx.my_pokemon_in_play() for e in ctx.attached_energies(p) + if energy_provides_type(e, PokemonTypes.PSYCHIC.value) + ] + picks = await ctx.choose_cards( + energies, 3, minimum=0, + prompt="Discard up to 3 Psychic Energy from your Pokémon.", + ) + await ctx.discard_cards(picks) + if picks: + await ctx.deal_damage(90 * len(picks)) + + +async def star_raid(ctx): + for pokemon in ctx.opponent_pokemon_in_play(): + if is_pokemon_v(pokemon.archetype_id): + await ctx.deal_damage(120, target=pokemon, apply_modifiers=False) + card = PokemonCardDef( guid="87ec5586-6ef2-5aae-a1bc-c7303bcaa2da", @@ -26,13 +48,14 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=90, damage_operator="x", - effect=unimplemented, + effect=psy_purge, ), Attack( title="Star Raid", game_text="This attack does 120 damage to each of your opponent's Pok\u00e9mon V. This damage isn't affected by Weakness or Resistance. (You can't use more than 1 VSTAR Power in a game.)", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + vstar=True, + effect=star_raid, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/MewtwoVSTAR_79.py b/spirit/game/scripts/cards/PGO/MewtwoVSTAR_79.py index 7f3475f..b2fc8a4 100644 --- a/spirit/game/scripts/cards/PGO/MewtwoVSTAR_79.py +++ b/spirit/game/scripts/cards/PGO/MewtwoVSTAR_79.py @@ -1,5 +1,27 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, is_pokemon_v from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import energy_provides_type + + +async def psy_purge(ctx): + energies = [ + e for p in ctx.my_pokemon_in_play() for e in ctx.attached_energies(p) + if energy_provides_type(e, PokemonTypes.PSYCHIC.value) + ] + picks = await ctx.choose_cards( + energies, 3, minimum=0, + prompt="Discard up to 3 Psychic Energy from your Pokémon.", + ) + await ctx.discard_cards(picks) + if picks: + await ctx.deal_damage(90 * len(picks)) + + +async def star_raid(ctx): + for pokemon in ctx.opponent_pokemon_in_play(): + if is_pokemon_v(pokemon.archetype_id): + await ctx.deal_damage(120, target=pokemon, apply_modifiers=False) + card = PokemonCardDef( guid="761bc299-300a-5c20-92b2-dfccdbf1b307", @@ -26,13 +48,14 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=90, damage_operator="x", - effect=unimplemented, + effect=psy_purge, ), Attack( title="Star Raid", game_text="This attack does 120 damage to each of your opponent's Pok\u00e9mon V. This damage isn't affected by Weakness or Resistance. (You can't use more than 1 VSTAR Power in a game.)", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + vstar=True, + effect=star_raid, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/MewtwoVSTAR_86.py b/spirit/game/scripts/cards/PGO/MewtwoVSTAR_86.py index 10acf4e..947d2af 100644 --- a/spirit/game/scripts/cards/PGO/MewtwoVSTAR_86.py +++ b/spirit/game/scripts/cards/PGO/MewtwoVSTAR_86.py @@ -1,5 +1,27 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, is_pokemon_v from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import energy_provides_type + + +async def psy_purge(ctx): + energies = [ + e for p in ctx.my_pokemon_in_play() for e in ctx.attached_energies(p) + if energy_provides_type(e, PokemonTypes.PSYCHIC.value) + ] + picks = await ctx.choose_cards( + energies, 3, minimum=0, + prompt="Discard up to 3 Psychic Energy from your Pokémon.", + ) + await ctx.discard_cards(picks) + if picks: + await ctx.deal_damage(90 * len(picks)) + + +async def star_raid(ctx): + for pokemon in ctx.opponent_pokemon_in_play(): + if is_pokemon_v(pokemon.archetype_id): + await ctx.deal_damage(120, target=pokemon, apply_modifiers=False) + card = PokemonCardDef( guid="54612974-e4ee-5bff-86c8-fb5a59135b84", @@ -26,13 +48,14 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=90, damage_operator="x", - effect=unimplemented, + effect=psy_purge, ), Attack( title="Star Raid", game_text="This attack does 120 damage to each of your opponent's Pok\u00e9mon V. This damage isn't affected by Weakness or Resistance. (You can't use more than 1 VSTAR Power in a game.)", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + vstar=True, + effect=star_raid, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/MewtwoV_30.py b/spirit/game/scripts/cards/PGO/MewtwoV_30.py index 7f55c6b..2c926c9 100644 --- a/spirit/game/scripts/cards/PGO/MewtwoV_30.py +++ b/spirit/game/scripts/cards/PGO/MewtwoV_30.py @@ -1,6 +1,23 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def _transfer_break(ctx): + await ctx.deal_damage() + bench = ctx.my_bench() + energies = ctx.attached_energies(ctx.attacker) + if not bench or not energies: + return + picked = await ctx.choose_cards(energies, 1, minimum=1, prompt="Choose an Energy to move") + if not picked: + return + target = await ctx.choose_pokemon( + bench, "Choose the Benched Pokémon to move the Energy to" + ) + if target is not None: + await ctx.move_energy(picked[0], target) + + card = PokemonCardDef( guid="09d83eb7-1d28-555c-ba85-a8ad58e6d7e1", key="PGO", @@ -29,7 +46,7 @@ card = PokemonCardDef( game_text="Move an Energy from this Pok\u00e9mon to 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.PSYCHIC: 2, PokemonTypes.COLORLESS: 1}, damage=160, - effect=unimplemented, + effect=_transfer_break, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/MewtwoV_72.py b/spirit/game/scripts/cards/PGO/MewtwoV_72.py index f70b0cc..6eac092 100644 --- a/spirit/game/scripts/cards/PGO/MewtwoV_72.py +++ b/spirit/game/scripts/cards/PGO/MewtwoV_72.py @@ -1,6 +1,23 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def _transfer_break(ctx): + await ctx.deal_damage() + bench = ctx.my_bench() + energies = ctx.attached_energies(ctx.attacker) + if not bench or not energies: + return + picked = await ctx.choose_cards(energies, 1, minimum=1, prompt="Choose an Energy to move") + if not picked: + return + target = await ctx.choose_pokemon( + bench, "Choose the Benched Pokémon to move the Energy to" + ) + if target is not None: + await ctx.move_energy(picked[0], target) + + card = PokemonCardDef( guid="17788c8e-c675-52e8-aef1-79996fb495ed", key="PGO", @@ -29,7 +46,7 @@ card = PokemonCardDef( game_text="Move an Energy from this Pok\u00e9mon to 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.PSYCHIC: 2, PokemonTypes.COLORLESS: 1}, damage=160, - effect=unimplemented, + effect=_transfer_break, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/Moltres_12.py b/spirit/game/scripts/cards/PGO/Moltres_12.py index b2c686f..0714e75 100644 --- a/spirit/game/scripts/cards/PGO/Moltres_12.py +++ b/spirit/game/scripts/cards/PGO/Moltres_12.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, def_for +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_basic_pokemon +from spirit.game.card_effects.passives_common import team_damage_boost_passive + + +def _flare_symbol_attacker(pokemon) -> bool: + if not is_basic_pokemon(pokemon): + return False + types = pokemon.get_attribute(AttrID.POKEMON_TYPES) or [] + if PokemonTypes.FIRE.value not in types: + return False + definition = def_for(pokemon.archetype_id) + name = getattr(definition, "display_name", None) or "" + return name != "Moltres" + card = PokemonCardDef( guid="b3cf08b6-fbf4-5c63-ad13-b353e030ec75", @@ -21,7 +35,7 @@ card = PokemonCardDef( Ability( title="Flare Symbol", game_text="Your Basic Fire Pok\u00e9mon's attacks, except any Moltres, do 10 more damage to your opponent's Active Pok\u00e9mon (before applying Weakness and Resistance).", - effect=unimplemented, + passive=team_damage_boost_passive(10, attacker_pred=_flare_symbol_attacker), ), Attack( title="Fire Wing", diff --git a/spirit/game/scripts/cards/PGO/Natu_32.py b/spirit/game/scripts/cards/PGO/Natu_32.py index 85c691a..dc56c41 100644 --- a/spirit/game/scripts/cards/PGO/Natu_32.py +++ b/spirit/game/scripts/cards/PGO/Natu_32.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import heal_attack card = PokemonCardDef( guid="5b3ac0ad-0387-56bf-8874-885dbe59735d", @@ -23,7 +24,7 @@ card = PokemonCardDef( title="Nap", game_text="Heal 20 damage from this Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=heal_attack(20), ), Attack( title="Peck", diff --git a/spirit/game/scripts/cards/PGO/Numel_13.py b/spirit/game/scripts/cards/PGO/Numel_13.py index 1a1341a..8ac8935 100644 --- a/spirit/game/scripts/cards/PGO/Numel_13.py +++ b/spirit/game/scripts/cards/PGO/Numel_13.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus card = PokemonCardDef( guid="4e6cf096-db9c-5189-a3c9-0d1676c55ca7", @@ -29,7 +30,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=50, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(50), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/Onix_36.py b/spirit/game/scripts/cards/PGO/Onix_36.py index 73d52c2..a5086f1 100644 --- a/spirit/game/scripts/cards/PGO/Onix_36.py +++ b/spirit/game/scripts/cards/PGO/Onix_36.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import condition_attack, damage_per, damage_counters_on card = PokemonCardDef( guid="beff1709-f449-5379-a203-cf1395e94da7", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, the Defending Pok\u00e9mon can't retreat.", cost={PokemonTypes.COLORLESS: 3}, damage=50, - effect=unimplemented, + effect=condition_attack(no_retreat=True), ), Attack( title="Raging Swing", @@ -31,7 +32,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 2}, damage=50, damage_operator="x", - effect=unimplemented, + effect=damage_per(damage_counters_on("self"), 50), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/Pidove_61.py b/spirit/game/scripts/cards/PGO/Pidove_61.py index b616934..bc0095f 100644 --- a/spirit/game/scripts/cards/PGO/Pidove_61.py +++ b/spirit/game/scripts/cards/PGO/Pidove_61.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import debuff_defender_attacks card = PokemonCardDef( guid="c049f814-276b-5712-86e2-e33601c1aaca", @@ -23,7 +24,7 @@ card = PokemonCardDef( title="Growl", game_text="During your opponent's next turn, the Defending Pok\u00e9mon's attacks do 20 less damage (before applying Weakness and Resistance).", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=debuff_defender_attacks(20), ), Attack( title="Flap", diff --git a/spirit/game/scripts/cards/PGO/Pikachu_27.py b/spirit/game/scripts/cards/PGO/Pikachu_27.py index 6f708e0..ba7eb65 100644 --- a/spirit/game/scripts/cards/PGO/Pikachu_27.py +++ b/spirit/game/scripts/cards/PGO/Pikachu_27.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, TrainerType +from spirit.game.card_effects.attacks_common import bonus_if + + +def _played_supporter_this_turn(ctx): + return ctx.played_trainer_this_turn(lambda r: r[2] == TrainerType.SUPPORTER.value) > 0 + card = PokemonCardDef( guid="65db8eef-73af-5ed6-95d9-d3dfc1863e6d", @@ -24,7 +30,7 @@ card = PokemonCardDef( cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 2}, damage=30, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_played_supporter_this_turn, 30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/Pikachu_28.py b/spirit/game/scripts/cards/PGO/Pikachu_28.py index ef6c5b2..f45b978 100644 --- a/spirit/game/scripts/cards/PGO/Pikachu_28.py +++ b/spirit/game/scripts/cards/PGO/Pikachu_28.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import recoil_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 1}, damage=90, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/PokStop_68.py b/spirit/game/scripts/cards/PGO/PokStop_68.py index 5c23e29..6e477c9 100644 --- a/spirit/game/scripts/cards/PGO/PokStop_68.py +++ b/spirit/game/scripts/cards/PGO/PokStop_68.py @@ -1,5 +1,27 @@ -from spirit.game.data_utils import StadiumCardDef, unimplemented +from spirit.game.data_utils import Ability, Activations, StadiumCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import deck_nonempty, is_item_card + + +async def pokestop_effect(ctx): + """That player may discard the top 3 of their deck; any Item cards + discarded this way go into their hand instead.""" + top = ctx.deck_top(3) + if not top: + return + await ctx.discard_cards(top) + items = [c for c in top if is_item_card(c)] + if items: + await ctx.put_in_hand(items, reveal=False) + + +POKESTOP_ABILITY = Ability( + title="PokéStop", + game_text="Once during each player's turn, that player may discard 3 cards from the top of their deck. If a player discarded any Item cards in this way, they put those Item cards into their hand.", + activation=Activations.ONCE_PER_TURN, + effect=pokestop_effect, + condition=lambda board, player_id, stadium: deck_nonempty(board, player_id), +) card = StadiumCardDef( guid="57d9c8fc-1ed5-522a-b7de-93f1d280b715", @@ -11,5 +33,5 @@ card = StadiumCardDef( collector_number=68, set_code="PGO", rarity=Rarities.Uncommon, - effect=unimplemented + ability=POKESTOP_ABILITY, ) diff --git a/spirit/game/scripts/cards/PGO/Pupitar_38.py b/spirit/game/scripts/cards/PGO/Pupitar_38.py index 56001e9..310fc03 100644 --- a/spirit/game/scripts/cards/PGO/Pupitar_38.py +++ b/spirit/game/scripts/cards/PGO/Pupitar_38.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import spread_damage card = PokemonCardDef( guid="3d5b2fb5-e726-5f34-bdd5-0429a30bcf7e", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="This attack also does 20 damage to each Benched Pok\u00e9mon (both yours and your opponent's). (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.COLORLESS: 1}, damage=20, - effect=unimplemented, + effect=spread_damage(20, side="both", also_base=True), ), Attack( title="Tackle", diff --git a/spirit/game/scripts/cards/PGO/RadiantBlastoise_18.py b/spirit/game/scripts/cards/PGO/RadiantBlastoise_18.py index d478610..938af5c 100644 --- a/spirit/game/scripts/cards/PGO/RadiantBlastoise_18.py +++ b/spirit/game/scripts/cards/PGO/RadiantBlastoise_18.py @@ -1,5 +1,41 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.pokemon import is_energy_card + + +def _is_water_energy(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_energy_card(card) and PokemonTypes.WATER.value in types + + +def pump_shot_condition(board, player_id, pokemon): + hand = board.find_player_area(player_id, "hand") + if not hand or not any(_is_water_energy(c) for c in hand.children): + return False + opponent = next((pid for pid in board.player_ids if pid != player_id), None) + if opponent is None: + return False + bench = board.find_player_area(opponent, "bench") + return bool(bench) and len(bench.children) > 0 + + +async def pump_shot(ctx): + """Discard a Water Energy from hand; put 2 damage counters on 1 of your opponent's Benched Pokemon.""" + discarded = await ctx.discard_from_hand( + 1, predicate=_is_water_energy, + prompt="Discard a Water Energy card to use Pump Shot", + ) + if not discarded: + return + bench = ctx.opponent_bench() + if not bench: + return + target = await ctx.choose_pokemon( + bench, "Choose 1 of your opponent's Benched Pokémon" + ) + if target is not None: + await ctx.deal_damage(20, target=target, as_counters=True) + card = PokemonCardDef( guid="76360f77-185a-5c93-9e8e-b9cc93cc38e9", @@ -21,14 +57,16 @@ card = PokemonCardDef( Ability( title="Pump Shot", game_text="You must discard a Water Energy card from your hand in order to use this Ability. Once during your turn, you may put 2 damage counters on 1 of your opponent's Benched Pok\u00e9mon.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=pump_shot_condition, + effect=pump_shot, ), Attack( title="Torrential Cannon", game_text="During your next turn, this Pok\u00e9mon can't use Torrential Cannon.", cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, damage=170, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/RadiantVenusaur_4.py b/spirit/game/scripts/cards/PGO/RadiantVenusaur_4.py index 296b61a..89bf556 100644 --- a/spirit/game/scripts/cards/PGO/RadiantVenusaur_4.py +++ b/spirit/game/scripts/cards/PGO/RadiantVenusaur_4.py @@ -1,5 +1,25 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack + + +def _sunny_bloom_ready(board, player_id, pokemon): + hand = board.find_player_area(player_id, "hand") + deck = board.find_player_area(player_id, "deck") + return bool(deck and deck.children) and hand is not None \ + and len(hand.children) < 4 + + +async def sunny_bloom(ctx): + """End of your turn: you may draw until you have 4 cards in your hand.""" + if not _sunny_bloom_ready(ctx.board, ctx.player_id, ctx.source): + ctx.suppress_announce = True + return + if not await ctx.ask_yes_no("Use Sunny Bloom? Draw cards until you have " + "4 cards in your hand."): + ctx.suppress_announce = True + return + await ctx.draw_until(4) card = PokemonCardDef( guid="2baf28dd-fef2-52c9-826b-1fbf5017d922", @@ -21,14 +41,17 @@ card = PokemonCardDef( Ability( title="Sunny Bloom", game_text="Once at the end of your turn (after your attack), you may use this Ability. Draw cards until you have 4 cards in your hand.", - effect=unimplemented, + trigger=Triggers.END_OF_TURN, + condition=_sunny_bloom_ready, + effect=sunny_bloom, ), Attack( title="Pollen Hazard", game_text="Your opponent's Active Pok\u00e9mon is now Burned, Confused, and Poisoned.", cost={PokemonTypes.GRASS: 2, PokemonTypes.COLORLESS: 1}, damage=90, - effect=unimplemented, + effect=condition_attack(SpecialConditions.BURNED, SpecialConditions.CONFUSED, + SpecialConditions.POISONED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/RareCandy_69.py b/spirit/game/scripts/cards/PGO/RareCandy_69.py index 95c1ca2..1160c6f 100644 --- a/spirit/game/scripts/cards/PGO/RareCandy_69.py +++ b/spirit/game/scripts/cards/PGO/RareCandy_69.py @@ -1,5 +1,50 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import ItemCardDef, evolves_from +from spirit.game.attributes import Rarities, AttrID, PokemonStage +from spirit.game.session.effects import is_basic_pokemon, is_pokemon_card + + +def _stage2_matches(hand_cards, logic_name): + return [ + c for c in hand_cards + if is_pokemon_card(c) + and c.get_attribute(AttrID.STAGE) == PokemonStage.STAGE2.value + and evolves_from(c.archetype_id, logic_name) + ] + + +def _turn_eligible_basics(board, player_id): + turn_state = getattr(board, "turn_state", None) + if turn_state is None: + return [] + return [ + p for p in board.pokemon_in_play(player_id) + if is_basic_pokemon(p) and turn_state.may_evolve_target(p.entity_id) + ] + + +def _rare_candy_condition(board, player_id): + return bool(_turn_eligible_basics(board, player_id)) + + +async def _rare_candy(ctx): + """Choose a Basic Pokemon in play; if you have a Stage 2 in hand that evolves from it, put that card onto it, skipping the Stage 1.""" + candidates = _turn_eligible_basics(ctx.board, ctx.player_id) + if not candidates: + return + target = await ctx.choose_pokemon(candidates, "Choose a Basic Pokémon in play") + if target is None: + return + logic_name = target.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) + stage2_hand = _stage2_matches(ctx.hand(), logic_name) if logic_name else [] + if not stage2_hand: + return + picks = await ctx.choose_cards( + stage2_hand, 1, prompt="Choose a Stage 2 Pokémon to evolve into", + ) + if not picks: + return + await ctx.evolve_pokemon(target, picks[0]) + card = ItemCardDef( guid="32e87eba-71ac-50f5-9e8e-46186f5d339b", @@ -11,5 +56,6 @@ card = ItemCardDef( collector_number=69, set_code="PGO", rarity=Rarities.Uncommon, - effect=unimplemented + effect=_rare_candy, + condition=_rare_candy_condition, ) diff --git a/spirit/game/scripts/cards/PGO/SlakingV_58.py b/spirit/game/scripts/cards/PGO/SlakingV_58.py index a222802..54a82e9 100644 --- a/spirit/game/scripts/cards/PGO/SlakingV_58.py +++ b/spirit/game/scripts/cards/PGO/SlakingV_58.py @@ -1,6 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +def _prizes_remaining(board, player_id): + area = board.find_player_area(player_id, "prizePile") + return len(area.children) if area else 0 + + +def _kinda_lazy_condition(board, player_id, pokemon): + return _prizes_remaining(board, player_id) not in (2, 4, 6) + + card = PokemonCardDef( guid="92236790-2c73-56ed-b409-acf2bb56e82e", key="PGO", @@ -21,12 +31,12 @@ card = PokemonCardDef( Ability( title="Kinda Lazy", game_text="If you have exactly 2, 4, or 6 Prize cards remaining, this Pok\u00e9mon can't attack.", - effect=unimplemented, ), Attack( title="Heavy Impact", cost={PokemonTypes.COLORLESS: 4}, damage=260, + condition=_kinda_lazy_condition, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/SlakingV_77.py b/spirit/game/scripts/cards/PGO/SlakingV_77.py index ede7a4a..bea3251 100644 --- a/spirit/game/scripts/cards/PGO/SlakingV_77.py +++ b/spirit/game/scripts/cards/PGO/SlakingV_77.py @@ -1,6 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +def _prizes_remaining(board, player_id): + area = board.find_player_area(player_id, "prizePile") + return len(area.children) if area else 0 + + +def _kinda_lazy_condition(board, player_id, pokemon): + return _prizes_remaining(board, player_id) not in (2, 4, 6) + + card = PokemonCardDef( guid="2fa3076d-77d0-50cb-afb3-6c3c81cbdc46", key="PGO", @@ -21,12 +31,12 @@ card = PokemonCardDef( Ability( title="Kinda Lazy", game_text="If you have exactly 2, 4, or 6 Prize cards remaining, this Pok\u00e9mon can't attack.", - effect=unimplemented, ), Attack( title="Heavy Impact", cost={PokemonTypes.COLORLESS: 4}, damage=260, + condition=_kinda_lazy_condition, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/Slowbro_20.py b/spirit/game/scripts/cards/PGO/Slowbro_20.py index f61ced7..6e7838f 100644 --- a/spirit/game/scripts/cards/PGO/Slowbro_20.py +++ b/spirit/game/scripts/cards/PGO/Slowbro_20.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack + + +def _opponent_one_prize_left(board, player_id, pokemon): + opponent = next((p for p in board.player_ids if p != player_id), None) + if not opponent: + return False + area = board.find_player_area(opponent, "prizePile") + return bool(area) and len(area.children) == 1 + + +async def twilight_inspiration(ctx): + """Take 2 Prize cards (usable only when the opponent has exactly 1 left).""" + await ctx.take_prizes(2) card = PokemonCardDef( guid="12412fc3-817b-507d-8705-64286986f951", @@ -24,13 +38,14 @@ card = PokemonCardDef( game_text="Both Active Pok\u00e9mon are now Asleep.", cost={PokemonTypes.COLORLESS: 1}, damage=20, - effect=unimplemented, + effect=condition_attack(SpecialConditions.ASLEEP, both_actives=True), ), Attack( title="Twilight Inspiration", game_text="You can use this attack only if your opponent has exactly 1 Prize card remaining. Take 2 Prize cards.", cost={PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + effect=twilight_inspiration, + condition=_opponent_one_prize_left, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/Slowpoke_19.py b/spirit/game/scripts/cards/PGO/Slowpoke_19.py index 6e13f86..5e58e20 100644 --- a/spirit/game/scripts/cards/PGO/Slowpoke_19.py +++ b/spirit/game/scripts/cards/PGO/Slowpoke_19.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import heal_attack, recover_from_discard, requires_discard +from spirit.game.session.effects import is_item_card + +hold_still = heal_attack(30, target="self") + +ideal_fishing_day = recover_from_discard( + predicate=is_item_card, count=1, minimum=1, reveal=False, to="hand", + prompt="Choose an Item card to put into your hand.", +) card = PokemonCardDef( guid="e0fda57c-5575-5f1b-8484-6d4c3260ec86", @@ -22,13 +31,14 @@ card = PokemonCardDef( title="Hold Still", game_text="Heal 30 damage from this Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=hold_still, ), Attack( title="Ideal Fishing Day", game_text="Put an Item card from your discard pile into your hand.", cost={PokemonTypes.WATER: 1}, - effect=unimplemented, + effect=ideal_fishing_day, + condition=requires_discard(is_item_card), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/Snorlax_55.py b/spirit/game/scripts/cards/PGO/Snorlax_55.py index 3324fba..d3750af 100644 --- a/spirit/game/scripts/cards/PGO/Snorlax_55.py +++ b/spirit/game/scripts/cards/PGO/Snorlax_55.py @@ -1,5 +1,9 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.passives_common import ( + no_retreat_passive, is_in_active_spot, opposing_active, +) card = PokemonCardDef( guid="5796a3e5-68f2-5556-aee0-2216f67f5f11", @@ -21,14 +25,16 @@ card = PokemonCardDef( Ability( title="Block", game_text="As long as this Pok\u00e9mon is in the Active Spot, your opponent's Active Pok\u00e9mon can't retreat.", - effect=unimplemented, + passive=no_retreat_passive( + lambda p, c: opposing_active(p, c) and is_in_active_spot(c) + ), ), Attack( title="Collapse", game_text="This Pok\u00e9mon is now Asleep.", cost={PokemonTypes.COLORLESS: 4}, damage=150, - effect=unimplemented, + effect=condition_attack(self_conditions=(SpecialConditions.ASLEEP,)), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/Solrock_39.py b/spirit/game/scripts/cards/PGO/Solrock_39.py index 3d02a58..dd5de58 100644 --- a/spirit/game/scripts/cards/PGO/Solrock_39.py +++ b/spirit/game/scripts/cards/PGO/Solrock_39.py @@ -1,5 +1,44 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID, CardType + +LUNATONE_NAME = "com.direwolfdigital.cake.data.archetypes.pokemon.Lunatone.Name" + + +def _is_psychic_energy_card(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return card.get_attribute(AttrID.CARD_TYPE) == CardType.ENERGY.value \ + and PokemonTypes.PSYCHIC.value in types + + +def _my_lunatones(pokemon_list): + return [p for p in pokemon_list + if p.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) == LUNATONE_NAME] + + +def sun_energy_condition(board, player_id, pokemon): + if not _my_lunatones(board.pokemon_in_play(player_id)): + return False + discard = board.find_player_area(player_id, "discard") + return bool(discard) and any(_is_psychic_energy_card(c) for c in discard.children) + + +async def sun_energy(ctx): + lunatones = _my_lunatones(ctx.my_pokemon_in_play()) + cards = [c for c in ctx.discard_pile() if _is_psychic_energy_card(c)] + if not lunatones or not cards: + return + if not await ctx.ask_yes_no( + "Attach a Psychic Energy card from your discard pile to 1 of your Lunatone?" + ): + return + picks = await ctx.choose_cards(cards, 1, prompt="Choose a Psychic Energy card to attach") + if not picks: + return + target = await ctx.choose_pokemon( + lunatones, "Choose a Lunatone to attach the Energy to" + ) or lunatones[0] + await ctx.attach_energy(picks[0], target) + card = PokemonCardDef( guid="2470d314-35d8-54be-b8c4-3f0f504f0765", @@ -21,7 +60,9 @@ card = PokemonCardDef( Ability( title="Sun Energy", game_text="Once during your turn, you may attach a Psychic Energy card from your discard pile to 1 of your Lunatone.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=sun_energy_condition, + effect=sun_energy, ), Attack( title="Spinning Attack", diff --git a/spirit/game/scripts/cards/PGO/Spark_70.py b/spirit/game/scripts/cards/PGO/Spark_70.py index fa2af26..c46095c 100644 --- a/spirit/game/scripts/cards/PGO/Spark_70.py +++ b/spirit/game/scripts/cards/PGO/Spark_70.py @@ -1,5 +1,38 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import SupporterCardDef +from spirit.game.attributes import Rarities, PokemonTypes, AttrID +from spirit.game.card_effects.trainers import is_basic_energy_card + + +def _is_lightning_energy_card(card): + if not is_basic_energy_card(card): + return False + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return PokemonTypes.LIGHTNING.value in types + + +async def _spark(ctx): + drawn = await ctx.draw_cards(2) + if drawn <= 0: + return + heads = (await ctx.flip_coins(1, "Spark"))[0] + if not heads: + return + bench = ctx.my_bench() + if not bench: + return + energies = [c for c in ctx.discard_pile() if _is_lightning_energy_card(c)] + if not energies: + return + picks = await ctx.choose_cards( + energies, 1, minimum=1, prompt="Choose a Lightning Energy card from your discard pile.", + ) + if not picks: + return + target = await ctx.choose_pokemon(bench, "Choose 1 of your Benched Pokémon") + if target is None: + return + await ctx.attach_energy(picks[0], target) + card = SupporterCardDef( guid="332538c4-6a38-586d-a9f3-d4c321203813", @@ -11,5 +44,5 @@ card = SupporterCardDef( collector_number=70, set_code="PGO", rarity=Rarities.Uncommon, - effect=unimplemented + effect=_spark, ) diff --git a/spirit/game/scripts/cards/PGO/Spark_85.py b/spirit/game/scripts/cards/PGO/Spark_85.py index 38f0130..202e946 100644 --- a/spirit/game/scripts/cards/PGO/Spark_85.py +++ b/spirit/game/scripts/cards/PGO/Spark_85.py @@ -1,5 +1,38 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import SupporterCardDef +from spirit.game.attributes import Rarities, PokemonTypes, AttrID +from spirit.game.card_effects.trainers import is_basic_energy_card + + +def _is_lightning_energy_card(card): + if not is_basic_energy_card(card): + return False + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return PokemonTypes.LIGHTNING.value in types + + +async def _spark(ctx): + drawn = await ctx.draw_cards(2) + if drawn <= 0: + return + heads = (await ctx.flip_coins(1, "Spark"))[0] + if not heads: + return + bench = ctx.my_bench() + if not bench: + return + energies = [c for c in ctx.discard_pile() if _is_lightning_energy_card(c)] + if not energies: + return + picks = await ctx.choose_cards( + energies, 1, minimum=1, prompt="Choose a Lightning Energy card from your discard pile.", + ) + if not picks: + return + target = await ctx.choose_pokemon(bench, "Choose 1 of your Benched Pokémon") + if target is None: + return + await ctx.attach_energy(picks[0], target) + card = SupporterCardDef( guid="9cf50c38-d3d9-557b-8911-1c349f0ead04", @@ -11,5 +44,5 @@ card = SupporterCardDef( collector_number=85, set_code="PGO", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=_spark, ) diff --git a/spirit/game/scripts/cards/PGO/Spinarak_6.py b/spirit/game/scripts/cards/PGO/Spinarak_6.py index 986cb1e..74bdb12 100644 --- a/spirit/game/scripts/cards/PGO/Spinarak_6.py +++ b/spirit/game/scripts/cards/PGO/Spinarak_6.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="1977fb2e-58e3-5d29-9b85-9bd6bcdab63f", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Poisoned.", cost={PokemonTypes.GRASS: 1}, damage=10, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED, flip=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/Steelix_44.py b/spirit/game/scripts/cards/PGO/Steelix_44.py index f597a0d..003cd27 100644 --- a/spirit/game/scripts/cards/PGO/Steelix_44.py +++ b/spirit/game/scripts/cards/PGO/Steelix_44.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack, lock_all_attacks + + +async def iron_buster(ctx): + await ctx.deal_damage() + lock_all_attacks(ctx, ctx.attacker) card = PokemonCardDef( guid="d7b5bd78-2a18-5036-a981-ee7e2501ba2e", @@ -25,14 +31,14 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.COLORLESS: 3}, damage=70, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), Attack( title="Iron Buster", game_text="During your next turn, this Pok\u00e9mon can't attack.", cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 3}, damage=170, - effect=unimplemented, + effect=iron_buster, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/Sylveon_35.py b/spirit/game/scripts/cards/PGO/Sylveon_35.py index 0951982..0e20a0b 100644 --- a/spirit/game/scripts/cards/PGO/Sylveon_35.py +++ b/spirit/game/scripts/cards/PGO/Sylveon_35.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.card_effects.attacks_common import bonus_if, active_is + + +def _is_dragon(p): + return PokemonTypes.DRAGON.value in (p.get_attribute(AttrID.POKEMON_TYPES) or []) + card = PokemonCardDef( guid="952c22cb-ebfb-552c-b4e8-947c865d54e0", @@ -23,7 +30,7 @@ card = PokemonCardDef( title="Souvenir", game_text="Search your deck for up to 2 cards and put them into your hand. Then, shuffle your deck.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=search_to_hand(count=2), ), Attack( title="Wonder Flash", @@ -31,7 +38,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=90, damage_operator="+", - effect=unimplemented, + effect=bonus_if(active_is(_is_dragon), 90, base=90), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/Tranquill_62.py b/spirit/game/scripts/cards/PGO/Tranquill_62.py index 49fde08..c0e6d3d 100644 --- a/spirit/game/scripts/cards/PGO/Tranquill_62.py +++ b/spirit/game/scripts/cards/PGO/Tranquill_62.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus card = PokemonCardDef( guid="4dd644ed-17be-52d4-b5da-66d3922e2fd7", @@ -26,7 +27,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(20), ), Attack( title="Flap", diff --git a/spirit/game/scripts/cards/PGO/Tyranitar_43.py b/spirit/game/scripts/cards/PGO/Tyranitar_43.py index 1145720..2147975 100644 --- a/spirit/game/scripts/cards/PGO/Tyranitar_43.py +++ b/spirit/game/scripts/cards/PGO/Tyranitar_43.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.attacks_common import damage_per, spread_damage + + +def _bench_damage_counters(ctx) -> int: + total = 0 + for pokemon in ctx.my_bench(): + total += max(0, (ctx.max_hp(pokemon) - pokemon.get_attribute(AttrID.HP, 0)) // 10) + return total + card = PokemonCardDef( guid="8a6c5a53-4506-57a5-872d-916c35b27518", @@ -25,14 +34,14 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=10, damage_operator="x", - effect=unimplemented, + effect=damage_per(_bench_damage_counters, 10), ), Attack( title="Earthquake", game_text="This attack also does 20 damage to each of your Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.DARKNESS: 2, PokemonTypes.COLORLESS: 2}, damage=180, - effect=unimplemented, + effect=spread_damage(20, side="mine", also_base=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/Unfezant_63.py b/spirit/game/scripts/cards/PGO/Unfezant_63.py index 60d0cd3..7adc0fa 100644 --- a/spirit/game/scripts/cards/PGO/Unfezant_63.py +++ b/spirit/game/scripts/cards/PGO/Unfezant_63.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="74a1f6f2-765b-5aba-8f49-9a93e61f1165", @@ -31,7 +32,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=70, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=4, per_heads=70), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/Venusaur_3.py b/spirit/game/scripts/cards/PGO/Venusaur_3.py index 63f84fd..99c3f08 100644 --- a/spirit/game/scripts/cards/PGO/Venusaur_3.py +++ b/spirit/game/scripts/cards/PGO/Venusaur_3.py @@ -1,5 +1,33 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions + + +def _loopy_lasso_condition(board, player_id, pokemon=None): + opponent = next((p for p in board.player_ids if p != player_id), None) + bench = board.find_player_area(opponent, "bench") if opponent else None + return bool(bench and bench.children) + + +async def loopy_lasso(ctx): + """Once per turn: you may flip a coin. Heads: switch a Benched opponent + Pokemon into the Active spot, and it becomes Asleep and Poisoned.""" + if not await ctx.ask_yes_no("Flip a coin?"): + return + if not (await ctx.flip_coins(1, "Loopy Lasso"))[0]: + return + bench = ctx.opponent_bench() + if not bench: + return + target = await ctx.choose_pokemon( + bench, "Choose 1 of your opponent's Benched Pokémon" + ) + if target is None: + return + if not await ctx.switch_active(ctx.opponent_id, target): + return + await ctx.apply_special_condition(target, SpecialConditions.ASLEEP) + await ctx.apply_special_condition(target, SpecialConditions.POISONED) + card = PokemonCardDef( guid="48e0438d-10e4-5279-98ed-e89ff00e9f81", @@ -22,7 +50,9 @@ card = PokemonCardDef( Ability( title="Loopy Lasso", game_text="Once during your turn, you may flip a coin. If heads, switch 1 of your opponent's Benched Pok\u00e9mon with their Active Pok\u00e9mon, and the new Active Pok\u00e9mon is now Asleep and Poisoned.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=_loopy_lasso_condition, + effect=loopy_lasso, ), Attack( title="Solar Beam", diff --git a/spirit/game/scripts/cards/PGO/Wartortle_16.py b/spirit/game/scripts/cards/PGO/Wartortle_16.py index e65d6d1..478c394 100644 --- a/spirit/game/scripts/cards/PGO/Wartortle_16.py +++ b/spirit/game/scripts/cards/PGO/Wartortle_16.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy card = PokemonCardDef( guid="58e8c1e1-d081-5d38-9f74-74fd67f7d803", @@ -30,7 +31,9 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=50, damage_operator="+", - effect=unimplemented, + effect=damage_per( + count_energy("self", energy_type=PokemonTypes.WATER), 10, base=50 + ), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/Wimpod_25.py b/spirit/game/scripts/cards/PGO/Wimpod_25.py index 6ab9d4a..94a572f 100644 --- a/spirit/game/scripts/cards/PGO/Wimpod_25.py +++ b/spirit/game/scripts/cards/PGO/Wimpod_25.py @@ -1,5 +1,24 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, is_pokemon_v from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import retreat_free_when +from spirit.game.models.board import PokemonEntity, PlayerEntity + + +def _opponent_has_pokemon_v(pokemon, carrier): + root = carrier + while root.parent is not None: + root = root.parent + for player in root.children: + if not isinstance(player, PlayerEntity): + continue + if player.owning_player_id == carrier.owning_player_id: + continue + for area in player.children: + for card in getattr(area, "children", []): + if isinstance(card, PokemonEntity) and is_pokemon_v(card.archetype_id): + return True + return False + card = PokemonCardDef( guid="b9e39666-3332-5c20-89e8-e460ed95c0b0", @@ -20,8 +39,8 @@ card = PokemonCardDef( abilities=[ Ability( title="Punk Out", - game_text="If your opponent has any Pok\u00e9mon V in play, this Pok\u00e9mon has no Retreat Cost.", - effect=unimplemented, + game_text="If your opponent has any Pokémon V in play, this Pokémon has no Retreat Cost.", + passive=retreat_free_when(_opponent_has_pokemon_v), ), Attack( title="Gnaw", @@ -29,4 +48,4 @@ card = PokemonCardDef( damage=10, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/PGO/Xatu_33.py b/spirit/game/scripts/cards/PGO/Xatu_33.py index 0623bfd..270e8c6 100644 --- a/spirit/game/scripts/cards/PGO/Xatu_33.py +++ b/spirit/game/scripts/cards/PGO/Xatu_33.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, is_pokemon_v +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack + + +async def pinpoint_wave(ctx): + """90 damage to 1 of your opponent's Pokémon V, ignoring Weakness/Resistance.""" + targets = [p for p in ctx.opponent_pokemon_in_play() if is_pokemon_v(p.archetype_id)] + if not targets: + return + target = await ctx.choose_pokemon(targets, "Choose 1 of your opponent's Pokémon V") + if target is not None: + await ctx.deal_damage(90, target=target, apply_modifiers=False) + card = PokemonCardDef( guid="fd9344f2-806e-5221-ac9c-efed91b73386", @@ -24,14 +36,14 @@ card = PokemonCardDef( title="Pinpoint Wave", game_text="This attack does 90 damage to 1 of your opponent's Pok\u00e9mon V. This damage isn't affected by Weakness or Resistance.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=pinpoint_wave, ), Attack( title="Mind Bend", game_text="Your opponent's Active Pok\u00e9mon is now Confused.", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=50, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/PGO/Zapdos_29.py b/spirit/game/scripts/cards/PGO/Zapdos_29.py index 26ea6dd..1d9d806 100644 --- a/spirit/game/scripts/cards/PGO/Zapdos_29.py +++ b/spirit/game/scripts/cards/PGO/Zapdos_29.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.passives_common import team_damage_boost_passive + + +def _lightning_symbol_attacker(p): + if p.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) == "Zapdos": + return False + if p.get_attribute(AttrID.STAGE) != PokemonStage.BASIC.value: + return False + return PokemonTypes.LIGHTNING.value in (p.get_attribute(AttrID.POKEMON_TYPES) or []) + card = PokemonCardDef( guid="c6aa5471-9004-56f0-8f14-8ecad787bf37", @@ -21,7 +31,7 @@ card = PokemonCardDef( Ability( title="Lightning Symbol", game_text="Your Basic Lightning Pok\u00e9mon's attacks, except any Zapdos, do 10 more damage to your opponent's Active Pok\u00e9mon (before applying Weakness and Resistance).", - effect=unimplemented, + passive=team_damage_boost_passive(10, attacker_pred=_lightning_symbol_attacker), ), Attack( title="Electric Ball", diff --git a/spirit/game/scripts/cards/SWSH1/AirBalloon_213.py b/spirit/game/scripts/cards/SWSH1/AirBalloon_213.py index dc65b42..91045fb 100644 --- a/spirit/game/scripts/cards/SWSH1/AirBalloon_213.py +++ b/spirit/game/scripts/cards/SWSH1/AirBalloon_213.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented +from spirit.game.data_utils import PokemonToolCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import AirBalloonPassive card = PokemonToolCardDef( guid="faf92ae5-83b9-5ffb-949b-baf09dbbacbb", @@ -11,5 +12,5 @@ card = PokemonToolCardDef( collector_number=213, set_code="SWSH1", rarity=Rarities.RareSecret, - effect=unimplemented + passive=AirBalloonPassive() ) diff --git a/spirit/game/scripts/cards/SWSH1/BigCharm_158.py b/spirit/game/scripts/cards/SWSH1/BigCharm_158.py index 6e14209..ec6a122 100644 --- a/spirit/game/scripts/cards/SWSH1/BigCharm_158.py +++ b/spirit/game/scripts/cards/SWSH1/BigCharm_158.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented +from spirit.game.data_utils import PokemonToolCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.passives_common import hp_bonus_tool card = PokemonToolCardDef( guid="6cf61098-a412-5aa1-be7f-8eccb52f00b1", @@ -11,5 +12,5 @@ card = PokemonToolCardDef( collector_number=158, set_code="SWSH1", rarity=Rarities.Uncommon, - effect=unimplemented + passive=hp_bonus_tool(30) ) diff --git a/spirit/game/scripts/cards/SWSH1/Bisharp_134.py b/spirit/game/scripts/cards/SWSH1/Bisharp_134.py index 39df209..42199db 100644 --- a/spirit/game/scripts/cards/SWSH1/Bisharp_134.py +++ b/spirit/game/scripts/cards/SWSH1/Bisharp_134.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.attacks_common import damage_per, count_bench + +_is_pawniard = lambda p: p.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) == "Pawniard" card = PokemonCardDef( guid="e39ed81f-f4ec-5489-b7bc-4890749fa45e", @@ -26,7 +29,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 1}, damage=30, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_bench("mine", pred=_is_pawniard), 30, base=0), ), Attack( title="Slicing Blade", diff --git a/spirit/game/scripts/cards/SWSH1/Blipbug_17.py b/spirit/game/scripts/cards/SWSH1/Blipbug_17.py index c53e7f8..291aff8 100644 --- a/spirit/game/scripts/cards/SWSH1/Blipbug_17.py +++ b/spirit/game/scripts/cards/SWSH1/Blipbug_17.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_bench card = PokemonCardDef( guid="82dcba80-d155-5f10-9051-8fefbadaafb6", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Call for Family", game_text="Search your deck for a Basic Pok\u00e9mon and put it onto your Bench. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_to_bench(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Boltund_75.py b/spirit/game/scripts/cards/SWSH1/Boltund_75.py index 16541e1..87b71b0 100644 --- a/spirit/game/scripts/cards/SWSH1/Boltund_75.py +++ b/spirit/game/scripts/cards/SWSH1/Boltund_75.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import ( + condition_attack, bonus_if, defender_is_v, defender_is_gx, +) card = PokemonCardDef( guid="de957e31-726c-5a09-9d74-19f7a364a5d9", @@ -24,7 +27,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, the Defending Pok\u00e9mon can't retreat.", cost={PokemonTypes.COLORLESS: 2}, damage=50, - effect=unimplemented, + effect=condition_attack(no_retreat=True), ), Attack( title="Fighting Fangs", @@ -32,7 +35,7 @@ card = PokemonCardDef( cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 2}, damage=90, damage_operator="+", - effect=unimplemented, + effect=bonus_if(lambda ctx: defender_is_v(ctx) or defender_is_gx(ctx), 90), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Boltund_76.py b/spirit/game/scripts/cards/SWSH1/Boltund_76.py index 233dc24..27dbc20 100644 --- a/spirit/game/scripts/cards/SWSH1/Boltund_76.py +++ b/spirit/game/scripts/cards/SWSH1/Boltund_76.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import lock_all_attacks + + +async def electrodash(ctx): + """160 damage; during your next turn, this Pokémon can't attack at all.""" + await ctx.deal_damage() + lock_all_attacks(ctx, ctx.attacker) + card = PokemonCardDef( guid="6adfbdf3-7cac-58b3-8ccf-205a73355483", @@ -26,10 +34,10 @@ card = PokemonCardDef( ), Attack( title="Electrodash", - game_text="During your next turn, this Pok\u00e9mon can't attack.", + game_text="During your next turn, this Pokémon can't attack.", cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 1}, damage=160, - effect=unimplemented, + effect=electrodash, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH1/CelebiV_1.py b/spirit/game/scripts/cards/SWSH1/CelebiV_1.py index 9692728..f9122ee 100644 --- a/spirit/game/scripts/cards/SWSH1/CelebiV_1.py +++ b/spirit/game/scripts/cards/SWSH1/CelebiV_1.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_pokemon_card +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.card_effects.attacks_common import damage_per, count_bench card = PokemonCardDef( guid="93c4af72-ab2c-566a-871f-9834d9db7e9c", @@ -22,7 +25,10 @@ card = PokemonCardDef( title="Find a Friend", game_text="Search your deck for up to 2 Pok\u00e9mon, reveal them, and put them into your hand. Then, shuffle your deck.", cost={PokemonTypes.GRASS: 1}, - effect=unimplemented, + effect=search_to_hand( + is_pokemon_card, count=2, + prompt="Choose up to 2 Pok\u00e9mon to put into your hand.", + ), ), Attack( title="Line Force", @@ -30,7 +36,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=50, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_bench("mine"), 20, base=50), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Centiskorch_39.py b/spirit/game/scripts/cards/SWSH1/Centiskorch_39.py index baeba77..eaf6ae6 100644 --- a/spirit/game/scripts/cards/SWSH1/Centiskorch_39.py +++ b/spirit/game/scripts/cards/SWSH1/Centiskorch_39.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack, count_energy + + +async def hundred_foot_flames(ctx): + """Discard the top card of your opponent's deck for each Fire Energy + attached to this Pokémon.""" + count = count_energy("self", energy_type=PokemonTypes.FIRE)(ctx) + if count > 0: + await ctx.discard_cards(ctx.deck_top(count, player_id=ctx.opponent_id)) + card = PokemonCardDef( guid="5ab3accb-bf6e-526f-85c9-fd65413f0e64", @@ -23,14 +33,14 @@ card = PokemonCardDef( title="Hundred Foot Flames", game_text="For each Fire Energy attached to this Pok\u00e9mon, discard the top card of your opponent's deck.", cost={PokemonTypes.FIRE: 1}, - effect=unimplemented, + effect=hundred_foot_flames, ), Attack( title="Searing Flame", game_text="Your opponent's Active Pok\u00e9mon is now Burned.", cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 3}, damage=110, - effect=unimplemented, + effect=condition_attack(SpecialConditions.BURNED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Chinchou_68.py b/spirit/game/scripts/cards/SWSH1/Chinchou_68.py index c6c75c6..1723570 100644 --- a/spirit/game/scripts/cards/SWSH1/Chinchou_68.py +++ b/spirit/game/scripts/cards/SWSH1/Chinchou_68.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.support_common import gust_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Luring Glow", game_text="Switch 1 of your opponent's Benched Pok\u00e9mon with their Active Pok\u00e9mon.", cost={PokemonTypes.LIGHTNING: 1}, - effect=unimplemented, + effect=gust_attack(), ), Attack( title="Lightning Ball", diff --git a/spirit/game/scripts/cards/SWSH1/Cinccino_147.py b/spirit/game/scripts/cards/SWSH1/Cinccino_147.py index 0550bdf..f88e429 100644 --- a/spirit/game/scripts/cards/SWSH1/Cinccino_147.py +++ b/spirit/game/scripts/cards/SWSH1/Cinccino_147.py @@ -1,6 +1,39 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.support_common import requires_hand +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def make_do(ctx): + """Discard a card from your hand. Then, you may draw 2 cards.""" + if not await ctx.discard_from_hand(1, prompt="Discard a card for Make Do"): + return + if await ctx.ask_yes_no("Draw 2 cards?"): + await ctx.draw_cards(2) + + +async def energy_assist(ctx): + """40 damage. Attach a basic Energy card from your discard pile to 1 of + your Benched Pokémon.""" + await ctx.deal_damage() + bench = ctx.my_bench() + if not bench: + return + cards = [c for c in ctx.discard_pile() if is_basic_energy_card(c)] + if not cards: + return + picks = await ctx.choose_cards( + cards, 1, minimum=1, + prompt="Choose a basic Energy card from your discard pile to attach.", + ) + if not picks: + return + target = await ctx.choose_pokemon(bench, "Choose 1 of your Benched Pokémon") + if target is None: + return + await ctx.attach_energy(picks[0], target) + + card = PokemonCardDef( guid="7db31971-8780-5b20-a80e-8a6c2a580ba5", key="SWSH1", @@ -22,14 +55,16 @@ card = PokemonCardDef( Ability( title="Make Do", game_text="You must discard a card from your hand in order to use this Ability. Once during your turn, you may draw 2 cards.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=requires_hand(n=1), + effect=make_do, ), Attack( title="Energy Assist", game_text="Attach a basic Energy card from your discard pile to 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, damage=40, - effect=unimplemented, + effect=energy_assist, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Cinderace_34.py b/spirit/game/scripts/cards/SWSH1/Cinderace_34.py index 6acb2db..16d49cd 100644 --- a/spirit/game/scripts/cards/SWSH1/Cinderace_34.py +++ b/spirit/game/scripts/cards/SWSH1/Cinderace_34.py @@ -1,6 +1,24 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import self_energy_discard_attack +from spirit.game.card_effects.pokemon import energy_provides_type +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def libero(ctx): + """Once per turn, on move to Active: you may attach up to 2 Fire Energy from discard.""" + fire_energy = [c for c in ctx.discard_pile() if energy_provides_type(c, PokemonTypes.FIRE.value)] + if not fire_energy: + return + if not await ctx.ask_yes_no("Attach up to 2 Fire Energy cards from your discard pile to Cinderace?"): + return + picks = await ctx.choose_cards( + fire_energy, 2, minimum=1, + prompt="Choose up to 2 Fire Energy cards to attach.", + ) + for card in picks: + await ctx.attach_energy(card, ctx.source) + + card = PokemonCardDef( guid="9a908f80-98fe-5529-aeb7-aa5aaf67fdbe", key="SWSH1", @@ -22,14 +40,15 @@ card = PokemonCardDef( Ability( title="Libero", game_text="Once during your turn, when this Pok\u00e9mon moves from your Bench to the Active Spot, you may attach up to 2 Fire Energy cards from your discard pile to it.", - effect=unimplemented, + trigger=Triggers.ON_MOVE_TO_ACTIVE, + effect=libero, ), Attack( title="Flare Striker", game_text="Discard 2 Energy from this Pok\u00e9mon.", cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=190, - effect=unimplemented, + effect=self_energy_discard_attack(count=2), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Cinderace_35.py b/spirit/game/scripts/cards/SWSH1/Cinderace_35.py index 86f5962..5a84490 100644 --- a/spirit/game/scripts/cards/SWSH1/Cinderace_35.py +++ b/spirit/game/scripts/cards/SWSH1/Cinderace_35.py @@ -1,5 +1,6 @@ +from spirit.game.card_effects.attacks_common import self_energy_discard_attack from spirit.game.card_effects.pokemon import condition_attack -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions card = PokemonCardDef( @@ -32,7 +33,7 @@ card = PokemonCardDef( game_text="Discard all Energy from this Pok\u00e9mon.", cost={PokemonTypes.FIRE: 2}, damage=160, - effect=unimplemented, + effect=self_energy_discard_attack(all_energy=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Cinderace_36.py b/spirit/game/scripts/cards/SWSH1/Cinderace_36.py index 24e5f24..5c2b8eb 100644 --- a/spirit/game/scripts/cards/SWSH1/Cinderace_36.py +++ b/spirit/game/scripts/cards/SWSH1/Cinderace_36.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import attach_from_discard +from spirit.game.card_effects.pokemon import is_energy_card, energy_provides_type + + +def _is_fire_energy(card): + return is_energy_card(card) and energy_provides_type(card, PokemonTypes.FIRE.value) + + +async def bright_flame(ctx): + await ctx.deal_damage() + heads = (await ctx.flip_coins(1, "Bright Flame"))[0] + if not heads: + await ctx.discard_energy_from(ctx.attacker, 2) + card = PokemonCardDef( guid="7ede9024-e3c6-5ba6-8d74-b92b700c48fd", @@ -21,17 +35,17 @@ card = PokemonCardDef( abilities=[ Attack( title="Flame Cloak", - game_text="Attach a Fire Energy card from your discard pile to this Pok\u00e9mon.", + game_text="Attach a Fire Energy card from your discard pile to this Pokémon.", cost={PokemonTypes.FIRE: 1}, damage=40, - effect=unimplemented, + effect=attach_from_discard(predicate=_is_fire_energy, count=1, target="self"), ), Attack( title="Bright Flame", - game_text="Flip a coin. If tails, discard 2 Energy from this Pok\u00e9mon.", + game_text="Flip a coin. If tails, discard 2 Energy from this Pokémon.", cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=160, - effect=unimplemented, + effect=bright_flame, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH1/Claydol_103.py b/spirit/game/scripts/cards/SWSH1/Claydol_103.py index 84959bc..30767ca 100644 --- a/spirit/game/scripts/cards/SWSH1/Claydol_103.py +++ b/spirit/game/scripts/cards/SWSH1/Claydol_103.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack, recoil_attack card = PokemonCardDef( guid="1a30868c-51f0-5922-b1fc-19faec908e7a", @@ -24,14 +25,14 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Confused.", cost={PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED), ), Attack( title="Explosion", game_text="This Pok\u00e9mon also does 120 damage to itself.", cost={PokemonTypes.FIGHTING: 2}, damage=200, - effect=unimplemented, + effect=recoil_attack(120), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Clobbopus_111.py b/spirit/game/scripts/cards/SWSH1/Clobbopus_111.py index 4f7aff1..4705249 100644 --- a/spirit/game/scripts/cards/SWSH1/Clobbopus_111.py +++ b/spirit/game/scripts/cards/SWSH1/Clobbopus_111.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions card = PokemonCardDef( guid="98a1a6a4-4f0e-5dff-9536-252c5cf2b20c", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.FIGHTING: 1}, damage=20, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Cloyster_41.py b/spirit/game/scripts/cards/SWSH1/Cloyster_41.py index 9da9d9b..dae83b7 100644 --- a/spirit/game/scripts/cards/SWSH1/Cloyster_41.py +++ b/spirit/game/scripts/cards/SWSH1/Cloyster_41.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions card = PokemonCardDef( guid="d2eb0dc1-7549-5011-80e7-e293c2501a3f", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), Attack( title="Tidal Wave", diff --git a/spirit/game/scripts/cards/SWSH1/Copperajah_137.py b/spirit/game/scripts/cards/SWSH1/Copperajah_137.py index a61a691..69e27f1 100644 --- a/spirit/game/scripts/cards/SWSH1/Copperajah_137.py +++ b/spirit/game/scripts/cards/SWSH1/Copperajah_137.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, damage_counters_on +from spirit.game.card_effects.support_common import heal_attack card = PokemonCardDef( guid="88bf3243-6420-5365-9449-6de59cd6dbbf", @@ -25,14 +27,16 @@ card = PokemonCardDef( game_text="Heal 30 damage from this Pok\u00e9mon.", cost={PokemonTypes.METAL: 2}, damage=60, - effect=unimplemented, + effect=heal_attack(30), ), Attack( title="Muscular Nose", game_text="If this Pok\u00e9mon has 8 or more damage counters on it, this attack does nothing.", cost={PokemonTypes.METAL: 3}, damage=220, - effect=unimplemented, + effect=bonus_if( + lambda ctx: damage_counters_on("self")(ctx) < 8, 0, else_nothing=True + ), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Corviknight_135.py b/spirit/game/scripts/cards/SWSH1/Corviknight_135.py index d7fa047..59a9edc 100644 --- a/spirit/game/scripts/cards/SWSH1/Corviknight_135.py +++ b/spirit/game/scripts/cards/SWSH1/Corviknight_135.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import apply_protection + + +async def iron_wings(ctx): + await ctx.deal_damage() + energies = ctx.attached_energies(ctx.attacker) + if len(energies) >= 2 and await ctx.ask_yes_no( + "Discard 2 Energy from this Pokémon?" + ): + picks = await ctx.discard_energy_from(ctx.attacker, 2) + if picks: + await apply_protection(ctx, reduce=100) + card = PokemonCardDef( guid="b5520748-df6e-54f8-a8c2-37f749adb4db", @@ -30,7 +43,7 @@ card = PokemonCardDef( game_text="You may discard 2 Energy from this Pok\u00e9mon. If you do, during your opponent's next turn, this Pok\u00e9mon takes 100 less damage from attacks (after applying Weakness and Resistance).", cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 2}, damage=130, - effect=unimplemented, + effect=iron_wings, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Corvisquire_151.py b/spirit/game/scripts/cards/SWSH1/Corvisquire_151.py index 584f56a..f60e4fa 100644 --- a/spirit/game/scripts/cards/SWSH1/Corvisquire_151.py +++ b/spirit/game/scripts/cards/SWSH1/Corvisquire_151.py @@ -1,6 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def pluck(ctx): + """Before doing damage, discard all Pokémon Tools from the opponent's Active.""" + target = ctx.defender + if target is not None and not ctx.effects_blocked(target): + tools = [t for t, p in ctx.tools_in_play() if p is target] + if tools: + await ctx.discard_cards(tools) + await ctx.deal_damage() + + card = PokemonCardDef( guid="75f1df96-2e2a-506d-b0f8-ebb1a8630804", key="SWSH1", @@ -22,10 +33,10 @@ card = PokemonCardDef( abilities=[ Attack( title="Pluck", - game_text="Before doing damage, discard all Pok\u00e9mon Tools from your opponent's Active Pok\u00e9mon.", + game_text="Before doing damage, discard all Pokémon Tools from your opponent's Active Pokémon.", cost={PokemonTypes.COLORLESS: 1}, damage=20, - effect=unimplemented, + effect=pluck, ), Attack( title="Drill Peck", @@ -33,4 +44,4 @@ card = PokemonCardDef( damage=50, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH1/CramorantV_155.py b/spirit/game/scripts/cards/SWSH1/CramorantV_155.py index 2120eda..643470d 100644 --- a/spirit/game/scripts/cards/SWSH1/CramorantV_155.py +++ b/spirit/game/scripts/cards/SWSH1/CramorantV_155.py @@ -1,6 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def spit_shot(ctx): + """Discard all Energy from this Pokémon. 160 damage to 1 of your + opponent's Pokémon (no W/R on a Benched target).""" + await ctx.discard_energy_from(ctx.source, 99) + candidates = ctx.opponent_pokemon_in_play() + if not candidates: + return + target = await ctx.choose_pokemon(candidates, "Choose 1 of your opponent's Pokémon") + if target is None: + return + await ctx.deal_damage(160, target=target) + + card = PokemonCardDef( guid="f0b289fa-4430-59a6-89f3-29d2422edd3a", key="SWSH1", @@ -23,13 +38,13 @@ card = PokemonCardDef( title="Beak Catch", game_text="Search your deck for up to 2 cards and put them into your hand. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_to_hand(count=2, reveal=False, prompt="Choose up to 2 cards."), ), Attack( title="Spit Shot", game_text="Discard all Energy from this Pok\u00e9mon. This attack does 160 damage to 1 of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.COLORLESS: 3}, - effect=unimplemented, + effect=spit_shot, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/CramorantV_198.py b/spirit/game/scripts/cards/SWSH1/CramorantV_198.py index 520f81a..91b9fa9 100644 --- a/spirit/game/scripts/cards/SWSH1/CramorantV_198.py +++ b/spirit/game/scripts/cards/SWSH1/CramorantV_198.py @@ -1,6 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def spit_shot(ctx): + """Discard all Energy from this Pokémon. 160 damage to 1 of your + opponent's Pokémon (no W/R on a Benched target).""" + await ctx.discard_energy_from(ctx.source, 99) + candidates = ctx.opponent_pokemon_in_play() + if not candidates: + return + target = await ctx.choose_pokemon(candidates, "Choose 1 of your opponent's Pokémon") + if target is None: + return + await ctx.deal_damage(160, target=target) + + card = PokemonCardDef( guid="ed9b3c3f-481f-58a4-820d-a5bb1b71d843", key="SWSH1", @@ -23,13 +38,13 @@ card = PokemonCardDef( title="Beak Catch", game_text="Search your deck for up to 2 cards and put them into your hand. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_to_hand(count=2, reveal=False, prompt="Choose up to 2 cards."), ), Attack( title="Spit Shot", game_text="Discard all Energy from this Pok\u00e9mon. This attack does 160 damage to 1 of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.COLORLESS: 3}, - effect=unimplemented, + effect=spit_shot, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Cramorant_62.py b/spirit/game/scripts/cards/SWSH1/Cramorant_62.py index 43432cd..ba7ef91 100644 --- a/spirit/game/scripts/cards/SWSH1/Cramorant_62.py +++ b/spirit/game/scripts/cards/SWSH1/Cramorant_62.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import snipe_attack card = PokemonCardDef( guid="9536776a-61ca-5b21-9c71-7afad05ec445", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Water Arrow", game_text="This attack does 20 damage to 1 of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.WATER: 1}, - effect=unimplemented, + effect=snipe_attack(20, pool="any", count=1), ), Attack( title="Water Gun", diff --git a/spirit/game/scripts/cards/SWSH1/CrushingHammer_159.py b/spirit/game/scripts/cards/SWSH1/CrushingHammer_159.py index 8eb25d1..4400014 100644 --- a/spirit/game/scripts/cards/SWSH1/CrushingHammer_159.py +++ b/spirit/game/scripts/cards/SWSH1/CrushingHammer_159.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import opponent_has_energy_attached + + +async def crushing_hammer(ctx): + """Flip a coin. If heads, discard an Energy from 1 of your opponent's Pokémon.""" + results = await ctx.flip_coins(1, "Crushing Hammer") + if not results or not results[0]: + return + candidates = [p for p in ctx.opponent_pokemon_in_play() if ctx.attached_energies(p)] + if not candidates: + return + target = await ctx.choose_pokemon(candidates, "Choose 1 of your opponent's Pokémon") + if target is None: + return + await ctx.discard_energy_from(target, 1) + card = ItemCardDef( guid="0f0d405a-bc01-5a5b-a5b6-7dec00073533", @@ -11,5 +27,6 @@ card = ItemCardDef( collector_number=159, set_code="SWSH1", rarity=Rarities.Uncommon, - effect=unimplemented + effect=crushing_hammer, + condition=opponent_has_energy_attached ) diff --git a/spirit/game/scripts/cards/SWSH1/Cufant_136.py b/spirit/game/scripts/cards/SWSH1/Cufant_136.py index 96ee2ad..3bf65ad 100644 --- a/spirit/game/scripts/cards/SWSH1/Cufant_136.py +++ b/spirit/game/scripts/cards/SWSH1/Cufant_136.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus card = PokemonCardDef( guid="3c5ab83e-4f17-5be2-a68f-a5442fa0ff3d", @@ -25,7 +26,7 @@ card = PokemonCardDef( cost={PokemonTypes.METAL: 2}, damage=20, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/DhelmiseV_187.py b/spirit/game/scripts/cards/SWSH1/DhelmiseV_187.py index 153deeb..6ba2f84 100644 --- a/spirit/game/scripts/cards/SWSH1/DhelmiseV_187.py +++ b/spirit/game/scripts/cards/SWSH1/DhelmiseV_187.py @@ -1,5 +1,30 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +import json + +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, def_for +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID + + +def _is_grass_archetype(archetype_id): + definition = def_for(archetype_id) + attr = definition.extra_attributes.get(str(AttrID.POKEMON_TYPES.value)) if definition else None + if not attr: + return False + try: + types = json.loads(attr["value"]) + except (TypeError, ValueError): + return False + return PokemonTypes.GRASS.value in types + + +async def anchor_anger(ctx): + """30, +90 if one of your Grass Pokemon was KO'd by an opponent's attack last turn.""" + bonus = 0 + for ko in ctx.session.turn_state.kos_by_attack_last_turn.get(ctx.player_id, []): + if _is_grass_archetype(ko.get("archetype_id")): + bonus = 90 + break + await ctx.deal_damage(30 + bonus) + card = PokemonCardDef( guid="0794674c-c5dc-59ba-86ac-a8b8b556683b", @@ -24,14 +49,14 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1}, damage=30, damage_operator="+", - effect=unimplemented, + effect=anchor_anger, ), Attack( title="Giga Hammer", game_text="During your next turn, this Pok\u00e9mon can't use Giga Hammer.", cost={PokemonTypes.GRASS: 2, PokemonTypes.COLORLESS: 1}, damage=200, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/DhelmiseV_9.py b/spirit/game/scripts/cards/SWSH1/DhelmiseV_9.py index e4b0d18..28065c5 100644 --- a/spirit/game/scripts/cards/SWSH1/DhelmiseV_9.py +++ b/spirit/game/scripts/cards/SWSH1/DhelmiseV_9.py @@ -1,5 +1,30 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +import json + +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, def_for +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID + + +def _is_grass_archetype(archetype_id): + definition = def_for(archetype_id) + attr = definition.extra_attributes.get(str(AttrID.POKEMON_TYPES.value)) if definition else None + if not attr: + return False + try: + types = json.loads(attr["value"]) + except (TypeError, ValueError): + return False + return PokemonTypes.GRASS.value in types + + +async def anchor_anger(ctx): + """30, +90 if one of your Grass Pokemon was KO'd by an opponent's attack last turn.""" + bonus = 0 + for ko in ctx.session.turn_state.kos_by_attack_last_turn.get(ctx.player_id, []): + if _is_grass_archetype(ko.get("archetype_id")): + bonus = 90 + break + await ctx.deal_damage(30 + bonus) + card = PokemonCardDef( guid="e639e595-bb1d-55a4-a0c0-b4e3dc1d153d", @@ -24,14 +49,14 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1}, damage=30, damage_operator="+", - effect=unimplemented, + effect=anchor_anger, ), Attack( title="Giga Hammer", game_text="During your next turn, this Pok\u00e9mon can't use Giga Hammer.", cost={PokemonTypes.GRASS: 2, PokemonTypes.COLORLESS: 1}, damage=200, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Dottler_18.py b/spirit/game/scripts/cards/SWSH1/Dottler_18.py index d6d4d08..60c1628 100644 --- a/spirit/game/scripts/cards/SWSH1/Dottler_18.py +++ b/spirit/game/scripts/cards/SWSH1/Dottler_18.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import protect_next_turn card = PokemonCardDef( guid="a3027d0a-bb55-55f3-a345-5816ada6e17e", @@ -23,7 +24,7 @@ card = PokemonCardDef( title="Reflect", game_text="During your opponent's next turn, this Pok\u00e9mon takes 40 less damage from attacks (after applying Weakness and Resistance).", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=protect_next_turn(reduce=40), ), Attack( title="Ram", diff --git a/spirit/game/scripts/cards/SWSH1/Drampa_149.py b/spirit/game/scripts/cards/SWSH1/Drampa_149.py index 96923a1..4b5b1cc 100644 --- a/spirit/game/scripts/cards/SWSH1/Drampa_149.py +++ b/spirit/game/scripts/cards/SWSH1/Drampa_149.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import mill_attack card = PokemonCardDef( guid="5e54416d-c387-5bf5-90d1-b0d8ab4ec408", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Discard the top 2 cards of your deck.", cost={PokemonTypes.COLORLESS: 2}, damage=80, - effect=unimplemented, + effect=mill_attack(2, opponent=False), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Drapion_122.py b/spirit/game/scripts/cards/SWSH1/Drapion_122.py index defb6c8..7ae5d35 100644 --- a/spirit/game/scripts/cards/SWSH1/Drapion_122.py +++ b/spirit/game/scripts/cards/SWSH1/Drapion_122.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="37b9aee1-cc00-5bcd-aeee-e25b288527bd", @@ -24,14 +25,14 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 2}, damage=80, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), Attack( title="Toxic Strike", game_text="Your opponent's Active Pok\u00e9mon is now Poisoned.", cost={PokemonTypes.DARKNESS: 2, PokemonTypes.COLORLESS: 2}, damage=130, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Drednaw_61.py b/spirit/game/scripts/cards/SWSH1/Drednaw_61.py index a5a7e4e..90df33d 100644 --- a/spirit/game/scripts/cards/SWSH1/Drednaw_61.py +++ b/spirit/game/scripts/cards/SWSH1/Drednaw_61.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.attacks_common import damage_per, condition_attack + + +def _defender_retreat_cost(ctx): + defender = ctx.defender + return defender.get_attribute(AttrID.RETREAT_COST, 0) if defender else 0 + card = PokemonCardDef( guid="c5f3a167-6b62-513b-9fde-d8e4e304c7b2", @@ -25,14 +32,14 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=60, damage_operator="+", - effect=unimplemented, + effect=damage_per(_defender_retreat_cost, 30, base=60), ), Attack( title="Jaw Lock", game_text="During your opponent's next turn, the Defending Pok\u00e9mon can't retreat.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 3}, damage=130, - effect=unimplemented, + effect=condition_attack(no_retreat=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Dubwool_154.py b/spirit/game/scripts/cards/SWSH1/Dubwool_154.py index 7bacaf5..81aab6c 100644 --- a/spirit/game/scripts/cards/SWSH1/Dubwool_154.py +++ b/spirit/game/scripts/cards/SWSH1/Dubwool_154.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack +from spirit.game.card_effects.passives_common import protect_next_turn card = PokemonCardDef( guid="c3fa38ce-33c2-5033-9ffb-07c430cf3a49", @@ -24,14 +26,14 @@ card = PokemonCardDef( game_text="During your opponent's next turn, this Pok\u00e9mon takes 30 less damage from attacks (after applying Weakness and Resistance).", cost={PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=protect_next_turn(reduce=30), ), Attack( title="Double-Edge", game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.COLORLESS: 3}, damage=120, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Durant_8.py b/spirit/game/scripts/cards/SWSH1/Durant_8.py index c58a2cd..37ddbce 100644 --- a/spirit/game/scripts/cards/SWSH1/Durant_8.py +++ b/spirit/game/scripts/cards/SWSH1/Durant_8.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities + + +async def bite_together(ctx): + """30, +60 more if another Durant is on your Bench.""" + name = ctx.attacker.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) + on_bench = any(p.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) == name for p in ctx.my_bench()) + await ctx.deal_damage(90 if on_bench else 30) card = PokemonCardDef( guid="f1844eb9-f5db-5ff6-a7e2-a3bd202dfbcf", @@ -24,7 +31,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=30, damage_operator="+", - effect=unimplemented, + effect=bite_together, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Eldegoss_21.py b/spirit/game/scripts/cards/SWSH1/Eldegoss_21.py index d1e55e8..f3ddf04 100644 --- a/spirit/game/scripts/cards/SWSH1/Eldegoss_21.py +++ b/spirit/game/scripts/cards/SWSH1/Eldegoss_21.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.trainers import is_grass_energy_card +from spirit.game.card_effects.support_common import distribute_energy + + +async def blessing_of_fluff(ctx): + """Search your deck for up to 3 Grass Energy cards and attach them to + your Benched Pokemon in any way you like. Then, shuffle your deck.""" + picks = await ctx.search_deck( + is_grass_energy_card, count=3, minimum=0, + prompt="Choose up to 3 Grass Energy cards to attach to your Benched Pokémon.", + ) + bench = ctx.my_bench() + if picks and bench: + await distribute_energy(ctx, picks, bench) + await ctx.shuffle_deck() + card = PokemonCardDef( guid="9ddf1965-4497-59ac-949c-0fec6ad61a9a", @@ -23,7 +39,7 @@ card = PokemonCardDef( title="Blessing of Fluff", game_text="Search your deck for up to 3 Grass Energy cards and attach them to your Benched Pok\u00e9mon in any way you like. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=blessing_of_fluff, ), Attack( title="Leafage", diff --git a/spirit/game/scripts/cards/SWSH1/EnergyRetrieval_160.py b/spirit/game/scripts/cards/SWSH1/EnergyRetrieval_160.py index 556d013..3e6e742 100644 --- a/spirit/game/scripts/cards/SWSH1/EnergyRetrieval_160.py +++ b/spirit/game/scripts/cards/SWSH1/EnergyRetrieval_160.py @@ -1,4 +1,6 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.card_effects.support_common import recover_from_discard, requires_discard +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.data_utils import ItemCardDef from spirit.game.attributes import Rarities card = ItemCardDef( @@ -11,5 +13,6 @@ card = ItemCardDef( collector_number=160, set_code="SWSH1", rarity=Rarities.Uncommon, - effect=unimplemented + condition=requires_discard(is_basic_energy_card), + effect=recover_from_discard(is_basic_energy_card, count=2, minimum=1, to="hand"), ) diff --git a/spirit/game/scripts/cards/SWSH1/EnergySearch_161.py b/spirit/game/scripts/cards/SWSH1/EnergySearch_161.py index a29d0ab..d6b6186 100644 --- a/spirit/game/scripts/cards/SWSH1/EnergySearch_161.py +++ b/spirit/game/scripts/cards/SWSH1/EnergySearch_161.py @@ -1,4 +1,6 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.data_utils import ItemCardDef from spirit.game.attributes import Rarities card = ItemCardDef( @@ -11,5 +13,6 @@ card = ItemCardDef( collector_number=161, set_code="SWSH1", rarity=Rarities.Uncommon, - effect=unimplemented + effect=search_to_hand(is_basic_energy_card, count=1, minimum=0, reveal=True, + prompt="Choose a basic Energy card to put into your hand."), ) diff --git a/spirit/game/scripts/cards/SWSH1/EnergySwitch_162.py b/spirit/game/scripts/cards/SWSH1/EnergySwitch_162.py index e013cda..5d5f4b2 100644 --- a/spirit/game/scripts/cards/SWSH1/EnergySwitch_162.py +++ b/spirit/game/scripts/cards/SWSH1/EnergySwitch_162.py @@ -1,6 +1,21 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.data_utils import ItemCardDef from spirit.game.attributes import Rarities + +def energy_switch_condition(board, player_id): + pokemon = board.pokemon_in_play(player_id) + if len(pokemon) < 2: + return False + return any(any(is_basic_energy_card(e) for e in p.children) for p in pokemon) + + +async def energy_switch(ctx): + """Move a basic Energy from 1 of your Pokemon to another of your Pokemon.""" + pokemon = ctx.my_pokemon_in_play() + await ctx.move_energy_freely(pokemon, pokemon, predicate=is_basic_energy_card, max_count=1) + + card = ItemCardDef( guid="6f38eb37-416a-53b6-b0c9-948c201994d0", key="SWSH1", @@ -11,5 +26,6 @@ card = ItemCardDef( collector_number=162, set_code="SWSH1", rarity=Rarities.Uncommon, - effect=unimplemented + condition=energy_switch_condition, + effect=energy_switch, ) diff --git a/spirit/game/scripts/cards/SWSH1/Ferrothorn_131.py b/spirit/game/scripts/cards/SWSH1/Ferrothorn_131.py index 4a9d7c9..5b738a9 100644 --- a/spirit/game/scripts/cards/SWSH1/Ferrothorn_131.py +++ b/spirit/game/scripts/cards/SWSH1/Ferrothorn_131.py @@ -1,6 +1,24 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import flip_damage +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def energy_slap(ctx): + await ctx.deal_damage() + bench = ctx.my_bench() + energies = ctx.attached_energies(ctx.attacker) + if not bench or not energies: + return + if not await ctx.ask_yes_no( + "Move all Energy from this Pokémon to 1 of your Benched Pokémon?"): + return + target = await ctx.choose_pokemon( + bench, "Choose a Benched Pokémon to move the Energy to") + if target is None: + return + for energy in list(energies): + await ctx.move_energy(energy, target) + card = PokemonCardDef( guid="76e5f6e1-66a7-54ed-afa5-1b2385487968", key="SWSH1", @@ -26,14 +44,14 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 1}, damage=30, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=3, per_heads=30), ), Attack( title="Energy Slap", game_text="You may move all Energy from this Pok\u00e9mon to 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.METAL: 2, PokemonTypes.COLORLESS: 1}, damage=100, - effect=unimplemented, + effect=energy_slap, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Frosmoth_64.py b/spirit/game/scripts/cards/SWSH1/Frosmoth_64.py index 885027a..4367a53 100644 --- a/spirit/game/scripts/cards/SWSH1/Frosmoth_64.py +++ b/spirit/game/scripts/cards/SWSH1/Frosmoth_64.py @@ -1,5 +1,33 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.session.effects import is_special_energy, is_water_pokemon +from spirit.game.card_effects.pokemon import is_energy_card + + +def _is_water_energy_card(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_energy_card(card) and not is_special_energy(card) and PokemonTypes.WATER.value in types + + +def _ice_dance_condition(board, player_id, pokemon): + hand = board.find_player_area(player_id, "hand") + if not hand or not any(_is_water_energy_card(c) for c in hand.children): + return False + bench = board.find_player_area(player_id, "bench") + return bool(bench) and any(is_water_pokemon(c) for c in bench.children) + + +async def ice_dance(ctx): + """As often as you like: attach a Water Energy card from hand to a Benched Water Pokemon.""" + energies = [c for c in ctx.hand() if _is_water_energy_card(c)] + picked = await ctx.choose_cards(energies, 1, minimum=1, prompt="Choose a Water Energy card to attach") + if not picked: + return + targets = [p for p in ctx.my_bench() if is_water_pokemon(p)] + target = await ctx.choose_pokemon(targets, "Choose the Benched Water Pokémon to attach it to") + if target is not None: + await ctx.attach_energy(picked[0], target) + card = PokemonCardDef( guid="895feaeb-86dc-52e7-a607-55225a6fa0f3", @@ -22,7 +50,9 @@ card = PokemonCardDef( Ability( title="Ice Dance", game_text="As often as you like during your turn, you may attach a Water Energy card from your hand to 1 of your Benched Water Pok\u00e9mon.", - effect=unimplemented, + activation=Activations.UNLIMITED, + condition=_ice_dance_condition, + effect=ice_dance, ), Attack( title="Aurora Beam", diff --git a/spirit/game/scripts/cards/SWSH1/GalarianLinoone_118.py b/spirit/game/scripts/cards/SWSH1/GalarianLinoone_118.py index 0f72528..3bc9ed8 100644 --- a/spirit/game/scripts/cards/SWSH1/GalarianLinoone_118.py +++ b/spirit/game/scripts/cards/SWSH1/GalarianLinoone_118.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.support_common import switch_self_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Switch this Pok\u00e9mon with 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.DARKNESS: 1}, damage=20, - effect=unimplemented, + effect=switch_self_attack(), ), Attack( title="Hammer In", diff --git a/spirit/game/scripts/cards/SWSH1/GalarianMeowth_127.py b/spirit/game/scripts/cards/SWSH1/GalarianMeowth_127.py index 9a0d6e6..a3b1073 100644 --- a/spirit/game/scripts/cards/SWSH1/GalarianMeowth_127.py +++ b/spirit/game/scripts/cards/SWSH1/GalarianMeowth_127.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import boost_own_next_turn card = PokemonCardDef( guid="9979e429-cad5-5a90-af2d-2460c4fb21c3", @@ -23,7 +24,7 @@ card = PokemonCardDef( title="Hone Claws", game_text="During your next turn, this Pok\u00e9mon's Slash attack does 60 more damage (before applying Weakness and Resistance).", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=boost_own_next_turn(60, attack_title="Slash"), ), Attack( title="Slash", diff --git a/spirit/game/scripts/cards/SWSH1/GalarianObstagoon_119.py b/spirit/game/scripts/cards/SWSH1/GalarianObstagoon_119.py index 52ed427..26abe1a 100644 --- a/spirit/game/scripts/cards/SWSH1/GalarianObstagoon_119.py +++ b/spirit/game/scripts/cards/SWSH1/GalarianObstagoon_119.py @@ -1,5 +1,31 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.passives_common import prevent_damage_when + + +async def untamed_shout(ctx): + if not await ctx.ask_yes_no( + "Put 3 damage counters on 1 of your opponent's Pokémon?"): + return + candidates = ctx.opponent_pokemon_in_play() + if not candidates: + return + target = await ctx.choose_pokemon( + candidates, "Choose 1 of your opponent's Pokémon") + if target is not None: + await ctx.deal_damage(30, target=target, as_counters=True) + + +def _attacker_is_basic(calc, carrier) -> bool: + attacker = calc.attacker + return attacker is not None \ + and attacker.get_attribute(AttrID.STAGE) == PokemonStage.BASIC.value + + +async def obstruct(ctx): + await ctx.deal_damage() + ctx.add_passive_through_opponents_turn( + ctx.attacker, prevent_damage_when(_attacker_is_basic)) card = PokemonCardDef( guid="fda319f9-4e2c-5630-bc12-3610a9617aa0", @@ -22,14 +48,15 @@ card = PokemonCardDef( Ability( title="Untamed Shout", game_text="When you play this Pok\u00e9mon from your hand to evolve 1 of your Pok\u00e9mon during your turn, you may put 3 damage counters on 1 of your opponent's Pok\u00e9mon.", - effect=unimplemented, + trigger=Triggers.ON_EVOLVE, + effect=untamed_shout, ), Attack( title="Obstruct", game_text="During your opponent's next turn, prevent all damage done to this Pok\u00e9mon by attacks from Basic Pok\u00e9mon.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 1}, damage=90, - effect=unimplemented, + effect=obstruct, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/GalarianPerrserker_128.py b/spirit/game/scripts/cards/SWSH1/GalarianPerrserker_128.py index ad1f733..4c882a5 100644 --- a/spirit/game/scripts/cards/SWSH1/GalarianPerrserker_128.py +++ b/spirit/game/scripts/cards/SWSH1/GalarianPerrserker_128.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.passives_common import team_damage_boost_passive + + +def _is_metal_pokemon(pokemon) -> bool: + types = pokemon.get_attribute(AttrID.POKEMON_TYPES) or [] + return PokemonTypes.METAL.value in types + card = PokemonCardDef( guid="0871347a-afd9-5bab-8954-8eba8d341e00", @@ -23,7 +30,7 @@ card = PokemonCardDef( Ability( title="Steely Spirit", game_text="Your Metal Pok\u00e9mon's attacks do 20 more damage to your opponent's Active Pok\u00e9mon (before applying Weakness and Resistance).", - effect=unimplemented, + passive=team_damage_boost_passive(20, attacker_pred=_is_metal_pokemon), ), Attack( title="Metal Claw", diff --git a/spirit/game/scripts/cards/SWSH1/GalarianPonyta_81.py b/spirit/game/scripts/cards/SWSH1/GalarianPonyta_81.py index e4ed5f1..2c80c4e 100644 --- a/spirit/game/scripts/cards/SWSH1/GalarianPonyta_81.py +++ b/spirit/game/scripts/cards/SWSH1/GalarianPonyta_81.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="9c37cbc6-f755-5bf8-b252-55099c40509a", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.PSYCHIC: 1}, damage=10, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/GalarianRapidash_82.py b/spirit/game/scripts/cards/SWSH1/GalarianRapidash_82.py index fb35d27..010adb8 100644 --- a/spirit/game/scripts/cards/SWSH1/GalarianRapidash_82.py +++ b/spirit/game/scripts/cards/SWSH1/GalarianRapidash_82.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy +from spirit.game.card_effects.passives_common import condition_immunity_passive card = PokemonCardDef( guid="ce15658e-4699-5663-978e-87cd51033118", @@ -23,7 +25,7 @@ card = PokemonCardDef( Ability( title="Pastel Veil", game_text="Your Pok\u00e9mon recover from all Special Conditions and can't be affected by any Special Conditions.", - effect=unimplemented, + passive=condition_immunity_passive(protects="team"), ), Attack( title="Psychic", @@ -31,7 +33,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=30, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("defender"), 30, base=30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/GalarianStunfisk_132.py b/spirit/game/scripts/cards/SWSH1/GalarianStunfisk_132.py index 0151e73..be9ff4d 100644 --- a/spirit/game/scripts/cards/SWSH1/GalarianStunfisk_132.py +++ b/spirit/game/scripts/cards/SWSH1/GalarianStunfisk_132.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage +from spirit.game.card_effects.pokemon import in_active_spot + + +async def snap_trap(ctx): + """Active Spot only: after being damaged by an attack (even if Knocked + Out), discard an Energy from the Attacking Pokémon.""" + if not in_active_spot(ctx.board, ctx.player_id, ctx.source): + return + attacker = ctx.damaged_by + if attacker is None or ctx.effects_blocked(attacker): + return + await ctx.discard_energy_from(attacker, 1) + card = PokemonCardDef( guid="59f91582-85c7-57c7-9fa5-7bc37ce7e13b", @@ -22,7 +36,8 @@ card = PokemonCardDef( Ability( title="Snap Trap", game_text="If this Pok\u00e9mon is in the Active Spot and is damaged by an opponent's attack (even if it is Knocked Out), discard an Energy from the Attacking Pok\u00e9mon.", - effect=unimplemented, + trigger=Triggers.ON_DAMAGED_BY_ATTACK, + effect=snap_trap, ), Attack( title="Damage Rush", @@ -30,7 +45,7 @@ card = PokemonCardDef( cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 1}, damage=30, damage_operator="+", - effect=unimplemented, + effect=flip_damage(until_tails=True, base=30, per_heads=30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/GalarianZigzagoon_117.py b/spirit/game/scripts/cards/SWSH1/GalarianZigzagoon_117.py index ae2f9cb..d1466ac 100644 --- a/spirit/game/scripts/cards/SWSH1/GalarianZigzagoon_117.py +++ b/spirit/game/scripts/cards/SWSH1/GalarianZigzagoon_117.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_or_nothing + + +async def headbutt_tantrum(ctx): + """On play from hand to Bench: you may put 1 damage counter on 1 of + your opponent's Pokémon.""" + if not await ctx.ask_yes_no( + "Put 1 damage counter on 1 of your opponent's Pokémon?" + ): + return + await ctx.place_damage_counters(1) + card = PokemonCardDef( guid="e3800492-f270-5d13-8cb3-7b695565caea", @@ -21,14 +33,15 @@ card = PokemonCardDef( Ability( title="Headbutt Tantrum", game_text="When you play this Pok\u00e9mon from your hand onto your Bench during your turn, you may put 1 damage counter on 1 of your opponent's Pok\u00e9mon.", - effect=unimplemented, + trigger=Triggers.ON_PLAY, + effect=headbutt_tantrum, ), Attack( title="Surprise Attack", game_text="Flip a coin. If tails, this attack does nothing.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=flip_or_nothing(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Galvantula_71.py b/spirit/game/scripts/cards/SWSH1/Galvantula_71.py index e255799..9a3c4e8 100644 --- a/spirit/game/scripts/cards/SWSH1/Galvantula_71.py +++ b/spirit/game/scripts/cards/SWSH1/Galvantula_71.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="c110aed7-6a10-5d9d-93eb-aa24489ec83c", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 1}, damage=60, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Gastly_83.py b/spirit/game/scripts/cards/SWSH1/Gastly_83.py index 82a4038..45244c3 100644 --- a/spirit/game/scripts/cards/SWSH1/Gastly_83.py +++ b/spirit/game/scripts/cards/SWSH1/Gastly_83.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import remove_self_from_play card = PokemonCardDef( guid="73bc8297-e84e-59b4-8e52-2c817f032d5b", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Put this Pok\u00e9mon and all attached cards into your hand.", cost={PokemonTypes.PSYCHIC: 1}, damage=20, - effect=unimplemented, + effect=remove_self_from_play("hand"), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Gengar_85.py b/spirit/game/scripts/cards/SWSH1/Gengar_85.py index e161191..e0ff4b0 100644 --- a/spirit/game/scripts/cards/SWSH1/Gengar_85.py +++ b/spirit/game/scripts/cards/SWSH1/Gengar_85.py @@ -1,5 +1,42 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions, AttrID +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.session.passives import effective_max_hp + + +def _is_psychic(pokemon): + types = pokemon.get_attribute(AttrID.POKEMON_TYPES) or [] + return PokemonTypes.PSYCHIC.value in types + + +def life_shaker_condition(board, player_id, pokemon): + mine = [p for p in board.pokemon_in_play(player_id) if _is_psychic(p)] + if len(mine) < 2: + return False + return any(p.get_attribute(AttrID.HP, 0) < effective_max_hp(board, p) for p in mine) + + +async def life_shaker(ctx): + """Move 1 damage counter from 1 of your Psychic Pokemon to another.""" + mine = [p for p in ctx.my_pokemon_in_play() if _is_psychic(p)] + damaged = [p for p in mine if p.get_attribute(AttrID.HP, 0) < ctx.max_hp(p)] + if not damaged: + return + source = await ctx.choose_pokemon( + damaged, "Choose 1 of your Psychic Pokémon to move a damage counter from" + ) + if source is None: + return + targets = [p for p in mine if p is not source] + if not targets: + return + dest = await ctx.choose_pokemon( + targets, "Choose 1 of your Psychic Pokémon to move the damage counter to" + ) + if dest is None: + return + await ctx.move_damage_counters(source, dest, max_count=1) + card = PokemonCardDef( guid="f34044bd-297a-51b8-8e60-68b859787747", @@ -23,14 +60,16 @@ card = PokemonCardDef( Ability( title="Life Shaker", game_text="As often as you like during your turn, you may move 1 damage counter from 1 of your Psychic Pok\u00e9mon to another of your Psychic Pok\u00e9mon.", - effect=unimplemented, + activation=Activations.UNLIMITED, + condition=life_shaker_condition, + effect=life_shaker, ), Attack( title="Hypnoblast", game_text="Your opponent's Active Pok\u00e9mon is now Asleep.", cost={PokemonTypes.PSYCHIC: 2, PokemonTypes.COLORLESS: 1}, damage=90, - effect=unimplemented, + effect=condition_attack(SpecialConditions.ASLEEP), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Goldeen_46.py b/spirit/game/scripts/cards/SWSH1/Goldeen_46.py index 11cc872..98f6b0c 100644 --- a/spirit/game/scripts/cards/SWSH1/Goldeen_46.py +++ b/spirit/game/scripts/cards/SWSH1/Goldeen_46.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import draw_attack card = PokemonCardDef( guid="5a22d19c-1c99-5b1a-9a49-b1bd190d0f66", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Collect", game_text="Draw a card.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=draw_attack(1), ), Attack( title="Waterfall", diff --git a/spirit/game/scripts/cards/SWSH1/Gossifleur_20.py b/spirit/game/scripts/cards/SWSH1/Gossifleur_20.py index 6e42569..f30a76c 100644 --- a/spirit/game/scripts/cards/SWSH1/Gossifleur_20.py +++ b/spirit/game/scripts/cards/SWSH1/Gossifleur_20.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_bench card = PokemonCardDef( guid="80c1d2ca-0559-5d32-962e-8679c9c7d324", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Call for Family", game_text="Search your deck for up to 3 Basic Pok\u00e9mon and put them onto your Bench. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_to_bench(count=3), ), Attack( title="Razor Leaf", diff --git a/spirit/game/scripts/cards/SWSH1/Grapploct_113.py b/spirit/game/scripts/cards/SWSH1/Grapploct_113.py index 5b57519..4a6d1be 100644 --- a/spirit/game/scripts/cards/SWSH1/Grapploct_113.py +++ b/spirit/game/scripts/cards/SWSH1/Grapploct_113.py @@ -1,5 +1,39 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.session.passives import Passive + + +class OctolockPassive(Passive): + """Until the carrier (Grapploct) leaves the Active Spot, its locked + target's attacks cost Colorless Colorless more and it can't retreat.""" + + def _locked(self, pokemon, carrier): + if pokemon.entity_id != getattr(carrier, "_octolock_target_id", None): + return False + parent = carrier.parent + return bool(parent) and parent.get_attribute(AttrID.NAME) == "activePokemonArea" + + def blocks_retreat(self, pokemon, carrier): + return self._locked(pokemon, carrier) + + def modify_attack_cost(self, cost, pokemon, carrier, board): + if self._locked(pokemon, carrier): + cost["Colorless"] = cost.get("Colorless", 0) + 2 + return cost + + +async def octolock(ctx): + """Until this Grapploct leaves the Active Spot, the Defending Pokemon's + attacks cost Colorless Colorless more, and it can't retreat. This effect + can't be applied more than once.""" + defender = ctx.defender + if defender is not None and not ctx.effects_blocked(defender): + ctx.attacker._octolock_target_id = defender.entity_id + + +async def tough_swing(ctx): + """130. This attack's damage isn't affected by Resistance.""" + await ctx.deal_damage(ignore_resistance=True) card = PokemonCardDef( guid="7ce5897c-5258-5bc4-9bca-0e58f47b223d", @@ -18,19 +52,20 @@ card = PokemonCardDef( weakness_type=PokemonTypes.PSYCHIC, evolves_from="com.direwolfdigital.cake.data.archetypes.pokemon.Clobbopus.Name", family_id=852, + passive=OctolockPassive(), abilities=[ Attack( title="Octolock", game_text="Until this Grapploct leaves the Active Spot, the Defending Pok\u00e9mon's attacks cost ColorlessColorless more, and the Defending Pok\u00e9mon can't retreat. This effect can't be applied more than once.", cost={PokemonTypes.FIGHTING: 2}, - effect=unimplemented, + effect=octolock, ), Attack( title="Tough Swing", game_text="This attack's damage isn't affected by Resistance.", cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=130, - effect=unimplemented, + effect=tough_swing, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/GreatBall_164.py b/spirit/game/scripts/cards/SWSH1/GreatBall_164.py index 1ec2782..38c9083 100644 --- a/spirit/game/scripts/cards/SWSH1/GreatBall_164.py +++ b/spirit/game/scripts/cards/SWSH1/GreatBall_164.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef from spirit.game.attributes import Rarities +from spirit.game.session.effects import is_pokemon_card + + +async def great_ball(ctx): + """Look at the top 7 cards of your deck. You may reveal a Pokemon you + find there and put it into your hand. Shuffle the other cards back.""" + top = ctx.deck_top(7) + candidates = [c for c in top if is_pokemon_card(c)] + if candidates: + picks = await ctx.choose_cards( + candidates, 1, minimum=0, + prompt="You may put a Pokémon into your hand.", + display_cards=top, + ) + await ctx.put_in_hand(picks, reveal=True) + await ctx.shuffle_deck() + card = ItemCardDef( guid="3bf08773-c039-574b-be6e-6f7d8d2d3c26", @@ -11,5 +28,5 @@ card = ItemCardDef( collector_number=164, set_code="SWSH1", rarity=Rarities.Uncommon, - effect=unimplemented + effect=great_ball ) diff --git a/spirit/game/scripts/cards/SWSH1/Grookey_10.py b/spirit/game/scripts/cards/SWSH1/Grookey_10.py index f94181b..13cfa7c 100644 --- a/spirit/game/scripts/cards/SWSH1/Grookey_10.py +++ b/spirit/game/scripts/cards/SWSH1/Grookey_10.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="b7fce58a-eed8-5d50-91ad-056c9af03b78", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 1}, damage=10, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=3, per_heads=10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Haunter_84.py b/spirit/game/scripts/cards/SWSH1/Haunter_84.py index b9bab2f..efa7777 100644 --- a/spirit/game/scripts/cards/SWSH1/Haunter_84.py +++ b/spirit/game/scripts/cards/SWSH1/Haunter_84.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="bbdfb894-b9f0-5b79-a023-a11f5f838cee", @@ -25,7 +26,7 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Asleep.", cost={PokemonTypes.COLORLESS: 1}, damage=20, - effect=unimplemented, + effect=condition_attack(SpecialConditions.ASLEEP), ), Attack( title="Spooky Shot", diff --git a/spirit/game/scripts/cards/SWSH1/Heatmor_26.py b/spirit/game/scripts/cards/SWSH1/Heatmor_26.py index 68a3810..68615db 100644 --- a/spirit/game/scripts/cards/SWSH1/Heatmor_26.py +++ b/spirit/game/scripts/cards/SWSH1/Heatmor_26.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="cb39cb46-25e0-5341-82b8-fc1af8548ea5", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Burned.", cost={PokemonTypes.FIRE: 1}, damage=10, - effect=unimplemented, + effect=condition_attack(SpecialConditions.BURNED), ), Attack( title="Fire Claws", diff --git a/spirit/game/scripts/cards/SWSH1/Hitmonchan_95.py b/spirit/game/scripts/cards/SWSH1/Hitmonchan_95.py index 8f61bf1..700ea33 100644 --- a/spirit/game/scripts/cards/SWSH1/Hitmonchan_95.py +++ b/spirit/game/scripts/cards/SWSH1/Hitmonchan_95.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, named_in_play card = PokemonCardDef( guid="18ac4b65-9532-5b79-abfb-4122e9a3a7ae", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=bonus_if(named_in_play("Hitmonlee"), 20, base=20), ), Attack( title="Mach Cross", diff --git a/spirit/game/scripts/cards/SWSH1/Hitmonlee_94.py b/spirit/game/scripts/cards/SWSH1/Hitmonlee_94.py index 5eebcc3..3b8a46f 100644 --- a/spirit/game/scripts/cards/SWSH1/Hitmonlee_94.py +++ b/spirit/game/scripts/cards/SWSH1/Hitmonlee_94.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, named_in_play card = PokemonCardDef( guid="5e263812-b1aa-508b-b004-3ea7bd37efdd", @@ -29,7 +30,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=80, damage_operator="+", - effect=unimplemented, + effect=bonus_if(named_in_play("Hitmonchan"), 80, base=80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Hoothoot_143.py b/spirit/game/scripts/cards/SWSH1/Hoothoot_143.py index 87e871b..5494779 100644 --- a/spirit/game/scripts/cards/SWSH1/Hoothoot_143.py +++ b/spirit/game/scripts/cards/SWSH1/Hoothoot_143.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import opponent_switches card = PokemonCardDef( guid="e420a598-5f83-5f20-aa67-26c21d58187a", @@ -23,7 +24,7 @@ card = PokemonCardDef( title="Send Back", game_text="Your opponent switches their Active Pok\u00e9mon with 1 of their Benched Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=opponent_switches, ), Attack( title="Wing Attack", diff --git a/spirit/game/scripts/cards/SWSH1/HyperPotion_166.py b/spirit/game/scripts/cards/SWSH1/HyperPotion_166.py index 83997cf..746aa80 100644 --- a/spirit/game/scripts/cards/SWSH1/HyperPotion_166.py +++ b/spirit/game/scripts/cards/SWSH1/HyperPotion_166.py @@ -1,5 +1,27 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.support_common import requires_in_play +from spirit.game.models.board import BoardState + + +def _has_two_energy(pokemon): + return len(BoardState.attached_energies(pokemon)) >= 2 + + +async def hyper_potion(ctx): + """Heal 120 from 1 of your Pokemon with >=2 Energy attached; if healed, discard 2 Energy from it.""" + candidates = [p for p in ctx.my_pokemon_in_play() if _has_two_energy(p)] + if not candidates: + return + target = await ctx.choose_pokemon( + candidates, "Choose a Pokémon with at least 2 Energy attached" + ) + if target is None: + return + healed = await ctx.heal(120, target) + if healed: + await ctx.discard_energy_from(target, 2, prompt="Discard 2 Energy") + card = ItemCardDef( guid="a50f6b61-4dbe-572f-820e-64500bb93bf9", @@ -11,5 +33,6 @@ card = ItemCardDef( collector_number=166, set_code="SWSH1", rarity=Rarities.Uncommon, - effect=unimplemented + effect=hyper_potion, + condition=requires_in_play(_has_two_energy), ) diff --git a/spirit/game/scripts/cards/SWSH1/IndeedeeV_192.py b/spirit/game/scripts/cards/SWSH1/IndeedeeV_192.py index 2d5ad7e..b9446d1 100644 --- a/spirit/game/scripts/cards/SWSH1/IndeedeeV_192.py +++ b/spirit/game/scripts/cards/SWSH1/IndeedeeV_192.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy + + +async def watch_over(ctx): + """Once during your turn, you may heal 20 damage from your Active Pokémon.""" + if await ctx.ask_yes_no("Heal 20 damage from your Active Pokémon?"): + await ctx.heal(20) + card = PokemonCardDef( guid="e736338d-3411-5b14-961e-3d4bae698613", @@ -22,7 +30,8 @@ card = PokemonCardDef( Ability( title="Watch Over", game_text="Once during your turn, you may heal 20 damage from your Active Pok\u00e9mon.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + effect=watch_over, ), Attack( title="Psychic", @@ -30,7 +39,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 2}, damage=10, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("defender"), 60, base=10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/IndeedeeV_91.py b/spirit/game/scripts/cards/SWSH1/IndeedeeV_91.py index 0adbb24..9f2a843 100644 --- a/spirit/game/scripts/cards/SWSH1/IndeedeeV_91.py +++ b/spirit/game/scripts/cards/SWSH1/IndeedeeV_91.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy + + +async def watch_over(ctx): + """Once during your turn, you may heal 20 damage from your Active Pokémon.""" + if await ctx.ask_yes_no("Heal 20 damage from your Active Pokémon?"): + await ctx.heal(20) + card = PokemonCardDef( guid="d079a361-f311-580b-8efc-62acca8e08af", @@ -22,7 +30,8 @@ card = PokemonCardDef( Ability( title="Watch Over", game_text="Once during your turn, you may heal 20 damage from your Active Pok\u00e9mon.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + effect=watch_over, ), Attack( title="Psychic", @@ -30,7 +39,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 2}, damage=10, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("defender"), 60, base=10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Inteleon_58.py b/spirit/game/scripts/cards/SWSH1/Inteleon_58.py index ba25e33..f2470cc 100644 --- a/spirit/game/scripts/cards/SWSH1/Inteleon_58.py +++ b/spirit/game/scripts/cards/SWSH1/Inteleon_58.py @@ -1,5 +1,6 @@ +from spirit.game.card_effects.attacks_common import snipe_attack from spirit.game.card_effects.pokemon import shady_dealings -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -31,7 +32,7 @@ card = PokemonCardDef( game_text="This attack also does 20 damage to 1 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + effect=snipe_attack(20, also_base=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Inteleon_59.py b/spirit/game/scripts/cards/SWSH1/Inteleon_59.py index 731ac86..35efb31 100644 --- a/spirit/game/scripts/cards/SWSH1/Inteleon_59.py +++ b/spirit/game/scripts/cards/SWSH1/Inteleon_59.py @@ -1,5 +1,33 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import discard_random_from_hand + + +async def silent_shot(ctx): + """Discard a random card from your opponent's hand.""" + await ctx.deal_damage() + await discard_random_from_hand(ctx, player_id=ctx.opponent_id, count=1) + + +async def hydro_snipe(ctx): + """You may put an Energy attached to your opponent's Active Pokémon into their hand.""" + await ctx.deal_damage() + defender = ctx.opponent_active() + if defender is None or ctx.effects_blocked(defender): + return + energies = ctx.attached_energies(defender) + if not energies: + return + if not await ctx.ask_yes_no( + "Put an Energy attached to your opponent's Active Pokémon into their hand?" + ): + return + picks = await ctx.choose_cards( + energies, 1, prompt="Choose an Energy to put into your opponent's hand", + ) + if picks: + await ctx.put_in_hand(picks, reveal=False) + card = PokemonCardDef( guid="69f74ec2-8a1d-59ad-84fd-a9ab666a182e", @@ -24,14 +52,14 @@ card = PokemonCardDef( game_text="Discard a random card from your opponent's hand.", cost={PokemonTypes.COLORLESS: 1}, damage=40, - effect=unimplemented, + effect=silent_shot, ), Attack( title="Hydro Snipe", game_text="You may put an Energy attached to your opponent's Active Pok\u00e9mon into their hand.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 1}, damage=100, - effect=unimplemented, + effect=hydro_snipe, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/KeldeoV_53.py b/spirit/game/scripts/cards/SWSH1/KeldeoV_53.py index f284161..9cf5aff 100644 --- a/spirit/game/scripts/cards/SWSH1/KeldeoV_53.py +++ b/spirit/game/scripts/cards/SWSH1/KeldeoV_53.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy card = PokemonCardDef( guid="b27685d3-902a-5039-8132-7ca8811267d0", @@ -29,7 +30,9 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=50, damage_operator="+", - effect=unimplemented, + effect=damage_per( + count_energy("self", energy_type=PokemonTypes.WATER), 30, base=50 + ), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Kingler_44.py b/spirit/game/scripts/cards/SWSH1/Kingler_44.py index ca16d77..3cae7b9 100644 --- a/spirit/game/scripts/cards/SWSH1/Kingler_44.py +++ b/spirit/game/scripts/cards/SWSH1/Kingler_44.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import bonus_if, has_damage, mill_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Discard the top card of your opponent's deck.", cost={PokemonTypes.WATER: 1}, damage=40, - effect=unimplemented, + effect=mill_attack(1), ), Attack( title="Claw Rend", @@ -32,7 +33,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=90, damage_operator="+", - effect=unimplemented, + effect=bonus_if(has_damage(), 60), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Krabby_42.py b/spirit/game/scripts/cards/SWSH1/Krabby_42.py index a1d4c80..78bcd54 100644 --- a/spirit/game/scripts/cards/SWSH1/Krabby_42.py +++ b/spirit/game/scripts/cards/SWSH1/Krabby_42.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_all_opponents card = PokemonCardDef( guid="7f56a111-9ed5-5b30-9c98-7c64041c1e46", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Aqua Shower", game_text="This attack does 10 damage to each of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.WATER: 1}, - effect=unimplemented, + effect=damage_all_opponents(10), ), Attack( title="Vise Grip", diff --git a/spirit/game/scripts/cards/SWSH1/Krabby_43.py b/spirit/game/scripts/cards/SWSH1/Krabby_43.py index 79bb649..4f37991 100644 --- a/spirit/game/scripts/cards/SWSH1/Krabby_43.py +++ b/spirit/game/scripts/cards/SWSH1/Krabby_43.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_or_nothing card = PokemonCardDef( guid="4a4c9826-7711-580d-a303-693eef871def", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Flip 2 coins. If either of them is tails, this attack does nothing.", cost={PokemonTypes.COLORLESS: 2}, damage=120, - effect=unimplemented, + effect=flip_or_nothing(coins=2), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Lanturn_69.py b/spirit/game/scripts/cards/SWSH1/Lanturn_69.py index 55b85ee..f3364d5 100644 --- a/spirit/game/scripts/cards/SWSH1/Lanturn_69.py +++ b/spirit/game/scripts/cards/SWSH1/Lanturn_69.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.trainers import is_energy_card + + +async def strobe_shock(ctx): + """90 damage. Your opponent reveals their hand; if it holds an Energy + card, their Active Pokemon is now Paralyzed.""" + await ctx.deal_damage() + hand = await ctx.reveal_hand(of_player=ctx.opponent_id) + if any(is_energy_card(c) for c in hand): + await ctx.apply_special_condition(ctx.defender, SpecialConditions.PARALYZED) + card = PokemonCardDef( guid="833a1edb-1f10-5dd4-addc-736fa418350c", @@ -26,10 +37,10 @@ card = PokemonCardDef( ), Attack( title="Strobe Shock", - game_text="Your opponent reveals their hand. If you find any Energy cards there, your opponent's Active Pok\u00e9mon is now Paralyzed.", + game_text="Your opponent reveals their hand. If you find any Energy cards there, your opponent's Active Pokémon is now Paralyzed.", cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 2}, damage=90, - effect=unimplemented, + effect=strobe_shock, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH1/LaprasVMAX_203.py b/spirit/game/scripts/cards/SWSH1/LaprasVMAX_203.py index 8eadbce..a931891 100644 --- a/spirit/game/scripts/cards/SWSH1/LaprasVMAX_203.py +++ b/spirit/game/scripts/cards/SWSH1/LaprasVMAX_203.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy card = PokemonCardDef( guid="534ba9ef-f878-546c-90c2-9fc6659ad1e2", @@ -25,7 +26,9 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=90, damage_operator="+", - effect=unimplemented, + effect=damage_per( + count_energy("self", energy_type=PokemonTypes.WATER), 30, base=90 + ), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/LaprasVMAX_50.py b/spirit/game/scripts/cards/SWSH1/LaprasVMAX_50.py index 3fbafbb..ef70ba7 100644 --- a/spirit/game/scripts/cards/SWSH1/LaprasVMAX_50.py +++ b/spirit/game/scripts/cards/SWSH1/LaprasVMAX_50.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy card = PokemonCardDef( guid="e91cd9dc-d345-5326-868d-93ee8b466e4a", @@ -25,7 +26,9 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=90, damage_operator="+", - effect=unimplemented, + effect=damage_per( + count_energy("self", energy_type=PokemonTypes.WATER), 30, base=90 + ), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/LaprasV_189.py b/spirit/game/scripts/cards/SWSH1/LaprasV_189.py index f2e8a63..61fcdee 100644 --- a/spirit/game/scripts/cards/SWSH1/LaprasV_189.py +++ b/spirit/game/scripts/cards/SWSH1/LaprasV_189.py @@ -1,5 +1,47 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.trainers import is_energy_card + + +def _is_water_energy(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_energy_card(card) and PokemonTypes.WATER.value in types + + +async def body_surf(ctx): + """Attach a Water Energy from hand to this Pokémon; if you do, switch it with a Benched Pokémon.""" + hand_water = [c for c in ctx.hand() if _is_water_energy(c)] + if not hand_water: + return + picks = await ctx.choose_cards( + hand_water, 1, minimum=0, + prompt="Attach a Water Energy card from your hand to this Pokémon?", + ) + if not picks: + return + if not await ctx.attach_energy(picks[0], ctx.source, counts_as_attachment=True): + return + bench = ctx.my_bench() + if not bench: + return + target = await ctx.choose_pokemon( + bench, "Choose a Benched Pokémon to switch with this Pokémon" + ) + if target is not None: + await ctx.switch_active(ctx.player_id, target) + + +async def ocean_loop(ctx): + """210 damage, then put 2 Water Energy attached to this Pokémon into your hand.""" + await ctx.deal_damage() + attached = [c for c in ctx.attached_energies(ctx.source) if _is_water_energy(c)] + if not attached: + return + picks = await ctx.choose_cards( + attached, 2, prompt="Choose 2 Water Energy to put into your hand.", + ) + await ctx.put_in_hand(picks, reveal=False) + card = PokemonCardDef( guid="4c3869cf-bc37-5c8b-ae22-7a5d5ea4e075", @@ -22,14 +64,14 @@ card = PokemonCardDef( title="Body Surf", game_text="Attach a Water Energy card from your hand to this Pok\u00e9mon. If you do, switch it with 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=body_surf, ), Attack( title="Ocean Loop", game_text="Put 2 Water Energy attached to this Pok\u00e9mon into your hand.", cost={PokemonTypes.WATER: 3, PokemonTypes.COLORLESS: 1}, damage=210, - effect=unimplemented, + effect=ocean_loop, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/LaprasV_49.py b/spirit/game/scripts/cards/SWSH1/LaprasV_49.py index 816c266..1b60441 100644 --- a/spirit/game/scripts/cards/SWSH1/LaprasV_49.py +++ b/spirit/game/scripts/cards/SWSH1/LaprasV_49.py @@ -1,5 +1,47 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.trainers import is_energy_card + + +def _is_water_energy(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_energy_card(card) and PokemonTypes.WATER.value in types + + +async def body_surf(ctx): + """Attach a Water Energy from hand to this Pokémon; if you do, switch it with a Benched Pokémon.""" + hand_water = [c for c in ctx.hand() if _is_water_energy(c)] + if not hand_water: + return + picks = await ctx.choose_cards( + hand_water, 1, minimum=0, + prompt="Attach a Water Energy card from your hand to this Pokémon?", + ) + if not picks: + return + if not await ctx.attach_energy(picks[0], ctx.source, counts_as_attachment=True): + return + bench = ctx.my_bench() + if not bench: + return + target = await ctx.choose_pokemon( + bench, "Choose a Benched Pokémon to switch with this Pokémon" + ) + if target is not None: + await ctx.switch_active(ctx.player_id, target) + + +async def ocean_loop(ctx): + """210 damage, then put 2 Water Energy attached to this Pokémon into your hand.""" + await ctx.deal_damage() + attached = [c for c in ctx.attached_energies(ctx.source) if _is_water_energy(c)] + if not attached: + return + picks = await ctx.choose_cards( + attached, 2, prompt="Choose 2 Water Energy to put into your hand.", + ) + await ctx.put_in_hand(picks, reveal=False) + card = PokemonCardDef( guid="d5306e12-72c2-5791-ae88-201d2806c2b8", @@ -22,14 +64,14 @@ card = PokemonCardDef( title="Body Surf", game_text="Attach a Water Energy card from your hand to this Pok\u00e9mon. If you do, switch it with 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=body_surf, ), Attack( title="Ocean Loop", game_text="Put 2 Water Energy attached to this Pok\u00e9mon into your hand.", cost={PokemonTypes.WATER: 3, PokemonTypes.COLORLESS: 1}, damage=210, - effect=unimplemented, + effect=ocean_loop, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Lapras_48.py b/spirit/game/scripts/cards/SWSH1/Lapras_48.py index 9526dfe..ed9f6eb 100644 --- a/spirit/game/scripts/cards/SWSH1/Lapras_48.py +++ b/spirit/game/scripts/cards/SWSH1/Lapras_48.py @@ -1,6 +1,23 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def _aqua_wash(ctx): + await ctx.deal_damage() + active = ctx.opponent_active() + energies = ctx.attached_energies(active) if active is not None else [] + if not energies: + return + if not await ctx.ask_yes_no( + "Put 2 Energy attached to your opponent's Active Pokémon into their hand?" + ): + return + picks = await ctx.choose_cards( + energies, 2, prompt="Choose Energy to put into their hand" + ) + await ctx.put_in_hand(picks, reveal=False) + + card = PokemonCardDef( guid="cba31192-46f4-5951-9a4f-adb183dc5ebd", key="SWSH1", @@ -23,7 +40,7 @@ card = PokemonCardDef( game_text="You may put 2 Energy attached to your opponent's Active Pok\u00e9mon into their hand.", cost={PokemonTypes.WATER: 4}, damage=70, - effect=unimplemented, + effect=_aqua_wash, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/LuckyEgg_167.py b/spirit/game/scripts/cards/SWSH1/LuckyEgg_167.py index 1a128e4..f869eea 100644 --- a/spirit/game/scripts/cards/SWSH1/LuckyEgg_167.py +++ b/spirit/game/scripts/cards/SWSH1/LuckyEgg_167.py @@ -1,6 +1,14 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented +from spirit.game.data_utils import PokemonToolCardDef, Ability, Triggers from spirit.game.attributes import Rarities + +async def lucky_egg_ko_effect(ctx): + """If the holder is Knocked Out by damage from an opponent's attack, draw until 7 in hand.""" + if not ctx.ko_from_attack: + return + await ctx.draw_until(7) + + card = PokemonToolCardDef( guid="7e0ea976-39b2-524d-b66f-5aa6d809eb51", key="SWSH1", @@ -11,5 +19,11 @@ card = PokemonToolCardDef( collector_number=167, set_code="SWSH1", rarity=Rarities.Uncommon, - effect=unimplemented + granted_abilities=[ + Ability( + title="Lucky Egg", + trigger=Triggers.ON_KNOCKED_OUT, + effect=lucky_egg_ko_effect, + ), + ], ) diff --git a/spirit/game/scripts/cards/SWSH1/LumBerry_168.py b/spirit/game/scripts/cards/SWSH1/LumBerry_168.py index f598235..5dc193c 100644 --- a/spirit/game/scripts/cards/SWSH1/LumBerry_168.py +++ b/spirit/game/scripts/cards/SWSH1/LumBerry_168.py @@ -1,5 +1,20 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import PokemonToolCardDef, Ability, Triggers +from spirit.game.attributes import AttrID, Rarities + +_LUM_BERRY_GUID = "fea6a9ac-0258-56e1-8696-2186eb91ea09" + + +async def lum_berry_checkup(ctx): + """End of each turn: if the holder has any Special Condition, cure them + all and discard this card.""" + pokemon = ctx.source + if not (pokemon.get_attribute(AttrID.SPECIAL_CONDITIONS) or []): + return + await ctx.cure_all_conditions(pokemon) + tool = next((c for c in pokemon.children if c.archetype_id == _LUM_BERRY_GUID), None) + if tool is not None: + await ctx.discard_cards([tool]) + card = PokemonToolCardDef( guid="fea6a9ac-0258-56e1-8696-2186eb91ea09", @@ -11,5 +26,11 @@ card = PokemonToolCardDef( collector_number=168, set_code="SWSH1", rarity=Rarities.Uncommon, - effect=unimplemented + granted_abilities=[ + Ability( + title="Lum Berry", + trigger=Triggers.BETWEEN_TURNS, + effect=lum_berry_checkup, + ), + ], ) diff --git a/spirit/game/scripts/cards/SWSH1/Mantine_52.py b/spirit/game/scripts/cards/SWSH1/Mantine_52.py index f6bd4d9..bc45ef6 100644 --- a/spirit/game/scripts/cards/SWSH1/Mantine_52.py +++ b/spirit/game/scripts/cards/SWSH1/Mantine_52.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.card_effects.pokemon import energy_provides_type card = PokemonCardDef( guid="62adf16b-d007-52df-b559-e5bb9a831219", @@ -22,7 +24,11 @@ card = PokemonCardDef( title="Water Reserve", game_text="Search your deck for up to 3 Water Energy cards, reveal them, and put them into your hand. Then, shuffle your deck.", cost={PokemonTypes.WATER: 1}, - effect=unimplemented, + effect=search_to_hand( + lambda c: energy_provides_type(c, PokemonTypes.WATER.value), + count=3, minimum=0, + prompt="Choose up to 3 Water Energy cards to put into your hand.", + ), ), Attack( title="Wave Splash", diff --git a/spirit/game/scripts/cards/SWSH1/Maractus_7.py b/spirit/game/scripts/cards/SWSH1/Maractus_7.py index 8a508fb..a15f49a 100644 --- a/spirit/game/scripts/cards/SWSH1/Maractus_7.py +++ b/spirit/game/scripts/cards/SWSH1/Maractus_7.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage, count_energy card = PokemonCardDef( guid="91548e48-5444-54ea-b2d2-d7a6eb2229a3", @@ -29,7 +30,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=60, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins_from=count_energy("self"), per_heads=60), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Mawile_129.py b/spirit/game/scripts/cards/SWSH1/Mawile_129.py index 93f8956..e72fac9 100644 --- a/spirit/game/scripts/cards/SWSH1/Mawile_129.py +++ b/spirit/game/scripts/cards/SWSH1/Mawile_129.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.card_effects.attacks_common import discard_opponent_energy_attack +from spirit.game.session.effects import is_pokemon_card card = PokemonCardDef( guid="4f0d77cc-19f5-52c9-85b9-f5c914f150f1", @@ -23,14 +26,17 @@ card = PokemonCardDef( title="Find a Friend", game_text="Search your deck for a Pok\u00e9mon, reveal it, and put it into your hand. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_to_hand( + is_pokemon_card, count=1, minimum=0, + prompt="Choose a Pok\u00e9mon to put into your hand.", + ), ), Attack( title="Crunch", game_text="Discard an Energy from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 1}, damage=40, - effect=unimplemented, + effect=discard_opponent_energy_attack(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/MetalSaucer_170.py b/spirit/game/scripts/cards/SWSH1/MetalSaucer_170.py index 58257a5..1d2df55 100644 --- a/spirit/game/scripts/cards/SWSH1/MetalSaucer_170.py +++ b/spirit/game/scripts/cards/SWSH1/MetalSaucer_170.py @@ -1,5 +1,40 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import ItemCardDef +from spirit.game.attributes import AttrID, PokemonTypes, Rarities +from spirit.game.card_effects.trainers import is_metal_energy_card +from spirit.game.session.effects import is_pokemon_card + + +def _is_metal_pokemon(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_pokemon_card(card) and PokemonTypes.METAL.value in types + + +def _metal_saucer_condition(board, player_id): + discard = board.find_player_area(player_id, "discard") + if not discard or not any(is_metal_energy_card(c) for c in discard.children): + return False + return any(_is_metal_pokemon(p) for p in board.pokemon_in_play(player_id) + if p is not board.active_pokemon(player_id)) + + +async def metal_saucer(ctx): + """Attach a Metal Energy card from your discard pile to 1 of your + Benched Metal Pokemon.""" + energies = [c for c in ctx.discard_pile() if is_metal_energy_card(c)] + if not energies: + return + bench = [p for p in ctx.my_bench() if _is_metal_pokemon(p)] + if not bench: + return + picks = await ctx.choose_cards( + energies, 1, minimum=1, prompt="Choose a Metal Energy card to attach" + ) + if not picks: + return + target = await ctx.choose_pokemon(bench, "Choose a Benched Metal Pokémon") + if target is not None: + await ctx.attach_energy(picks[0], target) + card = ItemCardDef( guid="7748ae8e-9cb3-54a2-a49f-07bd2e1558da", @@ -11,5 +46,6 @@ card = ItemCardDef( collector_number=170, set_code="SWSH1", rarity=Rarities.Uncommon, - effect=unimplemented + effect=metal_saucer, + condition=_metal_saucer_condition, ) diff --git a/spirit/game/scripts/cards/SWSH1/MetalSaucer_214.py b/spirit/game/scripts/cards/SWSH1/MetalSaucer_214.py index e376759..2331c6f 100644 --- a/spirit/game/scripts/cards/SWSH1/MetalSaucer_214.py +++ b/spirit/game/scripts/cards/SWSH1/MetalSaucer_214.py @@ -1,5 +1,40 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import ItemCardDef +from spirit.game.attributes import AttrID, PokemonTypes, Rarities +from spirit.game.card_effects.trainers import is_metal_energy_card +from spirit.game.session.effects import is_pokemon_card + + +def _is_metal_pokemon(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_pokemon_card(card) and PokemonTypes.METAL.value in types + + +def _metal_saucer_condition(board, player_id): + discard = board.find_player_area(player_id, "discard") + if not discard or not any(is_metal_energy_card(c) for c in discard.children): + return False + return any(_is_metal_pokemon(p) for p in board.pokemon_in_play(player_id) + if p is not board.active_pokemon(player_id)) + + +async def metal_saucer(ctx): + """Attach a Metal Energy card from your discard pile to 1 of your + Benched Metal Pokemon.""" + energies = [c for c in ctx.discard_pile() if is_metal_energy_card(c)] + if not energies: + return + bench = [p for p in ctx.my_bench() if _is_metal_pokemon(p)] + if not bench: + return + picks = await ctx.choose_cards( + energies, 1, minimum=1, prompt="Choose a Metal Energy card to attach" + ) + if not picks: + return + target = await ctx.choose_pokemon(bench, "Choose a Benched Metal Pokémon") + if target is not None: + await ctx.attach_energy(picks[0], target) + card = ItemCardDef( guid="6b53ba38-5c58-594e-88ab-453a5a764744", @@ -11,5 +46,6 @@ card = ItemCardDef( collector_number=214, set_code="SWSH1", rarity=Rarities.RareSecret, - effect=unimplemented + effect=metal_saucer, + condition=_metal_saucer_condition, ) diff --git a/spirit/game/scripts/cards/SWSH1/Minccino_145.py b/spirit/game/scripts/cards/SWSH1/Minccino_145.py index 36a4c9e..6dfa3ce 100644 --- a/spirit/game/scripts/cards/SWSH1/Minccino_145.py +++ b/spirit/game/scripts/cards/SWSH1/Minccino_145.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage + + +async def glance(ctx): + """Look at the top card of your opponent's deck.""" + top = ctx.deck_top(1, player_id=ctx.opponent_id) + if not top: + return + await ctx.present_card_choice( + top[0], "Top card of your opponent's deck", ["OK"]) card = PokemonCardDef( guid="8b5d3f00-2323-56c4-a204-eb60c05ac21e", @@ -22,7 +32,7 @@ card = PokemonCardDef( title="Glance", game_text="Look at the top card of your opponent's deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=glance, ), Attack( title="Tail Slap", @@ -30,7 +40,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=20, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=2, per_heads=20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/MorpekoVMAX_204.py b/spirit/game/scripts/cards/SWSH1/MorpekoVMAX_204.py index 82f9aaf..1e8da34 100644 --- a/spirit/game/scripts/cards/SWSH1/MorpekoVMAX_204.py +++ b/spirit/game/scripts/cards/SWSH1/MorpekoVMAX_204.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import spread_damage card = PokemonCardDef( guid="0514a97a-f46b-5276-95d4-c31f54b56b93", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="This attack also does 20 damage to each of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 1}, damage=180, - effect=unimplemented, + effect=spread_damage(20, side="opponent", also_base=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/MorpekoVMAX_80.py b/spirit/game/scripts/cards/SWSH1/MorpekoVMAX_80.py index 42e83a2..542b0a4 100644 --- a/spirit/game/scripts/cards/SWSH1/MorpekoVMAX_80.py +++ b/spirit/game/scripts/cards/SWSH1/MorpekoVMAX_80.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import spread_damage card = PokemonCardDef( guid="8d0567e8-2624-52e6-8abc-7ba26e335fe3", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="This attack also does 20 damage to each of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 1}, damage=180, - effect=unimplemented, + effect=spread_damage(20, side="opponent", also_base=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/MorpekoV_190.py b/spirit/game/scripts/cards/SWSH1/MorpekoV_190.py index a8b985a..f5365f9 100644 --- a/spirit/game/scripts/cards/SWSH1/MorpekoV_190.py +++ b/spirit/game/scripts/cards/SWSH1/MorpekoV_190.py @@ -1,6 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import snipe_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def electro_wheel(ctx): + await ctx.deal_damage() + picks = await ctx.discard_energy_from(ctx.attacker, 1, prompt="Discard an Energy from this Pokémon.") + if not picks: + return + bench = ctx.my_bench() + if not bench: + return + target = await ctx.choose_pokemon(bench, "Choose your new Active Pokémon") + if target is not None: + await ctx.switch_active(ctx.player_id, target) + card = PokemonCardDef( guid="3fb6c8ea-d7a4-57ff-80dc-1654cd0bb252", key="SWSH1", @@ -23,14 +37,14 @@ card = PokemonCardDef( game_text="This attack also does 20 damage to 1 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.COLORLESS: 1}, damage=20, - effect=unimplemented, + effect=snipe_attack(20, pool="bench", count=1, also_base=True), ), Attack( title="Electro Wheel", game_text="Discard an Energy from this Pok\u00e9mon. If you do, switch it with 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 1}, damage=150, - effect=unimplemented, + effect=electro_wheel, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/MorpekoV_79.py b/spirit/game/scripts/cards/SWSH1/MorpekoV_79.py index a12cc1a..44cdb64 100644 --- a/spirit/game/scripts/cards/SWSH1/MorpekoV_79.py +++ b/spirit/game/scripts/cards/SWSH1/MorpekoV_79.py @@ -1,6 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import snipe_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def electro_wheel(ctx): + await ctx.deal_damage() + picks = await ctx.discard_energy_from(ctx.attacker, 1, prompt="Discard an Energy from this Pokémon.") + if not picks: + return + bench = ctx.my_bench() + if not bench: + return + target = await ctx.choose_pokemon(bench, "Choose your new Active Pokémon") + if target is not None: + await ctx.switch_active(ctx.player_id, target) + card = PokemonCardDef( guid="8813cb5b-84e5-53d0-a934-e29de452ef45", key="SWSH1", @@ -23,14 +37,14 @@ card = PokemonCardDef( game_text="This attack also does 20 damage to 1 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.COLORLESS: 1}, damage=20, - effect=unimplemented, + effect=snipe_attack(20, pool="bench", count=1, also_base=True), ), Attack( title="Electro Wheel", game_text="Discard an Energy from this Pok\u00e9mon. If you do, switch it with 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 1}, damage=150, - effect=unimplemented, + effect=electro_wheel, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Morpeko_78.py b/spirit/game/scripts/cards/SWSH1/Morpeko_78.py index 4bdfb1f..0d0da37 100644 --- a/spirit/game/scripts/cards/SWSH1/Morpeko_78.py +++ b/spirit/game/scripts/cards/SWSH1/Morpeko_78.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, has_damage card = PokemonCardDef( guid="5858b043-bc4a-5864-8bf8-f638f5004313", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.LIGHTNING: 1}, damage=10, damage_operator="+", - effect=unimplemented, + effect=bonus_if(has_damage("defender"), 50), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Mudsdale_106.py b/spirit/game/scripts/cards/SWSH1/Mudsdale_106.py index 3a609c9..0164421 100644 --- a/spirit/game/scripts/cards/SWSH1/Mudsdale_106.py +++ b/spirit/game/scripts/cards/SWSH1/Mudsdale_106.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="a598a963-796c-5643-b898-0ad3e4809bfe", @@ -25,7 +26,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 3}, damage=120, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=2, per_heads=120), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Munna_87.py b/spirit/game/scripts/cards/SWSH1/Munna_87.py index ee00059..5abc7ae 100644 --- a/spirit/game/scripts/cards/SWSH1/Munna_87.py +++ b/spirit/game/scripts/cards/SWSH1/Munna_87.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import heal_attack card = PokemonCardDef( guid="cbe9f01d-ff26-5cda-ba9b-75b5e8d73ea5", @@ -23,7 +24,7 @@ card = PokemonCardDef( title="Moonlight", game_text="Heal 30 damage from this Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=heal_attack(30), ), Attack( title="Psychic Sphere", diff --git a/spirit/game/scripts/cards/SWSH1/Musharna_88.py b/spirit/game/scripts/cards/SWSH1/Musharna_88.py index b71ec92..f98d876 100644 --- a/spirit/game/scripts/cards/SWSH1/Musharna_88.py +++ b/spirit/game/scripts/cards/SWSH1/Musharna_88.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack, bonus_if, defender_has_condition card = PokemonCardDef( guid="bb1f1731-14bd-59fd-8657-a7fb347b2b2b", @@ -24,7 +25,7 @@ card = PokemonCardDef( title="Sleepy Pulse", game_text="Your opponent's Active Pok\u00e9mon is now Asleep. During Pok\u00e9mon Checkup, your opponent flips 2 coins instead of 1. If either of them is tails, that Pok\u00e9mon is still Asleep.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=condition_attack(SpecialConditions.ASLEEP, checkup_coins=2), ), Attack( title="Super Hypnoblast", @@ -32,7 +33,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=10, damage_operator="+", - effect=unimplemented, + effect=bonus_if(defender_has_condition(SpecialConditions.ASLEEP), 120), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Nickit_125.py b/spirit/game/scripts/cards/SWSH1/Nickit_125.py index 4bdfe19..b474327 100644 --- a/spirit/game/scripts/cards/SWSH1/Nickit_125.py +++ b/spirit/game/scripts/cards/SWSH1/Nickit_125.py @@ -1,6 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def instigate(ctx): + moved = await ctx.hand_to_bottom_of_deck(ctx.opponent_id) + if moved > 0: + await ctx.draw_cards(3, player_id=ctx.opponent_id) + + card = PokemonCardDef( guid="105970b3-e0a6-5408-a21a-cf67016c0743", key="SWSH1", @@ -22,7 +29,7 @@ card = PokemonCardDef( title="Instigate", game_text="Your opponent shuffles their hand and puts it on the bottom of their deck. If they put any cards on the bottom of their deck in this way, they draw 3 cards.", cost={PokemonTypes.DARKNESS: 1}, - effect=unimplemented, + effect=instigate, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Ninetales_23.py b/spirit/game/scripts/cards/SWSH1/Ninetales_23.py index a6220d6..9f0155c 100644 --- a/spirit/game/scripts/cards/SWSH1/Ninetales_23.py +++ b/spirit/game/scripts/cards/SWSH1/Ninetales_23.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import attach_from_discard +from spirit.game.card_effects.pokemon import energy_provides_type + + +def _is_fire_energy(card): + return energy_provides_type(card, PokemonTypes.FIRE.value) + card = PokemonCardDef( guid="dd15c132-56c8-588c-953a-1b83bf1b1f31", @@ -24,7 +31,7 @@ card = PokemonCardDef( game_text="Attach a Fire Energy card from your discard pile to this Pok\u00e9mon.", cost={PokemonTypes.FIRE: 1}, damage=30, - effect=unimplemented, + effect=attach_from_discard(predicate=_is_fire_energy), ), Attack( title="Fire Mane", diff --git a/spirit/game/scripts/cards/SWSH1/Noctowl_144.py b/spirit/game/scripts/cards/SWSH1/Noctowl_144.py index ce70e70..eb6a0f4 100644 --- a/spirit/game/scripts/cards/SWSH1/Noctowl_144.py +++ b/spirit/game/scripts/cards/SWSH1/Noctowl_144.py @@ -1,5 +1,30 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import full_stack + + +async def carry_off(ctx): + """Choose 1 of your opponent's Benched Pokemon. They shuffle that Pokemon + and all attached cards into their deck. Then, shuffle this Pokemon and + all attached cards into your deck.""" + bench = ctx.opponent_bench() + if bench: + target = await ctx.choose_pokemon( + bench, "Choose 1 of your opponent's Benched Pokémon" + ) + if target is not None and not ctx.effects_blocked(target): + await ctx.shuffle_into_deck(full_stack(target), ctx.opponent_id) + await ctx.shuffle_into_deck(full_stack(ctx.attacker), ctx.player_id) + + async def _promote(): + if not await ctx.session._promote_new_active(ctx.player_id): + screen_name = ctx.session.players[ctx.player_id].screen_name + await ctx.session.end_game( + ctx.opponent_id, f"{screen_name} has no Pokémon left" + ) + + ctx.deferred_actions.append(_promote) + card = PokemonCardDef( guid="d32a9091-53f1-5364-8586-1063e19c2c05", @@ -29,7 +54,7 @@ card = PokemonCardDef( title="Carry Off", game_text="Choose 1 of your opponent's Benched Pok\u00e9mon. They shuffle that Pok\u00e9mon and all attached cards into their deck. Then, shuffle this Pok\u00e9mon and all attached cards into your deck.", cost={PokemonTypes.COLORLESS: 3}, - effect=unimplemented, + effect=carry_off, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Orbeetle_19.py b/spirit/game/scripts/cards/SWSH1/Orbeetle_19.py index 870aa63..b8c13b4 100644 --- a/spirit/game/scripts/cards/SWSH1/Orbeetle_19.py +++ b/spirit/game/scripts/cards/SWSH1/Orbeetle_19.py @@ -1,5 +1,32 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import count_energy, damage_per + + +async def bugs_radar(ctx): + if not await ctx.ask_yes_no( + "Look at the top 3 cards of your opponent's deck and put them back " + "in any order?" + ): + return + top = ctx.deck_top(3, ctx.opponent_id) + if len(top) <= 1: + return + picked_ids = await ctx.session.prompt_card_chooser( + ctx.player_id, ctx.source.entity_id, top, len(top), minimum=len(top), + prompt="Put the cards back in any order.", ordered=True, + ) + by_id = {c.entity_id: c for c in top} + order = [by_id[i] for i in picked_ids if i in by_id] + for card in top: + if card not in order: + order.append(card) + deck = ctx.board.find_player_area(ctx.opponent_id, "deck") + for card in order: + deck.children.remove(card) + for card in reversed(order): + deck.children.append(card) + card = PokemonCardDef( guid="a3dc1dd1-a9fa-57e2-94dd-9186bf97ac4b", @@ -22,7 +49,8 @@ card = PokemonCardDef( Ability( title="Bug's Radar", game_text="Once during your turn, you may look at the top 3 cards of your opponent's deck and put them back in any order.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + effect=bugs_radar, ), Attack( title="Brainwave", @@ -30,7 +58,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=90, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("self", energy_type=PokemonTypes.PSYCHIC), 30, base=90), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/OrdinaryRod_215.py b/spirit/game/scripts/cards/SWSH1/OrdinaryRod_215.py index 133e187..3715715 100644 --- a/spirit/game/scripts/cards/SWSH1/OrdinaryRod_215.py +++ b/spirit/game/scripts/cards/SWSH1/OrdinaryRod_215.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.card_effects.trainers import ordinary_rod +from spirit.game.data_utils import ItemCardDef from spirit.game.attributes import Rarities card = ItemCardDef( @@ -11,5 +12,5 @@ card = ItemCardDef( collector_number=215, set_code="SWSH1", rarity=Rarities.RareSecret, - effect=unimplemented + effect=ordinary_rod ) diff --git a/spirit/game/scripts/cards/SWSH1/Pikachu_65.py b/spirit/game/scripts/cards/SWSH1/Pikachu_65.py index fe7f7e2..887551f 100644 --- a/spirit/game/scripts/cards/SWSH1/Pikachu_65.py +++ b/spirit/game/scripts/cards/SWSH1/Pikachu_65.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import is_lightning_energy +from spirit.game.card_effects.support_common import search_attach_energy card = PokemonCardDef( guid="39cf1016-73b9-52d0-ad83-2ff70a3d670e", @@ -20,9 +22,9 @@ card = PokemonCardDef( abilities=[ Attack( title="Charge", - game_text="Search your deck for a Lightning Energy card and attach it to this Pok\u00e9mon. Then, shuffle your deck.", + game_text="Search your deck for a Lightning Energy card and attach it to this Pokémon. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_attach_energy(predicate=is_lightning_energy, count=1, to_self=True), ), Attack( title="Electro Ball", @@ -30,4 +32,4 @@ card = PokemonCardDef( damage=20, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH1/Pincurchin_77.py b/spirit/game/scripts/cards/SWSH1/Pincurchin_77.py index 1706df2..209a4a2 100644 --- a/spirit/game/scripts/cards/SWSH1/Pincurchin_77.py +++ b/spirit/game/scripts/cards/SWSH1/Pincurchin_77.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions + + +async def shocking_needles(ctx): + """Flip 4 coins. 30 damage per heads; 2+ heads also Paralyzes the Defending Pokemon.""" + results = await ctx.flip_coins(4, ctx.ability.title) + heads = sum(results) + if heads: + await ctx.deal_damage(30 * heads) + if heads >= 2 and ctx.defender is not None: + await ctx.apply_special_condition(ctx.defender, SpecialConditions.PARALYZED) card = PokemonCardDef( guid="8fdce931-5d96-51ff-8ee4-a2dd043b9bde", @@ -24,7 +34,7 @@ card = PokemonCardDef( cost={PokemonTypes.LIGHTNING: 2}, damage=30, damage_operator="x", - effect=unimplemented, + effect=shocking_needles, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/PokKid_173.py b/spirit/game/scripts/cards/SWSH1/PokKid_173.py index 8f713b1..46fa3a0 100644 --- a/spirit/game/scripts/cards/SWSH1/PokKid_173.py +++ b/spirit/game/scripts/cards/SWSH1/PokKid_173.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.session.effects import is_pokemon_card card = SupporterCardDef( guid="bece6b76-81de-5a5a-873a-4681fe181172", @@ -11,5 +13,8 @@ card = SupporterCardDef( collector_number=173, set_code="SWSH1", rarity=Rarities.Uncommon, - effect=unimplemented + effect=search_to_hand( + is_pokemon_card, count=1, minimum=0, reveal=True, + prompt="Choose a Pokémon to put into your hand.", + ) ) diff --git a/spirit/game/scripts/cards/SWSH1/Pokgear30_174.py b/spirit/game/scripts/cards/SWSH1/Pokgear30_174.py index 068d04c..ef7c95b 100644 --- a/spirit/game/scripts/cards/SWSH1/Pokgear30_174.py +++ b/spirit/game/scripts/cards/SWSH1/Pokgear30_174.py @@ -1,5 +1,24 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef from spirit.game.attributes import Rarities +from spirit.game.session.effects import is_supporter_card + + +async def pokegear_30(ctx): + """Look at the top 7 cards of the deck; may reveal a Supporter found + there and put it into hand. Shuffle the rest back into the deck.""" + top = ctx.deck_top(7) + if not top: + return + candidates = [c for c in top if is_supporter_card(c)] + if candidates: + picks = await ctx.choose_cards( + candidates, 1, minimum=0, + prompt="Choose a Supporter card to put into your hand.", + display_cards=top, + ) + await ctx.put_in_hand(picks, reveal=True) + await ctx.shuffle_deck() + card = ItemCardDef( guid="307f7f44-275a-52a2-8f2e-300fd37739e1", @@ -11,5 +30,5 @@ card = ItemCardDef( collector_number=174, set_code="SWSH1", rarity=Rarities.Uncommon, - effect=unimplemented + effect=pokegear_30 ) diff --git a/spirit/game/scripts/cards/SWSH1/PokmonCatcher_175.py b/spirit/game/scripts/cards/SWSH1/PokmonCatcher_175.py index ed1b447..b196969 100644 --- a/spirit/game/scripts/cards/SWSH1/PokmonCatcher_175.py +++ b/spirit/game/scripts/cards/SWSH1/PokmonCatcher_175.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import opponent_has_bench + + +async def pokemon_catcher(ctx): + """Flip a coin. If heads, switch 1 of the opponent's Benched Pokemon + with their Active Pokemon.""" + if not (await ctx.flip_coins(1, "Pokémon Catcher"))[0]: + return + bench = ctx.opponent_bench() + if not bench: + return + target = await ctx.choose_pokemon( + bench, "Choose your opponent's new Active Pokémon" + ) + if target is not None: + await ctx.switch_active(ctx.opponent_id, target) + card = ItemCardDef( guid="02f84ed1-bd92-5d2c-ac8d-45ba09986d81", @@ -11,5 +28,6 @@ card = ItemCardDef( collector_number=175, set_code="SWSH1", rarity=Rarities.Uncommon, - effect=unimplemented + effect=pokemon_catcher, + condition=opponent_has_bench ) diff --git a/spirit/game/scripts/cards/SWSH1/Polteageist_90.py b/spirit/game/scripts/cards/SWSH1/Polteageist_90.py index 857c5ad..9fa928a 100644 --- a/spirit/game/scripts/cards/SWSH1/Polteageist_90.py +++ b/spirit/game/scripts/cards/SWSH1/Polteageist_90.py @@ -1,5 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_trainer_card + + +async def teatime(ctx): + """Each player draws 2 cards.""" + await ctx.draw_cards(2, player_id=ctx.player_id) + await ctx.draw_cards(2, player_id=ctx.opponent_id) + + +async def poltergeist(ctx): + """Opponent reveals their hand; 50 damage for each Trainer card found there.""" + cards = await ctx.reveal_hand(of_player=ctx.opponent_id) + count = sum(1 for c in cards if is_trainer_card(c)) + await ctx.deal_damage(50 * count) + card = PokemonCardDef( guid="d6a83755-2794-5dba-996e-639b2ee4639b", @@ -24,7 +39,7 @@ card = PokemonCardDef( title="Teatime", game_text="Each player draws 2 cards.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=teatime, ), Attack( title="Poltergeist", @@ -32,7 +47,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=50, damage_operator="x", - effect=unimplemented, + effect=poltergeist, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH1/Potion_177.py b/spirit/game/scripts/cards/SWSH1/Potion_177.py index dbe8cd5..aebaad3 100644 --- a/spirit/game/scripts/cards/SWSH1/Potion_177.py +++ b/spirit/game/scripts/cards/SWSH1/Potion_177.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.support_common import heal_item, requires_damaged_pokemon card = ItemCardDef( guid="4caa253e-ae15-556b-ba22-541b14b50fd5", @@ -11,5 +12,6 @@ card = ItemCardDef( collector_number=177, set_code="SWSH1", rarity=Rarities.Uncommon, - effect=unimplemented + condition=requires_damaged_pokemon(), + effect=heal_item(30) ) diff --git a/spirit/game/scripts/cards/SWSH1/Qwilfish_51.py b/spirit/game/scripts/cards/SWSH1/Qwilfish_51.py index 62d48f7..4beb26b 100644 --- a/spirit/game/scripts/cards/SWSH1/Qwilfish_51.py +++ b/spirit/game/scripts/cards/SWSH1/Qwilfish_51.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_bonus_attack +from spirit.game.card_effects.pokemon import in_active_spot + + +async def poison_point(ctx): + """Active Spot only: if damaged by an opponent's attack (even if + Knocked Out), the Attacking Pokemon is now Poisoned.""" + if not in_active_spot(ctx.board, ctx.player_id, ctx.source): + return + attacker = ctx.damaged_by + if attacker is None: + return + await ctx.apply_special_condition(attacker, SpecialConditions.POISONED) + card = PokemonCardDef( guid="350c8387-b318-5288-a64f-2b33779957e8", @@ -21,7 +35,8 @@ card = PokemonCardDef( Ability( title="Poison Point", game_text="If this Pok\u00e9mon is your Active Pok\u00e9mon and is damaged by an opponent's attack (even if this Pok\u00e9mon is Knocked Out), the Attacking Pok\u00e9mon is now Poisoned.", - effect=unimplemented, + trigger=Triggers.ON_DAMAGED_BY_ATTACK, + effect=poison_point, ), Attack( title="Venoshock", @@ -29,7 +44,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 1}, damage=10, damage_operator="+", - effect=unimplemented, + effect=condition_bonus_attack(60, SpecialConditions.POISONED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Raboot_32.py b/spirit/game/scripts/cards/SWSH1/Raboot_32.py index b42da0e..e6993fb 100644 --- a/spirit/game/scripts/cards/SWSH1/Raboot_32.py +++ b/spirit/game/scripts/cards/SWSH1/Raboot_32.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import energy_provides_type +from spirit.game.card_effects.support_common import search_attach_energy + + +def _is_fire_energy(card): + return energy_provides_type(card, PokemonTypes.FIRE.value) + card = PokemonCardDef( guid="e295f9a6-2b3b-5a0a-8929-8f832da97ffc", @@ -24,7 +31,7 @@ card = PokemonCardDef( game_text="Search your deck for a Fire Energy card and attach it to this Pok\u00e9mon. Then, shuffle your deck.", cost={PokemonTypes.FIRE: 1}, damage=20, - effect=unimplemented, + effect=search_attach_energy(predicate=_is_fire_energy, count=1, to_self=True), ), Attack( title="Magnum Kick", diff --git a/spirit/game/scripts/cards/SWSH1/RareCandy_180.py b/spirit/game/scripts/cards/SWSH1/RareCandy_180.py index 833b86b..313accd 100644 --- a/spirit/game/scripts/cards/SWSH1/RareCandy_180.py +++ b/spirit/game/scripts/cards/SWSH1/RareCandy_180.py @@ -1,5 +1,50 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import ItemCardDef, evolves_from +from spirit.game.attributes import Rarities, AttrID, PokemonStage +from spirit.game.session.effects import is_basic_pokemon, is_pokemon_card + + +def _stage2_matches(hand_cards, logic_name): + return [ + c for c in hand_cards + if is_pokemon_card(c) + and c.get_attribute(AttrID.STAGE) == PokemonStage.STAGE2.value + and evolves_from(c.archetype_id, logic_name) + ] + + +def _turn_eligible_basics(board, player_id): + turn_state = getattr(board, "turn_state", None) + if turn_state is None: + return [] + return [ + p for p in board.pokemon_in_play(player_id) + if is_basic_pokemon(p) and turn_state.may_evolve_target(p.entity_id) + ] + + +def _rare_candy_condition(board, player_id): + return bool(_turn_eligible_basics(board, player_id)) + + +async def _rare_candy(ctx): + """Choose a Basic Pokemon in play; if you have a Stage 2 in hand that evolves from it, put that card onto it, skipping the Stage 1.""" + candidates = _turn_eligible_basics(ctx.board, ctx.player_id) + if not candidates: + return + target = await ctx.choose_pokemon(candidates, "Choose a Basic Pokémon in play") + if target is None: + return + logic_name = target.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) + stage2_hand = _stage2_matches(ctx.hand(), logic_name) if logic_name else [] + if not stage2_hand: + return + picks = await ctx.choose_cards( + stage2_hand, 1, prompt="Choose a Stage 2 Pokémon to evolve into", + ) + if not picks: + return + await ctx.evolve_pokemon(target, picks[0]) + card = ItemCardDef( guid="deada367-2b30-5ab0-a4f8-2183298fd72b", @@ -11,5 +56,6 @@ card = ItemCardDef( collector_number=180, set_code="SWSH1", rarity=Rarities.Uncommon, - effect=unimplemented + effect=_rare_candy, + condition=_rare_candy_condition, ) diff --git a/spirit/game/scripts/cards/SWSH1/RegirockV_104.py b/spirit/game/scripts/cards/SWSH1/RegirockV_104.py index 61a1dfb..77c2230 100644 --- a/spirit/game/scripts/cards/SWSH1/RegirockV_104.py +++ b/spirit/game/scripts/cards/SWSH1/RegirockV_104.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, damage_counters_on, recoil_attack card = PokemonCardDef( guid="9f385d35-3e03-5e6f-a1e2-eb3802ff0fb6", @@ -24,14 +25,14 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 1}, damage=30, damage_operator="+", - effect=unimplemented, + effect=damage_per(damage_counters_on("self"), 10, base=30), ), Attack( title="Rocky Tackle", game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=190, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Rhydon_98.py b/spirit/game/scripts/cards/SWSH1/Rhydon_98.py index 347ffb0..1142692 100644 --- a/spirit/game/scripts/cards/SWSH1/Rhydon_98.py +++ b/spirit/game/scripts/cards/SWSH1/Rhydon_98.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import snipe_attack card = PokemonCardDef( guid="d934789a-ba62-5c73-826d-e0942b3d2c81", @@ -29,7 +30,7 @@ card = PokemonCardDef( game_text="This attack also does 10 damage to 2 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 2}, damage=100, - effect=unimplemented, + effect=snipe_attack(10, pool="bench", count=2, also_base=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Rhyhorn_97.py b/spirit/game/scripts/cards/SWSH1/Rhyhorn_97.py index 017f8cb..4491f3b 100644 --- a/spirit/game/scripts/cards/SWSH1/Rhyhorn_97.py +++ b/spirit/game/scripts/cards/SWSH1/Rhyhorn_97.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import mill_attack card = PokemonCardDef( guid="43af9110-c618-53a7-a962-c631c148afed", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Stomp Off", game_text="Discard the top 2 cards of your opponent's deck.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=mill_attack(2), ), Attack( title="Land Crush", diff --git a/spirit/game/scripts/cards/SWSH1/Rhyperior_99.py b/spirit/game/scripts/cards/SWSH1/Rhyperior_99.py index 5863042..91796b5 100644 --- a/spirit/game/scripts/cards/SWSH1/Rhyperior_99.py +++ b/spirit/game/scripts/cards/SWSH1/Rhyperior_99.py @@ -1,5 +1,23 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID + + +async def rock_tumble(ctx): + """90 damage; this attack's damage isn't affected by Resistance.""" + await ctx.deal_damage(ignore_resistance=True) + + +async def bedrock_shake(ctx): + """120 damage, plus 60 to each Benched Pokemon with any damage counters + on it (both sides; no Weakness/Resistance for Benched Pokemon).""" + await ctx.deal_damage() + targets = [ + p for p in ctx.my_bench() + ctx.opponent_bench() + if p.get_attribute(AttrID.HP, 0) < ctx.max_hp(p) + ] + for target in targets: + await ctx.deal_damage(60, target=target, apply_modifiers=False) + card = PokemonCardDef( guid="a42e2b20-ced8-57d6-a467-a95aa5dd2cd5", @@ -24,14 +42,14 @@ card = PokemonCardDef( game_text="This attack's damage isn't affected by Resistance.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 2}, damage=90, - effect=unimplemented, + effect=rock_tumble, ), Attack( title="Bedrock Shake", game_text="This attack also does 60 damage to each Benched Pok\u00e9mon that has any damage counters on it (both yours and your opponent's). (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 2}, damage=120, - effect=unimplemented, + effect=bedrock_shake, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Rillaboom_14.py b/spirit/game/scripts/cards/SWSH1/Rillaboom_14.py index c6ee290..bd9e575 100644 --- a/spirit/game/scripts/cards/SWSH1/Rillaboom_14.py +++ b/spirit/game/scripts/cards/SWSH1/Rillaboom_14.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.trainers import is_grass_energy_card +from spirit.game.card_effects.support_common import search_attach_energy card = PokemonCardDef( guid="cc8cfd56-b2a7-5e1d-8a87-b2f058565429", @@ -22,7 +24,8 @@ card = PokemonCardDef( Ability( title="Voltage Beat", game_text="Once during your turn, you may search your deck for up to 2 Grass Energy cards and attach them to 1 of your Pok\u00e9mon. Then, shuffle your deck.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + effect=search_attach_energy(predicate=is_grass_energy_card, count=2, distribute=False), ), Attack( title="Hammer In", diff --git a/spirit/game/scripts/cards/SWSH1/Rillaboom_15.py b/spirit/game/scripts/cards/SWSH1/Rillaboom_15.py index 92716a0..408adec 100644 --- a/spirit/game/scripts/cards/SWSH1/Rillaboom_15.py +++ b/spirit/game/scripts/cards/SWSH1/Rillaboom_15.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import spread_damage card = PokemonCardDef( guid="20d143fa-e3d6-597d-a0f7-6eca7d47bb65", @@ -24,14 +25,14 @@ card = PokemonCardDef( game_text="This attack also does 10 damage to each of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.GRASS: 2, PokemonTypes.COLORLESS: 1}, damage=90, - effect=unimplemented, + effect=spread_damage(10, side="opponent", also_base=True), ), Attack( title="Drum Beating", game_text="During your next turn, this Pok\u00e9mon can't use Drum Beating.", cost={PokemonTypes.GRASS: 3, PokemonTypes.COLORLESS: 1}, damage=180, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Roselia_2.py b/spirit/game/scripts/cards/SWSH1/Roselia_2.py index 7b7ae4f..4dffcfb 100644 --- a/spirit/game/scripts/cards/SWSH1/Roselia_2.py +++ b/spirit/game/scripts/cards/SWSH1/Roselia_2.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import heal_attack card = PokemonCardDef( guid="8c7a20b9-ceb3-5bc7-8759-1a4d8b7aaecc", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Heal 10 damage from this Pok\u00e9mon.", cost={PokemonTypes.GRASS: 1}, damage=10, - effect=unimplemented, + effect=heal_attack(10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Roselia_3.py b/spirit/game/scripts/cards/SWSH1/Roselia_3.py index 03e3881..c8dde53 100644 --- a/spirit/game/scripts/cards/SWSH1/Roselia_3.py +++ b/spirit/game/scripts/cards/SWSH1/Roselia_3.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import heal_targets card = PokemonCardDef( guid="e46313c8-85f8-5346-8946-0fe4dd5404ac", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Sweet Scent", game_text="Heal 30 damage from 1 of your Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=heal_targets(30, "choice"), ), Attack( title="Sting", diff --git a/spirit/game/scripts/cards/SWSH1/Roserade_4.py b/spirit/game/scripts/cards/SWSH1/Roserade_4.py index 3536a8b..3a34be4 100644 --- a/spirit/game/scripts/cards/SWSH1/Roserade_4.py +++ b/spirit/game/scripts/cards/SWSH1/Roserade_4.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.support_common import heal_attack card = PokemonCardDef( guid="683015b9-fae8-5341-aa5d-3c74a1e92f52", @@ -24,14 +26,17 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Poisoned. Flip a coin. If heads, your opponent's Active Pok\u00e9mon is also Paralyzed.", cost={PokemonTypes.GRASS: 1}, damage=30, - effect=unimplemented, + effect=condition_attack( + SpecialConditions.PARALYZED, flip=True, + always_conditions=(SpecialConditions.POISONED,), + ), ), Attack( title="Mega Drain", game_text="Heal 30 damage from this Pok\u00e9mon.", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 2}, damage=90, - effect=unimplemented, + effect=heal_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/RotomBike_181.py b/spirit/game/scripts/cards/SWSH1/RotomBike_181.py index dbb3a85..8b92dd7 100644 --- a/spirit/game/scripts/cards/SWSH1/RotomBike_181.py +++ b/spirit/game/scripts/cards/SWSH1/RotomBike_181.py @@ -1,6 +1,13 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef from spirit.game.attributes import Rarities + +async def _rotom_bike_effect(ctx): + """Draw cards until you have 6 cards in your hand. Your turn ends.""" + await ctx.draw_until(6) + ctx.ends_turn = True + + card = ItemCardDef( guid="a0bfd7b6-5c3e-5d34-a153-6904c38724d2", key="SWSH1", @@ -11,5 +18,5 @@ card = ItemCardDef( collector_number=181, set_code="SWSH1", rarity=Rarities.Uncommon, - effect=unimplemented + effect=_rotom_bike_effect, ) diff --git a/spirit/game/scripts/cards/SWSH1/SableyeV_120.py b/spirit/game/scripts/cards/SWSH1/SableyeV_120.py index b32a153..876a246 100644 --- a/spirit/game/scripts/cards/SWSH1/SableyeV_120.py +++ b/spirit/game/scripts/cards/SWSH1/SableyeV_120.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, damage_counters_on +from spirit.game.card_effects.support_common import recover_from_discard +from spirit.game.session.effects import is_trainer_card card = PokemonCardDef( guid="59eb1d89-ad28-5923-8d84-e867c63358cf", @@ -22,7 +25,8 @@ card = PokemonCardDef( title="Lode Search", game_text="Put a Trainer card from your discard pile into your hand.", cost={PokemonTypes.DARKNESS: 1}, - effect=unimplemented, + effect=recover_from_discard(is_trainer_card, count=1, minimum=1, + reveal=False, to="hand"), ), Attack( title="Crazy Claws", @@ -30,7 +34,7 @@ card = PokemonCardDef( cost={PokemonTypes.DARKNESS: 2}, damage=10, damage_operator="+", - effect=unimplemented, + effect=damage_per(damage_counters_on("defender"), 60, base=10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/SableyeV_194.py b/spirit/game/scripts/cards/SWSH1/SableyeV_194.py index f8d432a..77795bb 100644 --- a/spirit/game/scripts/cards/SWSH1/SableyeV_194.py +++ b/spirit/game/scripts/cards/SWSH1/SableyeV_194.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, damage_counters_on +from spirit.game.card_effects.support_common import recover_from_discard +from spirit.game.session.effects import is_trainer_card card = PokemonCardDef( guid="14baca74-f776-5380-a052-8b40e807da1a", @@ -22,7 +25,8 @@ card = PokemonCardDef( title="Lode Search", game_text="Put a Trainer card from your discard pile into your hand.", cost={PokemonTypes.DARKNESS: 1}, - effect=unimplemented, + effect=recover_from_discard(is_trainer_card, count=1, minimum=1, + reveal=False, to="hand"), ), Attack( title="Crazy Claws", @@ -30,7 +34,7 @@ card = PokemonCardDef( cost={PokemonTypes.DARKNESS: 2}, damage=10, damage_operator="+", - effect=unimplemented, + effect=damage_per(damage_counters_on("defender"), 60, base=10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Salandit_27.py b/spirit/game/scripts/cards/SWSH1/Salandit_27.py index f439f30..130bbc3 100644 --- a/spirit/game/scripts/cards/SWSH1/Salandit_27.py +++ b/spirit/game/scripts/cards/SWSH1/Salandit_27.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="01f88402-bb3c-5ec8-88db-3827c748ef00", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Smog", game_text="Your opponent's Active Pok\u00e9mon is now Poisoned.", cost={PokemonTypes.FIRE: 1}, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Salazzle_28.py b/spirit/game/scripts/cards/SWSH1/Salazzle_28.py index c3e43f1..8e2fca0 100644 --- a/spirit/game/scripts/cards/SWSH1/Salazzle_28.py +++ b/spirit/game/scripts/cards/SWSH1/Salazzle_28.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack, self_energy_discard_attack card = PokemonCardDef( guid="8147aaac-109a-544a-a64d-8a00320d3758", @@ -24,14 +25,14 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Burned.", cost={PokemonTypes.FIRE: 1}, damage=20, - effect=unimplemented, + effect=condition_attack(SpecialConditions.BURNED), ), Attack( title="Bright Flame", game_text="Discard 2 Energy from this Pok\u00e9mon.", cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + effect=self_energy_discard_attack(count=2), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Sandaconda_109.py b/spirit/game/scripts/cards/SWSH1/Sandaconda_109.py index 259d214..b60cfe6 100644 --- a/spirit/game/scripts/cards/SWSH1/Sandaconda_109.py +++ b/spirit/game/scripts/cards/SWSH1/Sandaconda_109.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.passives_common import boost_own_next_turn +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="During your next turn, this Pok\u00e9mon's attacks do 120 more damage to your opponent's Active Pok\u00e9mon (before applying Weakness and Resistance).", cost={PokemonTypes.COLORLESS: 1}, damage=10, - effect=unimplemented, + effect=boost_own_next_turn(120), ), Attack( title="Skull Bash", diff --git a/spirit/game/scripts/cards/SWSH1/Sandaconda_110.py b/spirit/game/scripts/cards/SWSH1/Sandaconda_110.py index 9e1521f..dbe413b 100644 --- a/spirit/game/scripts/cards/SWSH1/Sandaconda_110.py +++ b/spirit/game/scripts/cards/SWSH1/Sandaconda_110.py @@ -1,6 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import bonus_if, count_energy +from spirit.game.card_effects.passives_common import takes_less_passive +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +_extra_fighting_energy = lambda ctx: count_energy( + "self", energy_type=PokemonTypes.FIGHTING)(ctx) >= 3 + card = PokemonCardDef( guid="e820b680-0fd1-5cbc-bcf2-f509b9950590", key="SWSH1", @@ -22,7 +27,7 @@ card = PokemonCardDef( Ability( title="Sand Sac", game_text="This Pok\u00e9mon takes 30 less damage from attacks (after applying Weakness and Resistance).", - effect=unimplemented, + passive=takes_less_passive(30), ), Attack( title="Power Press", @@ -30,7 +35,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 2}, damage=60, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_extra_fighting_energy, 70), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Scorbunny_30.py b/spirit/game/scripts/cards/SWSH1/Scorbunny_30.py index 8588206..21a7a9a 100644 --- a/spirit/game/scripts/cards/SWSH1/Scorbunny_30.py +++ b/spirit/game/scripts/cards/SWSH1/Scorbunny_30.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import self_energy_discard_attack card = PokemonCardDef( guid="ceab92cb-833e-54da-8923-40c11485179a", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Discard an Energy from this Pok\u00e9mon.", cost={PokemonTypes.FIRE: 1}, damage=30, - effect=unimplemented, + effect=self_energy_discard_attack(count=1), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Seaking_47.py b/spirit/game/scripts/cards/SWSH1/Seaking_47.py index b5f2359..99aed29 100644 --- a/spirit/game/scripts/cards/SWSH1/Seaking_47.py +++ b/spirit/game/scripts/cards/SWSH1/Seaking_47.py @@ -1,6 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def ripping_horn(ctx): + results = await ctx.flip_coins(3, "Ripping Horn") + heads = sum(1 for r in results if r) + if heads == 0: + return + target = ctx.opponent_active() + if target is None or ctx.effects_blocked(target): + return + await ctx.discard_energy_from( + target, heads, + prompt="Choose Energy to discard from the Defending Pokémon") + card = PokemonCardDef( guid="bc700d43-8eac-5498-9d9c-2678301a8efb", key="SWSH1", @@ -23,7 +36,7 @@ card = PokemonCardDef( title="Ripping Horn", game_text="Flip 3 coins. For each heads, discard an Energy from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.WATER: 1}, - effect=unimplemented, + effect=ripping_horn, ), Attack( title="Pierce", diff --git a/spirit/game/scripts/cards/SWSH1/Shellder_40.py b/spirit/game/scripts/cards/SWSH1/Shellder_40.py index 78440a3..9647823 100644 --- a/spirit/game/scripts/cards/SWSH1/Shellder_40.py +++ b/spirit/game/scripts/cards/SWSH1/Shellder_40.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus card = PokemonCardDef( guid="c7e74187-edfd-5c01-a38a-1825a90121a3", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Silicobra_107.py b/spirit/game/scripts/cards/SWSH1/Silicobra_107.py index bafc38e..d092cbf 100644 --- a/spirit/game/scripts/cards/SWSH1/Silicobra_107.py +++ b/spirit/game/scripts/cards/SWSH1/Silicobra_107.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import smokescreen_attack card = PokemonCardDef( guid="5587f71b-a34a-529d-a92e-370ec6e0973e", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Sand Attack", game_text="During your opponent's next turn, if the Defending Pok\u00e9mon tries to attack, your opponent flips a coin. If tails, that attack doesn't happen.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=smokescreen_attack(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Sinistea_89.py b/spirit/game/scripts/cards/SWSH1/Sinistea_89.py index d596684..58d9152 100644 --- a/spirit/game/scripts/cards/SWSH1/Sinistea_89.py +++ b/spirit/game/scripts/cards/SWSH1/Sinistea_89.py @@ -1,6 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def teatime(ctx): + """Each player draws 2 cards.""" + await ctx.draw_cards(2) + await ctx.draw_cards(2, player_id=ctx.opponent_id) + + card = PokemonCardDef( guid="83a0d518-80bc-543a-9287-0a6d4b0759e5", key="SWSH1", @@ -23,7 +30,7 @@ card = PokemonCardDef( title="Teatime", game_text="Each player draws 2 cards.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=teatime, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/SitrusBerry_182.py b/spirit/game/scripts/cards/SWSH1/SitrusBerry_182.py index 0de4167..b6dec3c 100644 --- a/spirit/game/scripts/cards/SWSH1/SitrusBerry_182.py +++ b/spirit/game/scripts/cards/SWSH1/SitrusBerry_182.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import PokemonToolCardDef, Ability, Triggers +from spirit.game.attributes import Rarities, AttrID + + +async def _sitrus_berry(ctx): + pokemon = ctx.source + counters = (ctx.max_hp(pokemon) - pokemon.get_attribute(AttrID.HP, 0)) // 10 + if counters < 3: + return + await ctx.heal(30, target=pokemon) + tool = next((t for t, p in ctx.tools_in_play() if p is pokemon), None) + if tool is not None: + await ctx.discard_cards([tool]) + card = PokemonToolCardDef( guid="6a7b4b2f-2a34-53de-b5bd-6e328a88ef93", @@ -11,5 +23,12 @@ card = PokemonToolCardDef( collector_number=182, set_code="SWSH1", rarity=Rarities.Uncommon, - effect=unimplemented + granted_abilities=[ + Ability( + title="Sitrus Berry", + game_text="At the end of each turn, if the Pok\u00e9mon this card is attached to has 3 or more damage counters on it, heal 30 damage from it and discard this card.", + trigger=Triggers.BETWEEN_TURNS, + effect=_sitrus_berry, + ), + ], ) diff --git a/spirit/game/scripts/cards/SWSH1/Sizzlipede_38.py b/spirit/game/scripts/cards/SWSH1/Sizzlipede_38.py index c07ae1e..9ce2923 100644 --- a/spirit/game/scripts/cards/SWSH1/Sizzlipede_38.py +++ b/spirit/game/scripts/cards/SWSH1/Sizzlipede_38.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="5a8b1d66-0a2c-5f15-ac88-f3285e2b93a9", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 1}, damage=40, damage_operator="x", - effect=unimplemented, + effect=flip_damage(until_tails=True, per_heads=40), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Skorupi_121.py b/spirit/game/scripts/cards/SWSH1/Skorupi_121.py index f7551e3..ebf90b6 100644 --- a/spirit/game/scripts/cards/SWSH1/Skorupi_121.py +++ b/spirit/game/scripts/cards/SWSH1/Skorupi_121.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="7a7e8b1f-b89e-536a-b62f-dd6205d08716", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Poison Sting", game_text="Your opponent's Active Pok\u00e9mon is now Poisoned.", cost={PokemonTypes.DARKNESS: 1}, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED), ), Attack( title="Slashing Claw", diff --git a/spirit/game/scripts/cards/SWSH1/SnorlaxVMAX_142.py b/spirit/game/scripts/cards/SWSH1/SnorlaxVMAX_142.py index 42df716..35f2859 100644 --- a/spirit/game/scripts/cards/SWSH1/SnorlaxVMAX_142.py +++ b/spirit/game/scripts/cards/SWSH1/SnorlaxVMAX_142.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_bench card = PokemonCardDef( guid="43b577aa-2ead-5c2a-bf8b-ada729bca129", @@ -25,7 +26,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=60, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_bench("mine"), 30, base=60), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/SnorlaxVMAX_206.py b/spirit/game/scripts/cards/SWSH1/SnorlaxVMAX_206.py index c1e6ef6..29aa984 100644 --- a/spirit/game/scripts/cards/SWSH1/SnorlaxVMAX_206.py +++ b/spirit/game/scripts/cards/SWSH1/SnorlaxVMAX_206.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_bench card = PokemonCardDef( guid="ab662183-4c5a-5ed3-b7c3-614ad592e4b4", @@ -25,7 +26,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=60, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_bench("mine"), 30, base=60), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/SnorlaxV_141.py b/spirit/game/scripts/cards/SWSH1/SnorlaxV_141.py index 6795b73..a978789 100644 --- a/spirit/game/scripts/cards/SWSH1/SnorlaxV_141.py +++ b/spirit/game/scripts/cards/SWSH1/SnorlaxV_141.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack + + +async def swallow(ctx): + """Heal from this Pokemon the same amount of damage dealt to the Active.""" + dealt = await ctx.deal_damage() + if dealt: + await ctx.heal(dealt, target=ctx.attacker) card = PokemonCardDef( guid="0782209a-a452-56a2-a139-627e4076a465", @@ -23,14 +31,14 @@ card = PokemonCardDef( game_text="Heal from this Pok\u00e9mon the same amount of damage you did to your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 3}, damage=60, - effect=unimplemented, + effect=swallow, ), Attack( title="Falling Down", game_text="This Pok\u00e9mon is now Asleep.", cost={PokemonTypes.COLORLESS: 4}, damage=170, - effect=unimplemented, + effect=condition_attack(self_conditions=(SpecialConditions.ASLEEP,)), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/SnorlaxV_197.py b/spirit/game/scripts/cards/SWSH1/SnorlaxV_197.py index a413125..76a82b5 100644 --- a/spirit/game/scripts/cards/SWSH1/SnorlaxV_197.py +++ b/spirit/game/scripts/cards/SWSH1/SnorlaxV_197.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack + + +async def swallow(ctx): + """Heal from this Pokemon the same amount of damage dealt to the Active.""" + dealt = await ctx.deal_damage() + if dealt: + await ctx.heal(dealt, target=ctx.attacker) card = PokemonCardDef( guid="df8394b8-de15-51b8-92e0-7f3740ea024d", @@ -23,14 +31,14 @@ card = PokemonCardDef( game_text="Heal from this Pok\u00e9mon the same amount of damage you did to your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 3}, damage=60, - effect=unimplemented, + effect=swallow, ), Attack( title="Falling Down", game_text="This Pok\u00e9mon is now Asleep.", cost={PokemonTypes.COLORLESS: 4}, damage=170, - effect=unimplemented, + effect=condition_attack(self_conditions=(SpecialConditions.ASLEEP,)), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/StonjournerVMAX_116.py b/spirit/game/scripts/cards/SWSH1/StonjournerVMAX_116.py index 98ea38c..cde7632 100644 --- a/spirit/game/scripts/cards/SWSH1/StonjournerVMAX_116.py +++ b/spirit/game/scripts/cards/SWSH1/StonjournerVMAX_116.py @@ -1,5 +1,31 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.trainers import is_energy_card + + +def _is_fighting_energy(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_energy_card(card) and PokemonTypes.FIGHTING.value in types + + +async def stone_gift(ctx): + """Attach a Fighting Energy card from your hand to 1 of your Pokémon. + If you do, heal 120 damage from that Pokémon.""" + energies = [c for c in ctx.hand() if _is_fighting_energy(c)] + if not energies: + return + picked = await ctx.choose_cards( + energies, 1, minimum=1, prompt="Choose a Fighting Energy card to attach" + ) + if not picked: + return + target = await ctx.choose_pokemon( + ctx.my_pokemon_in_play(), "Choose a Pokémon to attach the Fighting Energy to" + ) + if target is None: + return + await ctx.attach_energy(picked[0], target) + await ctx.heal(120, target) card = PokemonCardDef( guid="6cbb866b-c7db-52cf-a07c-eebaebb46086", @@ -23,7 +49,7 @@ card = PokemonCardDef( title="Stone Gift", game_text="Attach a Fighting Energy card from your hand to 1 of your Pok\u00e9mon. If you do, heal 120 damage from that Pok\u00e9mon.", cost={PokemonTypes.FIGHTING: 1}, - effect=unimplemented, + effect=stone_gift, ), Attack( title="Max Rockfall", diff --git a/spirit/game/scripts/cards/SWSH1/StonjournerVMAX_205.py b/spirit/game/scripts/cards/SWSH1/StonjournerVMAX_205.py index 3fff447..3fe9d3a 100644 --- a/spirit/game/scripts/cards/SWSH1/StonjournerVMAX_205.py +++ b/spirit/game/scripts/cards/SWSH1/StonjournerVMAX_205.py @@ -1,5 +1,31 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.trainers import is_energy_card + + +def _is_fighting_energy(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_energy_card(card) and PokemonTypes.FIGHTING.value in types + + +async def stone_gift(ctx): + """Attach a Fighting Energy card from your hand to 1 of your Pokémon. + If you do, heal 120 damage from that Pokémon.""" + energies = [c for c in ctx.hand() if _is_fighting_energy(c)] + if not energies: + return + picked = await ctx.choose_cards( + energies, 1, minimum=1, prompt="Choose a Fighting Energy card to attach" + ) + if not picked: + return + target = await ctx.choose_pokemon( + ctx.my_pokemon_in_play(), "Choose a Pokémon to attach the Fighting Energy to" + ) + if target is None: + return + await ctx.attach_energy(picked[0], target) + await ctx.heal(120, target) card = PokemonCardDef( guid="0ff62fcb-96a4-5536-a125-88ffc99128cf", @@ -23,7 +49,7 @@ card = PokemonCardDef( title="Stone Gift", game_text="Attach a Fighting Energy card from your hand to 1 of your Pok\u00e9mon. If you do, heal 120 damage from that Pok\u00e9mon.", cost={PokemonTypes.FIGHTING: 1}, - effect=unimplemented, + effect=stone_gift, ), Attack( title="Max Rockfall", diff --git a/spirit/game/scripts/cards/SWSH1/StonjournerV_115.py b/spirit/game/scripts/cards/SWSH1/StonjournerV_115.py index 4f4cb0c..af332e2 100644 --- a/spirit/game/scripts/cards/SWSH1/StonjournerV_115.py +++ b/spirit/game/scripts/cards/SWSH1/StonjournerV_115.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import protect_next_turn card = PokemonCardDef( guid="57f7a6c7-a994-5788-94ee-a9cd13ff61ab", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, this Pok\u00e9mon takes 20 less damage from attacks (after applying Weakness and Resistance).", cost={PokemonTypes.FIGHTING: 1}, damage=40, - effect=unimplemented, + effect=protect_next_turn(reduce=20), ), Attack( title="Mega Kick", diff --git a/spirit/game/scripts/cards/SWSH1/StonjournerV_193.py b/spirit/game/scripts/cards/SWSH1/StonjournerV_193.py index 8e9e36a..326c5b3 100644 --- a/spirit/game/scripts/cards/SWSH1/StonjournerV_193.py +++ b/spirit/game/scripts/cards/SWSH1/StonjournerV_193.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import protect_next_turn card = PokemonCardDef( guid="2174ff3d-db2d-5c1b-b463-d4000d47b760", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, this Pok\u00e9mon takes 20 less damage from attacks (after applying Weakness and Resistance).", cost={PokemonTypes.FIGHTING: 1}, damage=40, - effect=unimplemented, + effect=protect_next_turn(reduce=20), ), Attack( title="Mega Kick", diff --git a/spirit/game/scripts/cards/SWSH1/Stonjourner_114.py b/spirit/game/scripts/cards/SWSH1/Stonjourner_114.py index 7c4b152..c635054 100644 --- a/spirit/game/scripts/cards/SWSH1/Stonjourner_114.py +++ b/spirit/game/scripts/cards/SWSH1/Stonjourner_114.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack card = PokemonCardDef( guid="c3fae7c1-d3d7-5147-98c0-07d7d6b75eed", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="This Pok\u00e9mon also does 10 damage to itself.", cost={PokemonTypes.FIGHTING: 2}, damage=70, - effect=unimplemented, + effect=recoil_attack(10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Sudowoodo_100.py b/spirit/game/scripts/cards/SWSH1/Sudowoodo_100.py index fe63e79..4b53537 100644 --- a/spirit/game/scripts/cards/SWSH1/Sudowoodo_100.py +++ b/spirit/game/scripts/cards/SWSH1/Sudowoodo_100.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import draw_attack +from spirit.game.card_effects.attacks_common import damage_per, damage_counters_on card = PokemonCardDef( guid="f5463868-6cf5-58e5-9d26-ef04c5886e49", @@ -22,7 +24,7 @@ card = PokemonCardDef( title="Double Draw", game_text="Draw 2 cards.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=draw_attack(2), ), Attack( title="Flail", @@ -30,7 +32,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 1}, damage=10, damage_operator="x", - effect=unimplemented, + effect=damage_per(damage_counters_on("self"), 10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/TapuKokoV_72.py b/spirit/game/scripts/cards/SWSH1/TapuKokoV_72.py index b18158f..cc099b5 100644 --- a/spirit/game/scripts/cards/SWSH1/TapuKokoV_72.py +++ b/spirit/game/scripts/cards/SWSH1/TapuKokoV_72.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import draw_attack +from spirit.game.card_effects.attacks_common import lock_all_attacks + + +async def _thunderous_bolt(ctx): + await ctx.deal_damage() + lock_all_attacks(ctx, ctx.attacker) + card = PokemonCardDef( guid="ecef3f1d-c12c-50f6-85ed-e869f10f9116", @@ -23,14 +31,14 @@ card = PokemonCardDef( game_text="Draw 2 cards.", cost={PokemonTypes.LIGHTNING: 1}, damage=20, - effect=unimplemented, + effect=draw_attack(2), ), Attack( title="Thunderous Bolt", game_text="During your next turn, this Pok\u00e9mon can't attack.", cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 1}, damage=200, - effect=unimplemented, + effect=_thunderous_bolt, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Thievul_126.py b/spirit/game/scripts/cards/SWSH1/Thievul_126.py index 36bcb6b..ed350de 100644 --- a/spirit/game/scripts/cards/SWSH1/Thievul_126.py +++ b/spirit/game/scripts/cards/SWSH1/Thievul_126.py @@ -1,6 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def thief(ctx): + """20 damage. Reveal the opponent's hand and put a card found there on + the bottom of their deck.""" + await ctx.deal_damage() + opponent_hand = await ctx.reveal_hand(of_player=ctx.opponent_id) + if not opponent_hand: + return + picks = await ctx.choose_cards( + opponent_hand, 1, minimum=1, + prompt="Choose a card to put on the bottom of your opponent's deck", + ) + for card_entity in picks: + await ctx.put_on_bottom_of_deck(card_entity) + + card = PokemonCardDef( guid="040476cc-db4a-5574-90d8-894d101e3325", key="SWSH1", @@ -24,7 +40,7 @@ card = PokemonCardDef( game_text="Your opponent reveals their hand. Choose a card you find there and put it on the bottom of their deck.", cost={PokemonTypes.DARKNESS: 1}, damage=20, - effect=unimplemented, + effect=thief, ), Attack( title="Darkness Fang", diff --git a/spirit/game/scripts/cards/SWSH1/Thwackey_12.py b/spirit/game/scripts/cards/SWSH1/Thwackey_12.py index 402ac75..9f392aa 100644 --- a/spirit/game/scripts/cards/SWSH1/Thwackey_12.py +++ b/spirit/game/scripts/cards/SWSH1/Thwackey_12.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage +from spirit.game.card_effects.support_common import gust_attack card = PokemonCardDef( guid="e75359b9-ec76-5414-bc7b-2841f4b97fdf", @@ -23,7 +25,7 @@ card = PokemonCardDef( title="Taunt", game_text="Switch 1 of your opponent's Benched Pok\u00e9mon with their Active Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=gust_attack(), ), Attack( title="Double Hit", @@ -31,7 +33,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 2, PokemonTypes.COLORLESS: 1}, damage=60, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=2, per_heads=60), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Thwackey_13.py b/spirit/game/scripts/cards/SWSH1/Thwackey_13.py index a6f5405..bb98090 100644 --- a/spirit/game/scripts/cards/SWSH1/Thwackey_13.py +++ b/spirit/game/scripts/cards/SWSH1/Thwackey_13.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus card = PokemonCardDef( guid="9509e516-3340-54d5-9bc2-fe553be702bb", @@ -30,7 +31,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 2, PokemonTypes.COLORLESS: 1}, damage=50, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/TorkoalV_188.py b/spirit/game/scripts/cards/SWSH1/TorkoalV_188.py index 7fc2d67..2a52ff8 100644 --- a/spirit/game/scripts/cards/SWSH1/TorkoalV_188.py +++ b/spirit/game/scripts/cards/SWSH1/TorkoalV_188.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import discard_opponent_energy_attack +from spirit.game.card_effects.pokemon import is_energy_card, energy_provides_type + + +async def _combustion_pillar(ctx): + top = ctx.deck_top(1) + bonus = 0 + if top: + card = top[0] + await ctx.discard_cards([card]) + if is_energy_card(card) and energy_provides_type(card, PokemonTypes.FIRE.value): + bonus = 90 + await ctx.deal_damage(90 + bonus) + card = PokemonCardDef( guid="10c5b818-6abf-50d7-a391-7ad6a0d31e46", @@ -24,14 +38,14 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=90, damage_operator="+", - effect=unimplemented, + effect=_combustion_pillar, ), Attack( title="Steam Crush", game_text="Discard 2 Energy from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.FIRE: 3, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + effect=discard_opponent_energy_attack(count=2), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/TorkoalV_24.py b/spirit/game/scripts/cards/SWSH1/TorkoalV_24.py index a3b6c41..e25cf21 100644 --- a/spirit/game/scripts/cards/SWSH1/TorkoalV_24.py +++ b/spirit/game/scripts/cards/SWSH1/TorkoalV_24.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import discard_opponent_energy_attack +from spirit.game.card_effects.pokemon import is_energy_card, energy_provides_type + + +async def _combustion_pillar(ctx): + top = ctx.deck_top(1) + bonus = 0 + if top: + card = top[0] + await ctx.discard_cards([card]) + if is_energy_card(card) and energy_provides_type(card, PokemonTypes.FIRE.value): + bonus = 90 + await ctx.deal_damage(90 + bonus) + card = PokemonCardDef( guid="7c706994-46ea-57df-8336-9c1dd1563e12", @@ -24,14 +38,14 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=90, damage_operator="+", - effect=unimplemented, + effect=_combustion_pillar, ), Attack( title="Steam Crush", game_text="Discard 2 Energy from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.FIRE: 3, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + effect=discard_opponent_energy_attack(count=2), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Toxicroak_124.py b/spirit/game/scripts/cards/SWSH1/Toxicroak_124.py index 986f049..bde2789 100644 --- a/spirit/game/scripts/cards/SWSH1/Toxicroak_124.py +++ b/spirit/game/scripts/cards/SWSH1/Toxicroak_124.py @@ -1,6 +1,7 @@ from spirit.game.card_effects.pokemon import more_poison -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions card = PokemonCardDef( guid="5ba53c61-dc4c-5619-bcc8-ea283c54e746", @@ -31,7 +32,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Poisoned.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 2}, damage=70, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED, flip=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Turtonator_29.py b/spirit/game/scripts/cards/SWSH1/Turtonator_29.py index d17fb9f..2369d42 100644 --- a/spirit/game/scripts/cards/SWSH1/Turtonator_29.py +++ b/spirit/game/scripts/cards/SWSH1/Turtonator_29.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import self_energy_discard_attack card = PokemonCardDef( guid="70e56c17-ebf7-5636-a7c3-f95f38c29d9e", @@ -28,7 +29,7 @@ card = PokemonCardDef( game_text="Discard 2 Energy from this Pok\u00e9mon.", cost={PokemonTypes.FIRE: 3, PokemonTypes.COLORLESS: 1}, damage=150, - effect=unimplemented, + effect=self_energy_discard_attack(count=2), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/VictiniV_25.py b/spirit/game/scripts/cards/SWSH1/VictiniV_25.py index d3d9e01..f5eb41a 100644 --- a/spirit/game/scripts/cards/SWSH1/VictiniV_25.py +++ b/spirit/game/scripts/cards/SWSH1/VictiniV_25.py @@ -1,5 +1,38 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per +from spirit.game.card_effects.pokemon import energy_provides_type +from spirit.game.card_effects.support_common import distribute_energy +from spirit.game.session.legal_actions import energy_provided_count + + +async def spreading_flames(ctx): + """Attach up to 3 Fire Energy cards from your discard pile to your + Pokemon, any way you like.""" + cards = [c for c in ctx.discard_pile() if energy_provides_type(c, PokemonTypes.FIRE.value)] + if not cards: + return + picks = await ctx.choose_cards( + cards, 3, minimum=0, + prompt="Choose up to 3 Fire Energy cards from your discard pile to attach.", + ) + if not picks: + return + candidates = ctx.my_pokemon_in_play() + if not candidates: + return + await distribute_energy(ctx, picks, candidates) + + +def _both_actives_energy(ctx) -> int: + total = 0 + for pokemon in (ctx.my_active(), ctx.opponent_active()): + if pokemon is None: + continue + for energy in ctx.attached_energies(pokemon): + total += energy_provided_count(energy) + return total + card = PokemonCardDef( guid="3b65f1a3-5299-5b0e-8a5b-524bf177645b", @@ -20,17 +53,17 @@ card = PokemonCardDef( abilities=[ Attack( title="Spreading Flames", - game_text="Attach up to 3 Fire Energy cards from your discard pile to your Pok\u00e9mon in any way you like.", + game_text="Attach up to 3 Fire Energy cards from your discard pile to your Pokémon in any way you like.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=spreading_flames, ), Attack( title="Energy Burst", - game_text="This attack does 30 damage for each Energy attached to both Active Pok\u00e9mon.", + game_text="This attack does 30 damage for each Energy attached to both Active Pokémon.", cost={PokemonTypes.FIRE: 2}, damage=30, damage_operator="x", - effect=unimplemented, + effect=damage_per(_both_actives_energy, 30), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH1/VitalityBand_185.py b/spirit/game/scripts/cards/SWSH1/VitalityBand_185.py index 9deb2ad..36069a5 100644 --- a/spirit/game/scripts/cards/SWSH1/VitalityBand_185.py +++ b/spirit/game/scripts/cards/SWSH1/VitalityBand_185.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented +from spirit.game.data_utils import PokemonToolCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.passives_common import typed_damage_boost_tool card = PokemonToolCardDef( guid="ddddd6e8-b9a3-52c9-8b10-4a89518534ef", @@ -11,5 +12,5 @@ card = PokemonToolCardDef( collector_number=185, set_code="SWSH1", rarity=Rarities.Uncommon, - effect=unimplemented + passive=typed_damage_boost_tool(lambda t: True, 10), ) diff --git a/spirit/game/scripts/cards/SWSH1/Whimsicott_6.py b/spirit/game/scripts/cards/SWSH1/Whimsicott_6.py index ea556ea..017529c 100644 --- a/spirit/game/scripts/cards/SWSH1/Whimsicott_6.py +++ b/spirit/game/scripts/cards/SWSH1/Whimsicott_6.py @@ -1,5 +1,26 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import full_stack + + +async def cotton_ride(ctx): + """Flip a coin. If heads, opponent shuffles their Active and all + attached cards into their deck.""" + heads = (await ctx.flip_coins(1, "Cotton Ride"))[0] + if not heads: + return + target = ctx.opponent_active() + if target is None or ctx.effects_blocked(target): + return + await ctx.shuffle_into_deck(full_stack(target), ctx.opponent_id) + + async def _promote(): + if not await ctx.session._promote_new_active(ctx.opponent_id): + screen_name = ctx.session.players[ctx.opponent_id].screen_name + await ctx.session.end_game( + ctx.player_id, f"{screen_name} has no Pokémon left") + ctx.deferred_actions.append(_promote) + card = PokemonCardDef( guid="a9813aa3-4044-50a3-88d4-ef9b6646f22f", @@ -23,7 +44,7 @@ card = PokemonCardDef( title="Cotton Ride", game_text="Flip a coin. If heads, your opponent shuffles their Active Pok\u00e9mon and all attached cards into their deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=cotton_ride, ), Attack( title="Leaf Step", diff --git a/spirit/game/scripts/cards/SWSH1/WobbuffetV_191.py b/spirit/game/scripts/cards/SWSH1/WobbuffetV_191.py index d5181a4..fd807fc 100644 --- a/spirit/game/scripts/cards/SWSH1/WobbuffetV_191.py +++ b/spirit/game/scripts/cards/SWSH1/WobbuffetV_191.py @@ -1,5 +1,26 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_counters_on + + +async def gritty_comeback(ctx): + """Switch all damage counters on this Pokemon with those on the opponent's Active.""" + defender = ctx.defender + if defender is None or ctx.effects_blocked(defender): + return + self_counters = damage_counters_on("self")(ctx) + opp_counters = damage_counters_on("defender")(ctx) + await ctx.set_damage_counters(ctx.source, opp_counters) + await ctx.set_damage_counters(defender, self_counters) + + +async def shadow_bind(ctx): + """70 damage. During your opponent's next turn, the Defending Pokemon can't retreat.""" + await ctx.deal_damage() + defender = ctx.defender + if defender is not None and not ctx.effects_blocked(defender): + ctx.lock_retreat(defender) + card = PokemonCardDef( guid="fa5391b4-9541-5280-9740-959adc2c715a", @@ -23,14 +44,14 @@ card = PokemonCardDef( title="Gritty Comeback", game_text="Switch all damage counters on this Pok\u00e9mon with those on your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + effect=gritty_comeback, ), Attack( title="Shadow Bind", game_text="During your opponent's next turn, the Defending Pok\u00e9mon can't retreat.", cost={PokemonTypes.PSYCHIC: 2}, damage=70, - effect=unimplemented, + effect=shadow_bind, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/WobbuffetV_86.py b/spirit/game/scripts/cards/SWSH1/WobbuffetV_86.py index 08245ff..cf9198a 100644 --- a/spirit/game/scripts/cards/SWSH1/WobbuffetV_86.py +++ b/spirit/game/scripts/cards/SWSH1/WobbuffetV_86.py @@ -1,5 +1,26 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_counters_on + + +async def gritty_comeback(ctx): + """Switch all damage counters on this Pokemon with those on the opponent's Active.""" + defender = ctx.defender + if defender is None or ctx.effects_blocked(defender): + return + self_counters = damage_counters_on("self")(ctx) + opp_counters = damage_counters_on("defender")(ctx) + await ctx.set_damage_counters(ctx.source, opp_counters) + await ctx.set_damage_counters(defender, self_counters) + + +async def shadow_bind(ctx): + """70 damage. During your opponent's next turn, the Defending Pokemon can't retreat.""" + await ctx.deal_damage() + defender = ctx.defender + if defender is not None and not ctx.effects_blocked(defender): + ctx.lock_retreat(defender) + card = PokemonCardDef( guid="23132b90-ec25-5146-9f8c-785d228719cb", @@ -23,14 +44,14 @@ card = PokemonCardDef( title="Gritty Comeback", game_text="Switch all damage counters on this Pok\u00e9mon with those on your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + effect=gritty_comeback, ), Attack( title="Shadow Bind", game_text="During your opponent's next turn, the Defending Pok\u00e9mon can't retreat.", cost={PokemonTypes.PSYCHIC: 2}, damage=70, - effect=unimplemented, + effect=shadow_bind, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Wooloo_152.py b/spirit/game/scripts/cards/SWSH1/Wooloo_152.py index 3189d7c..e9d7fba 100644 --- a/spirit/game/scripts/cards/SWSH1/Wooloo_152.py +++ b/spirit/game/scripts/cards/SWSH1/Wooloo_152.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import protect_next_turn card = PokemonCardDef( guid="1667d025-d514-599b-a796-aaf5efc12f88", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, this Pok\u00e9mon takes 10 less damage from attacks (after applying Weakness and Resistance).", cost={PokemonTypes.COLORLESS: 2}, damage=30, - effect=unimplemented, + effect=protect_next_turn(reduce=10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/Yamper_73.py b/spirit/game/scripts/cards/SWSH1/Yamper_73.py index c49f7d1..f6be347 100644 --- a/spirit/game/scripts/cards/SWSH1/Yamper_73.py +++ b/spirit/game/scripts/cards/SWSH1/Yamper_73.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import gust_attack card = PokemonCardDef( guid="39c6ff6d-7031-51e2-8ae6-84feab02af01", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Roar", game_text="Your opponent switches their Active Pok\u00e9mon with 1 of their Benched Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=gust_attack(opponent_chooses=True), ), Attack( title="Static Shock", diff --git a/spirit/game/scripts/cards/SWSH1/ZacianV_138.py b/spirit/game/scripts/cards/SWSH1/ZacianV_138.py index 5db25a5..56aa2dc 100644 --- a/spirit/game/scripts/cards/SWSH1/ZacianV_138.py +++ b/spirit/game/scripts/cards/SWSH1/ZacianV_138.py @@ -1,5 +1,28 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.trainers import is_metal_energy_card + + +async def intrepid_sword(ctx): + """Once per turn: look at the top 3, attach any Metal Energy found there + to this Pokémon, the rest to hand. Ends your turn.""" + top = ctx.deck_top(3) + if not top: + return + metal = [c for c in top if is_metal_energy_card(c)] + picks = [] + if metal: + picks = await ctx.choose_cards( + metal, len(metal), minimum=0, + prompt="Attach any number of Metal Energy cards to this Pokémon.", + display_cards=top, + ) + for energy in picks: + await ctx.attach_energy(energy, ctx.source) + rest = [c for c in top if c not in picks] + if rest: + await ctx.put_in_hand(rest, reveal=False) + card = PokemonCardDef( guid="b370c27e-c08b-5d0f-ab21-42fafe1a3e32", @@ -22,14 +45,16 @@ card = PokemonCardDef( Ability( title="Intrepid Sword", game_text="Once during your turn, you may look at the top 3 cards of your deck and attach any number of Metal Energy cards you find there to this Pok\u00e9mon. Put the other cards into your hand. If you use this Ability, your turn ends.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + ends_turn=True, + effect=intrepid_sword, ), Attack( title="Brave Blade", game_text="During your next turn, this Pok\u00e9mon can't attack.", cost={PokemonTypes.METAL: 3}, damage=230, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/ZacianV_195.py b/spirit/game/scripts/cards/SWSH1/ZacianV_195.py index 7e816af..c3abd14 100644 --- a/spirit/game/scripts/cards/SWSH1/ZacianV_195.py +++ b/spirit/game/scripts/cards/SWSH1/ZacianV_195.py @@ -1,5 +1,28 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.trainers import is_metal_energy_card + + +async def intrepid_sword(ctx): + """Once per turn: look at the top 3, attach any Metal Energy found there + to this Pokémon, the rest to hand. Ends your turn.""" + top = ctx.deck_top(3) + if not top: + return + metal = [c for c in top if is_metal_energy_card(c)] + picks = [] + if metal: + picks = await ctx.choose_cards( + metal, len(metal), minimum=0, + prompt="Attach any number of Metal Energy cards to this Pokémon.", + display_cards=top, + ) + for energy in picks: + await ctx.attach_energy(energy, ctx.source) + rest = [c for c in top if c not in picks] + if rest: + await ctx.put_in_hand(rest, reveal=False) + card = PokemonCardDef( guid="2ad92cc8-f8b9-5fac-b33a-85e1464b5467", @@ -21,15 +44,17 @@ card = PokemonCardDef( abilities=[ Ability( title="Intrepid Sword", - game_text="Once during your turn, you may look at the top 3 cards of your deck and attach any number of Metal Energy cards you find there to this Pok\u00e9mon. Put the other cards into your hand. If you use this Ability, your turn ends.", - effect=unimplemented, + game_text="Once during your turn, you may look at the top 3 cards of your deck and attach any number of Metal Energy cards you find there to this Pokémon. Put the other cards into your hand. If you use this Ability, your turn ends.", + activation=Activations.ONCE_PER_TURN, + ends_turn=True, + effect=intrepid_sword, ), Attack( title="Brave Blade", - game_text="During your next turn, this Pok\u00e9mon can't attack.", + game_text="During your next turn, this Pokémon can't attack.", cost={PokemonTypes.METAL: 3}, damage=230, - effect=unimplemented, + locks_next_turn=True, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH1/ZacianV_211.py b/spirit/game/scripts/cards/SWSH1/ZacianV_211.py index d3954b8..2e6f25d 100644 --- a/spirit/game/scripts/cards/SWSH1/ZacianV_211.py +++ b/spirit/game/scripts/cards/SWSH1/ZacianV_211.py @@ -1,5 +1,28 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.trainers import is_metal_energy_card + + +async def intrepid_sword(ctx): + """Once per turn: look at the top 3, attach any Metal Energy found there + to this Pokémon, the rest to hand. Ends your turn.""" + top = ctx.deck_top(3) + if not top: + return + metal = [c for c in top if is_metal_energy_card(c)] + picks = [] + if metal: + picks = await ctx.choose_cards( + metal, len(metal), minimum=0, + prompt="Attach any number of Metal Energy cards to this Pokémon.", + display_cards=top, + ) + for energy in picks: + await ctx.attach_energy(energy, ctx.source) + rest = [c for c in top if c not in picks] + if rest: + await ctx.put_in_hand(rest, reveal=False) + card = PokemonCardDef( guid="66be087c-64c1-5a49-8a50-9d889d582c18", @@ -21,15 +44,17 @@ card = PokemonCardDef( abilities=[ Ability( title="Intrepid Sword", - game_text="Once during your turn, you may look at the top 3 cards of your deck and attach any number of Metal Energy cards you find there to this Pok\u00e9mon. Put the other cards into your hand. If you use this Ability, your turn ends.", - effect=unimplemented, + game_text="Once during your turn, you may look at the top 3 cards of your deck and attach any number of Metal Energy cards you find there to this Pokémon. Put the other cards into your hand. If you use this Ability, your turn ends.", + activation=Activations.ONCE_PER_TURN, + ends_turn=True, + effect=intrepid_sword, ), Attack( title="Brave Blade", - game_text="During your next turn, this Pok\u00e9mon can't attack.", + game_text="During your next turn, this Pokémon can't attack.", cost={PokemonTypes.METAL: 3}, damage=230, - effect=unimplemented, + locks_next_turn=True, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH1/ZamazentaV_139.py b/spirit/game/scripts/cards/SWSH1/ZamazentaV_139.py index ea70d91..3232cce 100644 --- a/spirit/game/scripts/cards/SWSH1/ZamazentaV_139.py +++ b/spirit/game/scripts/cards/SWSH1/ZamazentaV_139.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import discard_opponent_energy_attack +from spirit.game.card_effects.passives_common import prevent_damage_when +from spirit.game.card_effects.pokemon import is_pokemon_vmax + +_dauntless_shield = prevent_damage_when( + lambda calc, c: calc.target is c and calc.attacker is not None + and is_pokemon_vmax(calc.attacker.archetype_id) +) card = PokemonCardDef( guid="61323c30-4785-5617-9967-087dd01de004", @@ -22,14 +30,14 @@ card = PokemonCardDef( Ability( title="Dauntless Shield", game_text="Prevent all damage done to this Pok\u00e9mon by attacks from your opponent's Pok\u00e9mon VMAX.", - effect=unimplemented, + passive=_dauntless_shield, ), Attack( title="Assault Tackle", game_text="Discard a Special Energy from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.METAL: 2, PokemonTypes.COLORLESS: 1}, damage=130, - effect=unimplemented, + effect=discard_opponent_energy_attack(special_only=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/ZamazentaV_196.py b/spirit/game/scripts/cards/SWSH1/ZamazentaV_196.py index 8a700eb..8dc332a 100644 --- a/spirit/game/scripts/cards/SWSH1/ZamazentaV_196.py +++ b/spirit/game/scripts/cards/SWSH1/ZamazentaV_196.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import discard_opponent_energy_attack +from spirit.game.card_effects.passives_common import prevent_damage_when +from spirit.game.card_effects.pokemon import is_pokemon_vmax + +_dauntless_shield = prevent_damage_when( + lambda calc, c: calc.target is c and calc.attacker is not None + and is_pokemon_vmax(calc.attacker.archetype_id) +) card = PokemonCardDef( guid="aaae6968-9dcc-5b93-970a-7af10408f106", @@ -22,14 +30,14 @@ card = PokemonCardDef( Ability( title="Dauntless Shield", game_text="Prevent all damage done to this Pok\u00e9mon by attacks from your opponent's Pok\u00e9mon VMAX.", - effect=unimplemented, + passive=_dauntless_shield, ), Attack( title="Assault Tackle", game_text="Discard a Special Energy from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.METAL: 2, PokemonTypes.COLORLESS: 1}, damage=130, - effect=unimplemented, + effect=discard_opponent_energy_attack(special_only=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH1/ZamazentaV_212.py b/spirit/game/scripts/cards/SWSH1/ZamazentaV_212.py index f959565..478f9f2 100644 --- a/spirit/game/scripts/cards/SWSH1/ZamazentaV_212.py +++ b/spirit/game/scripts/cards/SWSH1/ZamazentaV_212.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import discard_opponent_energy_attack +from spirit.game.card_effects.passives_common import prevent_damage_when +from spirit.game.card_effects.pokemon import is_pokemon_vmax + +_dauntless_shield = prevent_damage_when( + lambda calc, c: calc.target is c and calc.attacker is not None + and is_pokemon_vmax(calc.attacker.archetype_id) +) card = PokemonCardDef( guid="7256e884-9c4d-5004-aa26-cc71c361f26d", @@ -22,14 +30,14 @@ card = PokemonCardDef( Ability( title="Dauntless Shield", game_text="Prevent all damage done to this Pok\u00e9mon by attacks from your opponent's Pok\u00e9mon VMAX.", - effect=unimplemented, + passive=_dauntless_shield, ), Attack( title="Assault Tackle", game_text="Discard a Special Energy from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.METAL: 2, PokemonTypes.COLORLESS: 1}, damage=130, - effect=unimplemented, + effect=discard_opponent_energy_attack(special_only=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/Absol_97.py b/spirit/game/scripts/cards/SWSH10/Absol_97.py index 3205a31..6e21776 100644 --- a/spirit/game/scripts/cards/SWSH10/Absol_97.py +++ b/spirit/game/scripts/cards/SWSH10/Absol_97.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import ( + bonus_if, damage_all_opponents, has_damage, +) card = PokemonCardDef( guid="3608cb47-17b9-56f8-a268-f6a58a8692a5", @@ -20,17 +23,17 @@ card = PokemonCardDef( abilities=[ Attack( title="Swirling Disaster", - game_text="This attack does 10 damage to each of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", + game_text="This attack does 10 damage to each of your opponent's Pokémon. (Don't apply Weakness and Resistance for Benched Pokémon.)", cost={PokemonTypes.DARKNESS: 1}, - effect=unimplemented, + effect=damage_all_opponents(10), ), Attack( title="Claw Rend", - game_text="If your opponent's Active Pok\u00e9mon already has any damage counters on it, this attack does 70 more damage.", + game_text="If your opponent's Active Pokémon already has any damage counters on it, this attack does 70 more damage.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 1}, damage=50, damage_operator="+", - effect=unimplemented, + effect=bonus_if(has_damage("defender"), 70), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH10/Azelf_67.py b/spirit/game/scripts/cards/SWSH10/Azelf_67.py index 9f48547..fe69c96 100644 --- a/spirit/game/scripts/cards/SWSH10/Azelf_67.py +++ b/spirit/game/scripts/cards/SWSH10/Azelf_67.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.data_utils import PokemonCardDef, Attack +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions card = PokemonCardDef( guid="c02f3522-4ca8-582f-b942-0ea1060e462a", @@ -21,10 +22,10 @@ card = PokemonCardDef( abilities=[ Attack( title="Mind Bend", - game_text="Your opponent's Active Pok\u00e9mon is now Confused.", + game_text="Your opponent's Active Pokémon is now Confused.", cost={PokemonTypes.PSYCHIC: 1}, damage=30, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH10/Bastiodon_110.py b/spirit/game/scripts/cards/SWSH10/Bastiodon_110.py index ec87a92..edf998a 100644 --- a/spirit/game/scripts/cards/SWSH10/Bastiodon_110.py +++ b/spirit/game/scripts/cards/SWSH10/Bastiodon_110.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, is_pokemon_v from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack +from spirit.game.card_effects.passives_common import takes_less_passive card = PokemonCardDef( guid="7384f801-e9f5-559f-92f5-2f16b740cb32", @@ -23,14 +25,15 @@ card = PokemonCardDef( Ability( title="Primal Fortress", game_text="Your Pok\u00e9mon take 30 less damage from attacks from your opponent's Pok\u00e9mon V (after applying Weakness and Resistance).", - effect=unimplemented, + passive=takes_less_passive(30, protects="team", + attacker_pred=lambda p: is_pokemon_v(p.archetype_id)), ), Attack( title="Iron Tackle", game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.METAL: 2, PokemonTypes.COLORLESS: 1}, damage=180, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/BeedrillV_1.py b/spirit/game/scripts/cards/SWSH10/BeedrillV_1.py index 65a8641..5cf00b8 100644 --- a/spirit/game/scripts/cards/SWSH10/BeedrillV_1.py +++ b/spirit/game/scripts/cards/SWSH10/BeedrillV_1.py @@ -1,5 +1,24 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage + + +async def swarming_sting(ctx): + """50 damage to 1 chosen opposing Pokémon for each Beedrill V you have in play.""" + count = sum( + 1 for p in ctx.my_pokemon_in_play() + if p.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) == "BeedrillV" + ) + amount = 50 * count + if amount <= 0: + return + candidates = ctx.opponent_pokemon_in_play() + if not candidates: + return + target = await ctx.choose_pokemon(candidates, "Choose 1 of your opponent's Pokémon") + if target is not None: + await ctx.deal_damage(amount, target=target) + card = PokemonCardDef( guid="56670779-5c0f-54d8-a41b-323f334efaf8", @@ -24,13 +43,13 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1}, damage=40, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=2, per_heads=40), ), Attack( title="Swarming Sting", game_text="This attack does 50 damage to 1 of your opponent's Pok\u00e9mon for each of your Beedrill V in play. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.GRASS: 2, PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=swarming_sting, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/BeedrillV_160.py b/spirit/game/scripts/cards/SWSH10/BeedrillV_160.py index cdfff8f..9e044ea 100644 --- a/spirit/game/scripts/cards/SWSH10/BeedrillV_160.py +++ b/spirit/game/scripts/cards/SWSH10/BeedrillV_160.py @@ -1,5 +1,24 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage + + +async def swarming_sting(ctx): + """50 damage to 1 chosen opposing Pokémon for each Beedrill V you have in play.""" + count = sum( + 1 for p in ctx.my_pokemon_in_play() + if p.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) == "BeedrillV" + ) + amount = 50 * count + if amount <= 0: + return + candidates = ctx.opponent_pokemon_in_play() + if not candidates: + return + target = await ctx.choose_pokemon(candidates, "Choose 1 of your opponent's Pokémon") + if target is not None: + await ctx.deal_damage(amount, target=target) + card = PokemonCardDef( guid="c9cab229-fbc2-50be-ba16-37a9eb097305", @@ -24,13 +43,13 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1}, damage=40, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=2, per_heads=40), ), Attack( title="Swarming Sting", game_text="This attack does 50 damage to 1 of your opponent's Pok\u00e9mon for each of your Beedrill V in play. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.GRASS: 2, PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=swarming_sting, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/BeedrillV_161.py b/spirit/game/scripts/cards/SWSH10/BeedrillV_161.py index 9067141..ad07c97 100644 --- a/spirit/game/scripts/cards/SWSH10/BeedrillV_161.py +++ b/spirit/game/scripts/cards/SWSH10/BeedrillV_161.py @@ -1,5 +1,24 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage + + +async def swarming_sting(ctx): + """50 damage to 1 chosen opposing Pokémon for each Beedrill V you have in play.""" + count = sum( + 1 for p in ctx.my_pokemon_in_play() + if p.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) == "BeedrillV" + ) + amount = 50 * count + if amount <= 0: + return + candidates = ctx.opponent_pokemon_in_play() + if not candidates: + return + target = await ctx.choose_pokemon(candidates, "Choose 1 of your opponent's Pokémon") + if target is not None: + await ctx.deal_damage(amount, target=target) + card = PokemonCardDef( guid="2ad57dde-cafd-53ea-a54f-2b397dca584f", @@ -24,13 +43,13 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1}, damage=40, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=2, per_heads=40), ), Attack( title="Swarming Sting", game_text="This attack does 50 damage to 1 of your opponent's Pok\u00e9mon for each of your Beedrill V in play. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.GRASS: 2, PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=swarming_sting, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/Bisharp_116.py b/spirit/game/scripts/cards/SWSH10/Bisharp_116.py index 128c15a..c5f1a45 100644 --- a/spirit/game/scripts/cards/SWSH10/Bisharp_116.py +++ b/spirit/game/scripts/cards/SWSH10/Bisharp_116.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID + + +async def vengeful_cut(ctx): + """30 more damage for each damage counter on all of your Benched + Pawniard.""" + counters = 0 + for pokemon in ctx.my_bench(): + if pokemon.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) == "Pawniard": + counters += max( + 0, (ctx.max_hp(pokemon) - pokemon.get_attribute(AttrID.HP, 0)) // 10 + ) + await ctx.deal_damage(30 + 30 * counters) + card = PokemonCardDef( guid="0db2735c-68ba-5f30-8a8c-477790d124da", @@ -26,7 +39,7 @@ card = PokemonCardDef( cost={PokemonTypes.METAL: 1}, damage=30, damage_operator="+", - effect=unimplemented, + effect=vengeful_cut, ), Attack( title="Slicing Blade", diff --git a/spirit/game/scripts/cards/SWSH10/Bronzong_112.py b/spirit/game/scripts/cards/SWSH10/Bronzong_112.py index 9f7ced1..2c3ecab 100644 --- a/spirit/game/scripts/cards/SWSH10/Bronzong_112.py +++ b/spirit/game/scripts/cards/SWSH10/Bronzong_112.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import prevent_damage_when + + +def _heatproof_pred(calc, carrier): + if calc.target is not carrier: + return False + types = calc.attacker.get_attribute(AttrID.POKEMON_TYPES) or [] + return PokemonTypes.FIRE.value in types + card = PokemonCardDef( guid="79c18e5d-69ef-53e6-a6aa-91bbbc1d5153", @@ -22,8 +31,8 @@ card = PokemonCardDef( abilities=[ Ability( title="Heatproof", - game_text="Prevent all damage done to this Pok\u00e9mon by attacks from your opponent's Fire Pok\u00e9mon.", - effect=unimplemented, + game_text="Prevent all damage done to this Pokémon by attacks from your opponent's Fire Pokémon.", + passive=prevent_damage_when(_heatproof_pred), ), Attack( title="Hammer In", @@ -31,4 +40,4 @@ card = PokemonCardDef( damage=100, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH10/Bronzor_111.py b/spirit/game/scripts/cards/SWSH10/Bronzor_111.py index 6cf35cd..b794780 100644 --- a/spirit/game/scripts/cards/SWSH10/Bronzor_111.py +++ b/spirit/game/scripts/cards/SWSH10/Bronzor_111.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="1369457a-feda-5c44-b697-de4dd9fb0ea3", @@ -21,10 +22,10 @@ card = PokemonCardDef( abilities=[ Attack( title="Metal Press", - game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", + game_text="Flip a coin. If heads, your opponent's Active Pokémon is now Paralyzed.", cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 1}, damage=20, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH10/CancelingCologne_136.py b/spirit/game/scripts/cards/SWSH10/CancelingCologne_136.py index b0e478e..72bd9c5 100644 --- a/spirit/game/scripts/cards/SWSH10/CancelingCologne_136.py +++ b/spirit/game/scripts/cards/SWSH10/CancelingCologne_136.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.passives_common import ability_lock_passive + + +async def canceling_cologne(ctx): + """Until the end of your turn, your opponent's Active Pokemon has no + Abilities (including one that comes into play this turn).""" + target = ctx.opponent_active() + if target is None: + return + ctx.add_temporary_passive( + target, ability_lock_passive(lambda p, c: p is target), + expires_after_turn=ctx.session.turn_state.turn_number, + ) + card = ItemCardDef( guid="6dfa3e30-4350-5602-8ff0-83f57687e48d", @@ -11,5 +25,5 @@ card = ItemCardDef( collector_number=136, set_code="SWSH10", rarity=Rarities.Uncommon, - effect=unimplemented + effect=canceling_cologne ) diff --git a/spirit/game/scripts/cards/SWSH10/Chatot_129.py b/spirit/game/scripts/cards/SWSH10/Chatot_129.py index f82d26d..6c6fc07 100644 --- a/spirit/game/scripts/cards/SWSH10/Chatot_129.py +++ b/spirit/game/scripts/cards/SWSH10/Chatot_129.py @@ -1,4 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.support_common import draw_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -23,14 +25,14 @@ card = PokemonCardDef( title="Collect", game_text="Draw a card.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=draw_attack(1), ), Attack( title="Jabber On", game_text="During your opponent's next turn, the Defending Pok\u00e9mon can't retreat.", cost={PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=condition_attack(no_retreat=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/Choy_137.py b/spirit/game/scripts/cards/SWSH10/Choy_137.py index 6f918e6..cacafda 100644 --- a/spirit/game/scripts/cards/SWSH10/Choy_137.py +++ b/spirit/game/scripts/cards/SWSH10/Choy_137.py @@ -1,6 +1,14 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities + +async def choy(ctx): + """Each player reveals their hand. Draw 3 cards.""" + await ctx.reveal_hand(ctx.player_id, ctx.opponent_id) + await ctx.reveal_hand(ctx.opponent_id, ctx.player_id) + await ctx.draw_cards(3) + + card = SupporterCardDef( guid="0d575500-29ba-57e6-9a41-b7c3d68cbaff", key="SWSH10", @@ -11,5 +19,5 @@ card = SupporterCardDef( collector_number=137, set_code="SWSH10", rarity=Rarities.Uncommon, - effect=unimplemented + effect=choy, ) diff --git a/spirit/game/scripts/cards/SWSH10/Choy_182.py b/spirit/game/scripts/cards/SWSH10/Choy_182.py index 7e3db80..8195b16 100644 --- a/spirit/game/scripts/cards/SWSH10/Choy_182.py +++ b/spirit/game/scripts/cards/SWSH10/Choy_182.py @@ -1,6 +1,14 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities + +async def choy(ctx): + """Each player reveals their hand. Draw 3 cards.""" + await ctx.reveal_hand(ctx.player_id, ctx.opponent_id) + await ctx.reveal_hand(ctx.opponent_id, ctx.player_id) + await ctx.draw_cards(3) + + card = SupporterCardDef( guid="8d696952-8618-5bb7-9c79-ed5fb18ec5b2", key="SWSH10", @@ -11,5 +19,5 @@ card = SupporterCardDef( collector_number=182, set_code="SWSH10", rarity=Rarities.RareUltra, - effect=unimplemented + effect=choy, ) diff --git a/spirit/game/scripts/cards/SWSH10/Choy_200.py b/spirit/game/scripts/cards/SWSH10/Choy_200.py index 603068e..48fac27 100644 --- a/spirit/game/scripts/cards/SWSH10/Choy_200.py +++ b/spirit/game/scripts/cards/SWSH10/Choy_200.py @@ -1,6 +1,14 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities + +async def choy(ctx): + """Each player reveals their hand. Draw 3 cards.""" + await ctx.reveal_hand(ctx.player_id, ctx.opponent_id) + await ctx.reveal_hand(ctx.opponent_id, ctx.player_id) + await ctx.draw_cards(3) + + card = SupporterCardDef( guid="1479c7a3-c3c3-56e4-86fa-d770af40c554", key="SWSH10", @@ -11,5 +19,5 @@ card = SupporterCardDef( collector_number=200, set_code="SWSH10", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=choy, ) diff --git a/spirit/game/scripts/cards/SWSH10/Combee_11.py b/spirit/game/scripts/cards/SWSH10/Combee_11.py index c3ed429..c8f9b79 100644 --- a/spirit/game/scripts/cards/SWSH10/Combee_11.py +++ b/spirit/game/scripts/cards/SWSH10/Combee_11.py @@ -1,4 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.session.effects import is_item_card +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -22,7 +24,10 @@ card = PokemonCardDef( title="Honey Courier", game_text="Search your deck for an Item card, reveal it, and put it into your hand. Then, shuffle your deck.", cost={PokemonTypes.GRASS: 1}, - effect=unimplemented, + effect=search_to_hand( + is_item_card, count=1, reveal=True, + prompt="Choose an Item card.", + ), ), Attack( title="Bug Bite", diff --git a/spirit/game/scripts/cards/SWSH10/Cranidos_76.py b/spirit/game/scripts/cards/SWSH10/Cranidos_76.py index fd1615a..b5a445c 100644 --- a/spirit/game/scripts/cards/SWSH10/Cranidos_76.py +++ b/spirit/game/scripts/cards/SWSH10/Cranidos_76.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus card = PokemonCardDef( guid="66ee5c48-3937-507b-8da6-bdc5b7b6175a", @@ -30,7 +31,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 2}, damage=40, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(40), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/Cyndaquil_23.py b/spirit/game/scripts/cards/SWSH10/Cyndaquil_23.py index 24bd980..e1af641 100644 --- a/spirit/game/scripts/cards/SWSH10/Cyndaquil_23.py +++ b/spirit/game/scripts/cards/SWSH10/Cyndaquil_23.py @@ -1,4 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -22,7 +24,10 @@ card = PokemonCardDef( title="Charge Energy", game_text="Search your deck for up to 2 basic Energy cards, reveal them, and put them into your hand. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_to_hand( + is_basic_energy_card, count=2, reveal=True, + prompt="Choose up to 2 basic Energy cards.", + ), ), Attack( title="Live Coal", diff --git a/spirit/game/scripts/cards/SWSH10/DarkPatch_139.py b/spirit/game/scripts/cards/SWSH10/DarkPatch_139.py index 6f95a77..64fac86 100644 --- a/spirit/game/scripts/cards/SWSH10/DarkPatch_139.py +++ b/spirit/game/scripts/cards/SWSH10/DarkPatch_139.py @@ -1,5 +1,30 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import ItemCardDef +from spirit.game.attributes import Rarities, PokemonTypes, AttrID +from spirit.game.card_effects.support_common import attach_from_discard +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.card_effects.passives_common import is_in_active_spot + + +def _is_basic_darkness_energy(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_basic_energy_card(card) and PokemonTypes.DARKNESS.value in types + + +def _is_benched_darkness_pokemon(pokemon): + types = pokemon.get_attribute(AttrID.POKEMON_TYPES) or [] + return not is_in_active_spot(pokemon) and PokemonTypes.DARKNESS.value in types + + +def _dark_patch_condition(board, player_id, pokemon=None): + bench = board.find_player_area(player_id, "bench") + bench_children = bench.children if bench else [] + if not any(PokemonTypes.DARKNESS.value in (p.get_attribute(AttrID.POKEMON_TYPES) or []) + for p in bench_children): + return False + discard = board.find_player_area(player_id, "discard") + cards = discard.children if discard else [] + return any(_is_basic_darkness_energy(c) for c in cards) + card = ItemCardDef( guid="f3804cce-d01c-5887-bca9-6408572e8f32", @@ -11,5 +36,10 @@ card = ItemCardDef( collector_number=139, set_code="SWSH10", rarity=Rarities.Uncommon, - effect=unimplemented + effect=attach_from_discard( + predicate=_is_basic_darkness_energy, count=1, minimum=1, + target=_is_benched_darkness_pokemon, + prompt="Choose a basic Darkness Energy card to attach to a Benched Darkness Pokémon", + ), + condition=_dark_patch_condition ) diff --git a/spirit/game/scripts/cards/SWSH10/DarkraiVSTAR_99.py b/spirit/game/scripts/cards/SWSH10/DarkraiVSTAR_99.py index f794ae9..6f69e87 100644 --- a/spirit/game/scripts/cards/SWSH10/DarkraiVSTAR_99.py +++ b/spirit/game/scripts/cards/SWSH10/DarkraiVSTAR_99.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_item_card +from spirit.game.card_effects.attacks_common import damage_per, count_energy + + +async def star_abyss(ctx): + """VSTAR Power: you may put up to 2 Item cards from your discard pile into your hand.""" + items = [c for c in ctx.discard_pile() if is_item_card(c)] + picks = await ctx.choose_cards( + items, 2, minimum=0, + prompt="Choose up to 2 Item cards to put into your hand.", + ) + await ctx.put_in_hand(picks, reveal=False) + card = PokemonCardDef( guid="0e27aad5-2f95-5632-aad5-aa2779a903ab", @@ -22,7 +35,8 @@ card = PokemonCardDef( Ability( title="Star Abyss", game_text="During your turn, you may put up to 2 Item cards from your discard pile into your hand. (You can't use more than 1 VSTAR Power in a game.)", - effect=unimplemented, + vstar=True, + effect=star_abyss, ), Attack( title="Dark Pulse", @@ -30,7 +44,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=30, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("mine", energy_type=PokemonTypes.DARKNESS), 30, base=30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/DarkraiV_98.py b/spirit/game/scripts/cards/SWSH10/DarkraiV_98.py index 76b260b..0b2f4a7 100644 --- a/spirit/game/scripts/cards/SWSH10/DarkraiV_98.py +++ b/spirit/game/scripts/cards/SWSH10/DarkraiV_98.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="0387e604-211c-5d17-922c-b8395fd9cbb1", @@ -28,7 +29,7 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Asleep.", cost={PokemonTypes.DARKNESS: 2, PokemonTypes.COLORLESS: 1}, damage=130, - effect=unimplemented, + effect=condition_attack(SpecialConditions.ASLEEP), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/Dewott_42.py b/spirit/game/scripts/cards/SWSH10/Dewott_42.py index b6fa395..502b031 100644 --- a/spirit/game/scripts/cards/SWSH10/Dewott_42.py +++ b/spirit/game/scripts/cards/SWSH10/Dewott_42.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus card = PokemonCardDef( guid="9ca513e5-ce85-5f1b-98bf-11d9c764eb1b", @@ -25,7 +26,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/Diancie_68.py b/spirit/game/scripts/cards/SWSH10/Diancie_68.py index 2d5fba6..29b2ac2 100644 --- a/spirit/game/scripts/cards/SWSH10/Diancie_68.py +++ b/spirit/game/scripts/cards/SWSH10/Diancie_68.py @@ -1,5 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import draw_attack +from spirit.game.card_effects.passives_common import trainer_effect_shield_passive +from spirit.game.session.passives import carrier_pokemon + + +def _benched_basic(entity, carrier): + # Attachments count as effects done to the Pokemon they ride under. + holder = carrier_pokemon(entity) if entity is not None else None + if holder is None: + return False + parent = getattr(holder, "parent", None) + return ( + bool(parent) and parent.get_attribute(AttrID.NAME) == "bench" + and holder.get_attribute(AttrID.STAGE) == PokemonStage.BASIC.value + ) card = PokemonCardDef( guid="080590a2-5fbf-57df-957a-1228e9c3fd6a", @@ -21,14 +36,15 @@ card = PokemonCardDef( Ability( title="Princess's Curtain", game_text="As long as this Pok\u00e9mon is in the Active Spot, whenever your opponent plays a Supporter card from their hand, prevent all effects of that card done to your Benched Basic Pok\u00e9mon.", - effect=unimplemented, + passive=trainer_effect_shield_passive( + protects=_benched_basic, while_active=True), ), Attack( title="Spike Draw", game_text="Draw 2 cards.", cost={PokemonTypes.COLORLESS: 1}, damage=20, - effect=unimplemented, + effect=draw_attack(2), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/Drifblim_64.py b/spirit/game/scripts/cards/SWSH10/Drifblim_64.py index f55f9cc..be2b9e7 100644 --- a/spirit/game/scripts/cards/SWSH10/Drifblim_64.py +++ b/spirit/game/scripts/cards/SWSH10/Drifblim_64.py @@ -1,6 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def spooky_balloon(ctx): + await ctx.deal_damage() + bench = ctx.opponent_bench() + if bench: + target = await ctx.choose_pokemon( + bench, "Choose 1 of your opponent's Benched Pokémon") + if target is not None: + await ctx.deal_damage(20, target=target, as_counters=True) + card = PokemonCardDef( guid="a3c060f2-38b7-5ce9-adef-650fd8c57732", key="SWSH10", @@ -25,7 +35,7 @@ card = PokemonCardDef( game_text="Put 2 damage counters on 1 of your opponent's Benched Pok\u00e9mon.", cost={PokemonTypes.PSYCHIC: 1}, damage=50, - effect=unimplemented, + effect=spooky_balloon, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/Drifloon_63.py b/spirit/game/scripts/cards/SWSH10/Drifloon_63.py index 265776d..bc1874d 100644 --- a/spirit/game/scripts/cards/SWSH10/Drifloon_63.py +++ b/spirit/game/scripts/cards/SWSH10/Drifloon_63.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="db9b1871-d7d9-5435-aad0-056634fc7a3f", @@ -25,7 +26,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 1}, damage=10, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=3, per_heads=10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/Eevee_119.py b/spirit/game/scripts/cards/SWSH10/Eevee_119.py index b74a13a..4fbe495 100644 --- a/spirit/game/scripts/cards/SWSH10/Eevee_119.py +++ b/spirit/game/scripts/cards/SWSH10/Eevee_119.py @@ -1,5 +1,37 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities + + +async def resonant_evolution(ctx): + """When another of your Eevee evolves from hand, you may search your deck + for a card that evolves from this Pokemon and evolve it. Then shuffle.""" + my_logic_name = ctx.source.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) + evolved_from = ctx.evolved_from + if evolved_from is None or not my_logic_name: + return + if evolved_from.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) != my_logic_name: + return + # "Once during your turn": accepting consumes the use even if the search + # then takes nothing; declining does not. + used_key = (ctx.source.entity_id, ctx.ability.ability_id if ctx.ability else "resonant") + if used_key in ctx.session.turn_state.used_abilities: + return + if not await ctx.ask_yes_no( + "Search your deck for a card that evolves from this Pokémon " + "and put it onto this Pokémon to evolve it?"): + return + ctx.session.turn_state.used_abilities.add(used_key) + + def matches(c): + return c.get_attribute(AttrID.EVOLUTION_LOGIC_FROM) == my_logic_name + + picks = await ctx.search_deck( + matches, count=1, minimum=0, + prompt="Choose a card that evolves from this Pokémon.", + ) + if picks: + await ctx.evolve_pokemon(ctx.source, picks[0]) + await ctx.shuffle_deck() card = PokemonCardDef( guid="abae60a7-0638-5d4b-b8b9-51b6852c6efb", @@ -21,7 +53,8 @@ card = PokemonCardDef( Ability( title="Resonant Evolution", game_text="Once during your turn, when you play a Pok\u00e9mon from your hand to evolve 1 of your other Eevee, you may search your deck for a card that evolves from this Pok\u00e9mon and put it onto this Pok\u00e9mon to evolve it. Then, shuffle your deck.", - effect=unimplemented, + trigger=Triggers.ON_ALLY_EVOLVED, + effect=resonant_evolution, ), Attack( title="Tackle", diff --git a/spirit/game/scripts/cards/SWSH10/EnergyLoto_140.py b/spirit/game/scripts/cards/SWSH10/EnergyLoto_140.py index 0de749d..13d19d2 100644 --- a/spirit/game/scripts/cards/SWSH10/EnergyLoto_140.py +++ b/spirit/game/scripts/cards/SWSH10/EnergyLoto_140.py @@ -1,5 +1,23 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import is_energy_card + + +async def energy_loto(ctx): + """Look at the top 7 cards of your deck. You may reveal an Energy card + you find there and put it into your hand. Shuffle the rest back.""" + top = ctx.deck_top(7) + if top: + candidates = [c for c in top if is_energy_card(c)] + if candidates: + picks = await ctx.choose_cards( + candidates, 1, minimum=0, + prompt="You may choose an Energy card to reveal and put into your hand.", + display_cards=top if len(candidates) < len(top) else None, + ) + await ctx.put_in_hand(picks, reveal=True) + await ctx.shuffle_deck() + card = ItemCardDef( guid="d1eb2cba-088d-50de-936d-623ac279b013", @@ -11,5 +29,5 @@ card = ItemCardDef( collector_number=140, set_code="SWSH10", rarity=Rarities.Uncommon, - effect=unimplemented + effect=energy_loto, ) diff --git a/spirit/game/scripts/cards/SWSH10/FeatherBall_141.py b/spirit/game/scripts/cards/SWSH10/FeatherBall_141.py index 39e4fd7..e653ec5 100644 --- a/spirit/game/scripts/cards/SWSH10/FeatherBall_141.py +++ b/spirit/game/scripts/cards/SWSH10/FeatherBall_141.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import ItemCardDef +from spirit.game.attributes import Rarities, AttrID +from spirit.game.session.effects import is_pokemon_card +from spirit.game.card_effects.support_common import search_to_hand + + +def _no_retreat_pokemon(card): + return is_pokemon_card(card) and int(card.get_attribute(AttrID.RETREAT_COST) or 0) == 0 + card = ItemCardDef( guid="5e68504e-3c22-568d-8dcf-81449c97f37a", @@ -11,5 +18,8 @@ card = ItemCardDef( collector_number=141, set_code="SWSH10", rarity=Rarities.Uncommon, - effect=unimplemented + effect=search_to_hand( + _no_retreat_pokemon, count=1, minimum=0, reveal=True, + prompt="Choose a Pokémon that has no Retreat Cost.", + ), ) diff --git a/spirit/game/scripts/cards/SWSH10/GalarianMrRimeV_49.py b/spirit/game/scripts/cards/SWSH10/GalarianMrRimeV_49.py index 50a4ebc..666be6c 100644 --- a/spirit/game/scripts/cards/SWSH10/GalarianMrRimeV_49.py +++ b/spirit/game/scripts/cards/SWSH10/GalarianMrRimeV_49.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.card_effects.attacks_common import bonus_if, has_tool +from spirit.game.session.effects import is_item_card card = PokemonCardDef( guid="505bdf56-6674-52ff-aa9b-743f89d0698b", @@ -22,7 +25,7 @@ card = PokemonCardDef( title="Surprising Hand", game_text="Search your deck for up to 3 Item cards, reveal them, and put them into your hand. Then, shuffle your deck.", cost={PokemonTypes.WATER: 1}, - effect=unimplemented, + effect=search_to_hand(is_item_card, count=3, minimum=0, reveal=True), ), Attack( title="Customized Cane", @@ -30,7 +33,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, damage=90, damage_operator="+", - effect=unimplemented, + effect=bonus_if(lambda ctx: has_tool(ctx.attacker), 90), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/Gallade_62.py b/spirit/game/scripts/cards/SWSH10/Gallade_62.py index 5ac6bea..a21ff67 100644 --- a/spirit/game/scripts/cards/SWSH10/Gallade_62.py +++ b/spirit/game/scripts/cards/SWSH10/Gallade_62.py @@ -1,5 +1,26 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.session.effects import is_supporter_card + + +async def swirling_slice(ctx): + await ctx.deal_damage() + bench = ctx.my_bench() + energies = ctx.attached_energies(ctx.attacker) + if not bench or not energies: + return + picks = await ctx.choose_cards( + energies, 1, prompt="Choose an Energy to move to 1 of your Benched Pokémon" + ) + if not picks: + return + target = await ctx.choose_pokemon( + bench, "Choose a Benched Pokémon to move the Energy to" + ) + if target is not None: + await ctx.move_energy(picks[0], target) + card = PokemonCardDef( guid="2cad74c1-a679-5ef9-8f3e-2798019c904e", @@ -23,14 +44,15 @@ card = PokemonCardDef( Ability( title="Buddy Catch", game_text="Once during your turn, you may search your deck for a Supporter card, reveal it, and put it into your hand. Then, shuffle your deck.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + effect=search_to_hand(is_supporter_card, count=1, minimum=0, prompt="Choose a Supporter card."), ), Attack( title="Swirling Slice", game_text="Move an Energy from this Pok\u00e9mon to 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 2}, damage=160, - effect=unimplemented, + effect=swirling_slice, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/GapejawBog_142.py b/spirit/game/scripts/cards/SWSH10/GapejawBog_142.py index 1622de2..cc9e76e 100644 --- a/spirit/game/scripts/cards/SWSH10/GapejawBog_142.py +++ b/spirit/game/scripts/cards/SWSH10/GapejawBog_142.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import StadiumCardDef, unimplemented +from spirit.game.card_effects.trainers import gapejaw_bog_watch +from spirit.game.data_utils import StadiumCardDef, Ability, Triggers from spirit.game.attributes import Rarities card = StadiumCardDef( @@ -11,5 +12,12 @@ card = StadiumCardDef( collector_number=142, set_code="SWSH10", rarity=Rarities.Uncommon, - effect=unimplemented + abilities=[ + Ability( + title="Gapejaw Bog", + game_text="Whenever either player puts a Basic Pokémon from their hand onto their Bench, put 2 damage counters on that Pokémon.", + trigger=Triggers.ON_POKEMON_BENCHED, + effect=gapejaw_bog_watch, + ), + ], ) diff --git a/spirit/game/scripts/cards/SWSH10/GarchompV_117.py b/spirit/game/scripts/cards/SWSH10/GarchompV_117.py index b147f30..db7efae 100644 --- a/spirit/game/scripts/cards/SWSH10/GarchompV_117.py +++ b/spirit/game/scripts/cards/SWSH10/GarchompV_117.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import snipe_attack + + +async def sonic_strike(ctx): + await ctx.discard_energy_from(ctx.attacker, 3, prompt="Choose 3 Energy to discard") + await snipe_attack(220, pool="any", count=1)(ctx) + card = PokemonCardDef( guid="68de790c-724a-530f-acff-7bc437f639e5", @@ -26,7 +33,7 @@ card = PokemonCardDef( title="Sonic Strike", game_text="Discard 3 Energy from this Pok\u00e9mon. This attack does 220 damage to 1 of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.WATER: 1, PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=sonic_strike, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/GarchompV_178.py b/spirit/game/scripts/cards/SWSH10/GarchompV_178.py index 956286f..1897c70 100644 --- a/spirit/game/scripts/cards/SWSH10/GarchompV_178.py +++ b/spirit/game/scripts/cards/SWSH10/GarchompV_178.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import snipe_attack + + +async def sonic_strike(ctx): + await ctx.discard_energy_from(ctx.attacker, 3, prompt="Choose 3 Energy to discard") + await snipe_attack(220, pool="any", count=1)(ctx) + card = PokemonCardDef( guid="2db2e893-50d1-50b9-b12a-9f748f583367", @@ -26,7 +33,7 @@ card = PokemonCardDef( title="Sonic Strike", game_text="Discard 3 Energy from this Pok\u00e9mon. This attack does 220 damage to 1 of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.WATER: 1, PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=sonic_strike, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/Glaceon_38.py b/spirit/game/scripts/cards/SWSH10/Glaceon_38.py index 9301510..36e6b1e 100644 --- a/spirit/game/scripts/cards/SWSH10/Glaceon_38.py +++ b/spirit/game/scripts/cards/SWSH10/Glaceon_38.py @@ -1,5 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import prevent_damage_when +from spirit.game.session.effects import is_evolution_pokemon +from spirit.game.session.passives import carrier_pokemon + + +async def frost_wall(ctx): + """30. During your opponent's next turn, prevent all damage done to this + Pokemon by attacks from Evolution Pokemon.""" + await ctx.deal_damage() + passive = prevent_damage_when( + lambda calc, carrier: carrier_pokemon(carrier) is calc.target + and is_evolution_pokemon(calc.attacker) + ) + ctx.add_passive_through_opponents_turn(ctx.attacker, passive) + card = PokemonCardDef( guid="0cdcae9c-a299-5e7d-bac0-240e229456e9", @@ -24,7 +39,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, prevent all damage done to this Pok\u00e9mon by attacks from Evolution Pok\u00e9mon.", cost={PokemonTypes.WATER: 1}, damage=30, - effect=unimplemented, + effect=frost_wall, ), Attack( title="Ice Blast", diff --git a/spirit/game/scripts/cards/SWSH10/Glameow_127.py b/spirit/game/scripts/cards/SWSH10/Glameow_127.py index cbf4243..d99bd27 100644 --- a/spirit/game/scripts/cards/SWSH10/Glameow_127.py +++ b/spirit/game/scripts/cards/SWSH10/Glameow_127.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="6eba0a01-007e-56b6-a1ad-6dddc1ef5462", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.COLORLESS: 2}, damage=20, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/Golduck_29.py b/spirit/game/scripts/cards/SWSH10/Golduck_29.py index 29615aa..36cb823 100644 --- a/spirit/game/scripts/cards/SWSH10/Golduck_29.py +++ b/spirit/game/scripts/cards/SWSH10/Golduck_29.py @@ -1,5 +1,41 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import full_stack + + +async def entangled_dive(ctx): + """Discard each player's Active Pokémon and all attached cards; each + player chooses a new Active first (mine, then the opponent's).""" + old_actives = {} + need_promotion = [] + for pid in (ctx.player_id, ctx.opponent_id): + is_mine = pid == ctx.player_id + active = ctx.my_active() if is_mine else ctx.opponent_active() + if active is None: + continue + bench = ctx.my_bench() if is_mine else ctx.opponent_bench() + if bench: + target = await ctx.choose_pokemon( + bench, "Choose your new Active Pokémon", player_id=pid + ) + await ctx.switch_active(pid, target) + else: + need_promotion.append(pid) + old_actives[pid] = active + + for active in old_actives.values(): + await ctx.discard_cards(full_stack(active)) + + for pid in need_promotion: + async def _promote(pid=pid): + if not await ctx.session._promote_new_active(pid): + opponent = ctx.opponent_id if pid == ctx.player_id else ctx.player_id + screen_name = ctx.session.players[pid].screen_name + await ctx.session.end_game( + opponent, f"{screen_name} has no Pokémon left" + ) + ctx.deferred_actions.append(_promote) + card = PokemonCardDef( guid="d90ddaef-aaa2-58a0-8ba7-f4609d3d9680", @@ -28,7 +64,7 @@ card = PokemonCardDef( title="Entangled Dive", game_text="Discard each player's Active Pok\u00e9mon and all attached cards. (You choose a new Active Pok\u00e9mon first.)", cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=entangled_dive, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/Grant_144.py b/spirit/game/scripts/cards/SWSH10/Grant_144.py index 675d940..d75b503 100644 --- a/spirit/game/scripts/cards/SWSH10/Grant_144.py +++ b/spirit/game/scripts/cards/SWSH10/Grant_144.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import SupporterCardDef +from spirit.game.attributes import AttrID, PokemonTypes, Rarities +from spirit.game.session.passives import TurnDamageModifier + + +async def grant(ctx): + """This turn, your Fighting Pokemon's attacks do 30 more damage to the + opponent's Active Pokemon (before W/R). (Discard-pile self-recovery + clause needs Trainer PIE_ABILITIES support -- unscripted.)""" + ctx.add_turn_damage_modifier(TurnDamageModifier( + 30, ctx.player_id, + source_predicate=lambda p: PokemonTypes.FIGHTING.value in ( + p.get_attribute(AttrID.POKEMON_TYPES) or []), + )) + card = SupporterCardDef( guid="a8f1995e-7be2-502c-a816-33872a499b32", @@ -11,5 +24,5 @@ card = SupporterCardDef( collector_number=144, set_code="SWSH10", rarity=Rarities.Uncommon, - effect=unimplemented + effect=grant ) diff --git a/spirit/game/scripts/cards/SWSH10/Grant_185.py b/spirit/game/scripts/cards/SWSH10/Grant_185.py index 85b8d7b..b9ab4f1 100644 --- a/spirit/game/scripts/cards/SWSH10/Grant_185.py +++ b/spirit/game/scripts/cards/SWSH10/Grant_185.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import SupporterCardDef +from spirit.game.attributes import AttrID, PokemonTypes, Rarities +from spirit.game.session.passives import TurnDamageModifier + + +async def grant(ctx): + """This turn, your Fighting Pokemon's attacks do 30 more damage to the + opponent's Active Pokemon (before W/R). (Discard-pile self-recovery + clause needs Trainer PIE_ABILITIES support -- unscripted.)""" + ctx.add_turn_damage_modifier(TurnDamageModifier( + 30, ctx.player_id, + source_predicate=lambda p: PokemonTypes.FIGHTING.value in ( + p.get_attribute(AttrID.POKEMON_TYPES) or []), + )) + card = SupporterCardDef( guid="2155ebe8-9b1d-53b9-887b-903a962b7c43", @@ -11,5 +24,5 @@ card = SupporterCardDef( collector_number=185, set_code="SWSH10", rarity=Rarities.RareUltra, - effect=unimplemented + effect=grant ) diff --git a/spirit/game/scripts/cards/SWSH10/Grant_203.py b/spirit/game/scripts/cards/SWSH10/Grant_203.py index 3478c85..973bbe7 100644 --- a/spirit/game/scripts/cards/SWSH10/Grant_203.py +++ b/spirit/game/scripts/cards/SWSH10/Grant_203.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import SupporterCardDef +from spirit.game.attributes import AttrID, PokemonTypes, Rarities +from spirit.game.session.passives import TurnDamageModifier + + +async def grant(ctx): + """This turn, your Fighting Pokemon's attacks do 30 more damage to the + opponent's Active Pokemon (before W/R). (Discard-pile self-recovery + clause needs Trainer PIE_ABILITIES support -- unscripted.)""" + ctx.add_turn_damage_modifier(TurnDamageModifier( + 30, ctx.player_id, + source_predicate=lambda p: PokemonTypes.FIGHTING.value in ( + p.get_attribute(AttrID.POKEMON_TYPES) or []), + )) + card = SupporterCardDef( guid="f53e3719-5220-5ebd-9254-216b6b23cab5", @@ -11,5 +24,5 @@ card = SupporterCardDef( collector_number=203, set_code="SWSH10", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=grant ) diff --git a/spirit/game/scripts/cards/SWSH10/GutsyPickaxe_145.py b/spirit/game/scripts/cards/SWSH10/GutsyPickaxe_145.py index 4607623..ac27258 100644 --- a/spirit/game/scripts/cards/SWSH10/GutsyPickaxe_145.py +++ b/spirit/game/scripts/cards/SWSH10/GutsyPickaxe_145.py @@ -1,5 +1,33 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import ItemCardDef +from spirit.game.attributes import PokemonTypes, Rarities +from spirit.game.card_effects.pokemon import is_energy_card, energy_provides_type + + +def _is_fighting_energy_card(card) -> bool: + return is_energy_card(card) and energy_provides_type(card, PokemonTypes.FIGHTING.value) + + +async def gutsy_pickaxe(ctx): + """Reveal the top card of your deck. If it's a Fighting Energy card, + attach it to 1 of your Benched Pokemon; otherwise put it into your + hand.""" + top = ctx.deck_top(1) + if not top: + return + card = top[0] + await ctx.reveal_cards([card]) + if _is_fighting_energy_card(card): + bench = ctx.my_bench() + if not bench: + return + target = await ctx.choose_pokemon( + bench, "Choose a Benched Pokémon to attach the Energy to." + ) + if target is not None: + await ctx.attach_energy(card, target) + else: + await ctx.put_in_hand([card], reveal=False) + card = ItemCardDef( guid="f0cacb75-cb70-52c4-b02f-94d39e45d90b", @@ -11,5 +39,5 @@ card = ItemCardDef( collector_number=145, set_code="SWSH10", rarity=Rarities.Uncommon, - effect=unimplemented + effect=gutsy_pickaxe ) diff --git a/spirit/game/scripts/cards/SWSH10/HeatranVMAX_191.py b/spirit/game/scripts/cards/SWSH10/HeatranVMAX_191.py index 8dfd365..b457089 100644 --- a/spirit/game/scripts/cards/SWSH10/HeatranVMAX_191.py +++ b/spirit/game/scripts/cards/SWSH10/HeatranVMAX_191.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions, AttrID +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.session.passives import effective_max_hp + + +def _magma_gain_condition(board, player_id, pokemon): + stadium_area = board.find_global_area("activeStadium") + if not (stadium_area and stadium_area.children): + return False + return pokemon.get_attribute(AttrID.HP, 0) < effective_max_hp(board, pokemon) + + +async def _magma_gain(ctx): + if await ctx.ask_yes_no("Heal 50 damage from this Pokémon?"): + await ctx.heal(50, ctx.source) card = PokemonCardDef( guid="46c8d36e-0749-56ba-afd7-459c79c7e575", @@ -22,14 +36,16 @@ card = PokemonCardDef( Ability( title="Magma Gain", game_text="Once during your turn, if you have a Stadium in play, you may heal 50 damage from this Pok\u00e9mon.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=_magma_gain_condition, + effect=_magma_gain, ), Attack( title="Max Heat Burst", game_text="Your opponent's Active Pok\u00e9mon is now Burned.", cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=180, - effect=unimplemented, + effect=condition_attack(SpecialConditions.BURNED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/HeatranVMAX_26.py b/spirit/game/scripts/cards/SWSH10/HeatranVMAX_26.py index e9fa8e4..2c42365 100644 --- a/spirit/game/scripts/cards/SWSH10/HeatranVMAX_26.py +++ b/spirit/game/scripts/cards/SWSH10/HeatranVMAX_26.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions, AttrID +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.session.passives import effective_max_hp + + +def _magma_gain_condition(board, player_id, pokemon): + stadium_area = board.find_global_area("activeStadium") + if not (stadium_area and stadium_area.children): + return False + return pokemon.get_attribute(AttrID.HP, 0) < effective_max_hp(board, pokemon) + + +async def _magma_gain(ctx): + if await ctx.ask_yes_no("Heal 50 damage from this Pokémon?"): + await ctx.heal(50, ctx.source) card = PokemonCardDef( guid="3d870dc7-b34e-5a47-96a1-2743c9526f09", @@ -22,14 +36,16 @@ card = PokemonCardDef( Ability( title="Magma Gain", game_text="Once during your turn, if you have a Stadium in play, you may heal 50 damage from this Pok\u00e9mon.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=_magma_gain_condition, + effect=_magma_gain, ), Attack( title="Max Heat Burst", game_text="Your opponent's Active Pok\u00e9mon is now Burned.", cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=180, - effect=unimplemented, + effect=condition_attack(SpecialConditions.BURNED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/HeatranV_165.py b/spirit/game/scripts/cards/SWSH10/HeatranV_165.py index 3bf9ccc..ab61089 100644 --- a/spirit/game/scripts/cards/SWSH10/HeatranV_165.py +++ b/spirit/game/scripts/cards/SWSH10/HeatranV_165.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack, bonus_if card = PokemonCardDef( guid="1e76d67b-0c8a-5ca0-9fa7-f70306697561", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Burned.", cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=condition_attack(SpecialConditions.BURNED), ), Attack( title="Magma Fall", @@ -31,7 +32,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=90, damage_operator="+", - effect=unimplemented, + effect=bonus_if(lambda ctx: ctx.stadium_in_play() is not None, 90), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/HeatranV_25.py b/spirit/game/scripts/cards/SWSH10/HeatranV_25.py index 26ec1f7..6d3df4f 100644 --- a/spirit/game/scripts/cards/SWSH10/HeatranV_25.py +++ b/spirit/game/scripts/cards/SWSH10/HeatranV_25.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack, bonus_if card = PokemonCardDef( guid="77e81cf2-4b5a-529d-9996-b6b6d26d1dea", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Burned.", cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=condition_attack(SpecialConditions.BURNED), ), Attack( title="Magma Fall", @@ -31,7 +32,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=90, damage_operator="+", - effect=unimplemented, + effect=bonus_if(lambda ctx: ctx.stadium_in_play() is not None, 90), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/Heracross_8.py b/spirit/game/scripts/cards/SWSH10/Heracross_8.py index 29844b6..1540af0 100644 --- a/spirit/game/scripts/cards/SWSH10/Heracross_8.py +++ b/spirit/game/scripts/cards/SWSH10/Heracross_8.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import snipe_attack card = PokemonCardDef( guid="798ba382-b3d0-5782-b120-20d855b03b01", @@ -28,7 +29,7 @@ card = PokemonCardDef( game_text="This attack also does 30 damage to 1 of your Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.GRASS: 2, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + effect=snipe_attack(30, pool="bench", side="mine", also_base=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/Hippowdon_80.py b/spirit/game/scripts/cards/SWSH10/Hippowdon_80.py index de5ba96..e08a896 100644 --- a/spirit/game/scripts/cards/SWSH10/Hippowdon_80.py +++ b/spirit/game/scripts/cards/SWSH10/Hippowdon_80.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import self_energy_discard_attack card = PokemonCardDef( guid="96b6c2a6-5848-5fad-9aec-a33a6c7b3768", @@ -29,7 +30,7 @@ card = PokemonCardDef( game_text="Discard 2 Energy from this Pok\u00e9mon.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 3}, damage=180, - effect=unimplemented, + effect=self_energy_discard_attack(count=2), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/HisuianArcanine_71.py b/spirit/game/scripts/cards/SWSH10/HisuianArcanine_71.py index 7cbd0ca..59489dc 100644 --- a/spirit/game/scripts/cards/SWSH10/HisuianArcanine_71.py +++ b/spirit/game/scripts/cards/SWSH10/HisuianArcanine_71.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.pokemon import energy_provides_type + + +async def _scorching_horn(ctx): + has_fire = any( + energy_provides_type(energy, PokemonTypes.FIRE.value) + for energy in ctx.attached_energies(ctx.attacker) + ) + if has_fire: + await ctx.deal_damage(160) + await ctx.apply_special_condition(ctx.defender, SpecialConditions.BURNED) + else: + await ctx.deal_damage(80) card = PokemonCardDef( guid="2eb21b94-ae72-50e7-8175-5aff2bcf5ac3", @@ -30,7 +43,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=80, damage_operator="+", - effect=unimplemented, + effect=_scorching_horn, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/HisuianAvalugg_48.py b/spirit/game/scripts/cards/SWSH10/HisuianAvalugg_48.py index 9116ab7..f61bbe1 100644 --- a/spirit/game/scripts/cards/SWSH10/HisuianAvalugg_48.py +++ b/spirit/game/scripts/cards/SWSH10/HisuianAvalugg_48.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if +from spirit.game.card_effects.passives_common import takes_less_passive + + +async def _discard_stadium_after(ctx): + await ctx.discard_stadium() + card = PokemonCardDef( guid="405e7be5-a208-59db-b327-f0a9cfbd566b", @@ -22,7 +29,7 @@ card = PokemonCardDef( Ability( title="Massive Ice", game_text="This Pok\u00e9mon takes 30 less damage from attacks (after applying Weakness and Resistance).", - effect=unimplemented, + passive=takes_less_passive(30), ), Attack( title="Mountain Gale", @@ -30,7 +37,8 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 2}, damage=100, damage_operator="+", - effect=unimplemented, + effect=bonus_if(lambda ctx: ctx.stadium_in_play() is not None, 120, base=100, + also=_discard_stadium_after), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/HisuianBasculegion_44.py b/spirit/game/scripts/cards/SWSH10/HisuianBasculegion_44.py index b427e37..1e670ad 100644 --- a/spirit/game/scripts/cards/SWSH10/HisuianBasculegion_44.py +++ b/spirit/game/scripts/cards/SWSH10/HisuianBasculegion_44.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions + + +async def _grudge_dive(ctx): + if ctx.kos_suffered_last_turn() > 0: + await ctx.deal_damage(120) + await ctx.apply_special_condition(ctx.defender, SpecialConditions.CONFUSED) + else: + await ctx.deal_damage(30) card = PokemonCardDef( guid="867c3aa1-5afc-5774-9c6a-4be592e4bdc5", @@ -25,7 +33,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 1}, damage=30, damage_operator="+", - effect=unimplemented, + effect=_grudge_dive, ), Attack( title="Jet Headbutt", diff --git a/spirit/game/scripts/cards/SWSH10/HisuianBasculin_43.py b/spirit/game/scripts/cards/SWSH10/HisuianBasculin_43.py index 7f2a3d3..693f075 100644 --- a/spirit/game/scripts/cards/SWSH10/HisuianBasculin_43.py +++ b/spirit/game/scripts/cards/SWSH10/HisuianBasculin_43.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_bench card = PokemonCardDef( guid="ea73bad4-a7bf-57f2-90e8-ff01f0d564b2", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Gather the Crew", game_text="Search your deck for up to 2 Basic Pok\u00e9mon and put them onto your Bench. Then, shuffle your deck.", cost={}, - effect=unimplemented, + effect=search_to_bench(count=2), ), Attack( title="Tackle", diff --git a/spirit/game/scripts/cards/SWSH10/HisuianBraviary_132.py b/spirit/game/scripts/cards/SWSH10/HisuianBraviary_132.py index 7286bee..d1d5834 100644 --- a/spirit/game/scripts/cards/SWSH10/HisuianBraviary_132.py +++ b/spirit/game/scripts/cards/SWSH10/HisuianBraviary_132.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy card = PokemonCardDef( guid="1db0cd12-3529-52fe-998e-eb88e1bcf668", @@ -26,14 +27,14 @@ card = PokemonCardDef( cost={}, damage=30, damage_operator="x", - effect=unimplemented, + effect=damage_per(count_energy("defender"), 30), ), Attack( title="Slashing Strike", game_text="During your next turn, this Pok\u00e9mon can't use Slashing Strike.", cost={PokemonTypes.COLORLESS: 3}, damage=120, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/HisuianDecidueyeVSTAR_195.py b/spirit/game/scripts/cards/SWSH10/HisuianDecidueyeVSTAR_195.py index 6c59285..5b39781 100644 --- a/spirit/game/scripts/cards/SWSH10/HisuianDecidueyeVSTAR_195.py +++ b/spirit/game/scripts/cards/SWSH10/HisuianDecidueyeVSTAR_195.py @@ -1,5 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.trainers import is_energy_card + + +async def star_of_fortune(ctx): + if await ctx.ask_yes_no("Draw cards until you have 8 cards in your hand?"): + await ctx.draw_until(8) + + +async def somersault_feathers(ctx): + discarded = await ctx.discard_from_hand( + 3, minimum=0, predicate=is_energy_card, + prompt="Discard up to 3 Energy cards from your hand.", + ) + await ctx.deal_damage(160 + 30 * len(discarded)) + card = PokemonCardDef( guid="dbe58393-7634-5707-9f52-90033c4d4ca1", @@ -22,7 +37,8 @@ card = PokemonCardDef( Ability( title="Star of Fortune", game_text="During your turn, you may draw cards until you have 8 cards in your hand. (You can't use more than 1 VSTAR Power in a game.)", - effect=unimplemented, + vstar=True, + effect=star_of_fortune, ), Attack( title="Somersault Feathers", @@ -30,7 +46,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 2}, damage=160, damage_operator="+", - effect=unimplemented, + effect=somersault_feathers, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/HisuianDecidueyeVSTAR_84.py b/spirit/game/scripts/cards/SWSH10/HisuianDecidueyeVSTAR_84.py index 61c6416..ed3f685 100644 --- a/spirit/game/scripts/cards/SWSH10/HisuianDecidueyeVSTAR_84.py +++ b/spirit/game/scripts/cards/SWSH10/HisuianDecidueyeVSTAR_84.py @@ -1,5 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.trainers import is_energy_card + + +async def star_of_fortune(ctx): + if await ctx.ask_yes_no("Draw cards until you have 8 cards in your hand?"): + await ctx.draw_until(8) + + +async def somersault_feathers(ctx): + discarded = await ctx.discard_from_hand( + 3, minimum=0, predicate=is_energy_card, + prompt="Discard up to 3 Energy cards from your hand.", + ) + await ctx.deal_damage(160 + 30 * len(discarded)) + card = PokemonCardDef( guid="142b3a81-bb2f-590a-88b5-0729c48812a8", @@ -22,7 +37,8 @@ card = PokemonCardDef( Ability( title="Star of Fortune", game_text="During your turn, you may draw cards until you have 8 cards in your hand. (You can't use more than 1 VSTAR Power in a game.)", - effect=unimplemented, + vstar=True, + effect=star_of_fortune, ), Attack( title="Somersault Feathers", @@ -30,7 +46,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 2}, damage=160, damage_operator="+", - effect=unimplemented, + effect=somersault_feathers, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/HisuianDecidueyeV_173.py b/spirit/game/scripts/cards/SWSH10/HisuianDecidueyeV_173.py index 58bce8f..592c5ad 100644 --- a/spirit/game/scripts/cards/SWSH10/HisuianDecidueyeV_173.py +++ b/spirit/game/scripts/cards/SWSH10/HisuianDecidueyeV_173.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_hand + + +async def close_quarters_shooting(ctx): + await ctx.deal_damage(ignore_target_effects=True) + card = PokemonCardDef( guid="f53b9f97-9a62-5a12-ad23-69b283ccf6c2", @@ -22,14 +28,15 @@ card = PokemonCardDef( title="Mountain Hunt", game_text="Search your deck for up to 2 cards and put them into your hand. Then, shuffle your deck.", cost={PokemonTypes.FIGHTING: 1}, - effect=unimplemented, + effect=search_to_hand(count=2, minimum=0, reveal=False, + prompt="Choose up to 2 cards to put into your hand."), ), Attack( title="Close-Quarters Shooting", game_text="This attack's damage isn't affected by any effects on your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 2}, damage=100, - effect=unimplemented, + effect=close_quarters_shooting, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/HisuianDecidueyeV_83.py b/spirit/game/scripts/cards/SWSH10/HisuianDecidueyeV_83.py index e743bff..a818a4d 100644 --- a/spirit/game/scripts/cards/SWSH10/HisuianDecidueyeV_83.py +++ b/spirit/game/scripts/cards/SWSH10/HisuianDecidueyeV_83.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_hand + + +async def close_quarters_shooting(ctx): + await ctx.deal_damage(ignore_target_effects=True) + card = PokemonCardDef( guid="6edca0e4-efec-5a00-bb40-5a49c47f1943", @@ -22,14 +28,15 @@ card = PokemonCardDef( title="Mountain Hunt", game_text="Search your deck for up to 2 cards and put them into your hand. Then, shuffle your deck.", cost={PokemonTypes.FIGHTING: 1}, - effect=unimplemented, + effect=search_to_hand(count=2, minimum=0, reveal=False, + prompt="Choose up to 2 cards to put into your hand."), ), Attack( title="Close-Quarters Shooting", game_text="This attack's damage isn't affected by any effects on your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 2}, damage=100, - effect=unimplemented, + effect=close_quarters_shooting, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/HisuianDecidueye_82.py b/spirit/game/scripts/cards/SWSH10/HisuianDecidueye_82.py index 6ec213b..7725327 100644 --- a/spirit/game/scripts/cards/SWSH10/HisuianDecidueye_82.py +++ b/spirit/game/scripts/cards/SWSH10/HisuianDecidueye_82.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import damage_per, damage_counters_on, snipe_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -25,13 +26,13 @@ card = PokemonCardDef( cost={}, damage=30, damage_operator="x", - effect=unimplemented, + effect=damage_per(damage_counters_on("defender"), 30), ), Attack( title="Direct Arrow", game_text="This attack does 80 damage to 1 of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + effect=snipe_attack(80, pool="any", count=1), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/HisuianElectrode_3.py b/spirit/game/scripts/cards/SWSH10/HisuianElectrode_3.py index 39df03b..4b09b84 100644 --- a/spirit/game/scripts/cards/SWSH10/HisuianElectrode_3.py +++ b/spirit/game/scripts/cards/SWSH10/HisuianElectrode_3.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import draw_attack card = PokemonCardDef( guid="8ca70d54-0103-51d2-af39-8354e1a2620d", @@ -23,7 +24,7 @@ card = PokemonCardDef( title="Triple Draw", game_text="Draw 3 cards.", cost={}, - effect=unimplemented, + effect=draw_attack(3), ), Attack( title="Irritated Bomb", diff --git a/spirit/game/scripts/cards/SWSH10/HisuianGrowlithe_70.py b/spirit/game/scripts/cards/SWSH10/HisuianGrowlithe_70.py index 60f25e8..95bd32a 100644 --- a/spirit/game/scripts/cards/SWSH10/HisuianGrowlithe_70.py +++ b/spirit/game/scripts/cards/SWSH10/HisuianGrowlithe_70.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import flip_protection card = PokemonCardDef( guid="38ab5e40-9343-5775-b3be-76189b8b3ffc", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Defensive Posture", game_text="Flip a coin. If heads, during your opponent's next turn, prevent all damage done to this Pok\u00e9mon by attacks.", cost={}, - effect=unimplemented, + effect=flip_protection(prevent=True), ), Attack( title="Bite", diff --git a/spirit/game/scripts/cards/SWSH10/HisuianLilligantVSTAR_18.py b/spirit/game/scripts/cards/SWSH10/HisuianLilligantVSTAR_18.py index 587f18f..1e8289b 100644 --- a/spirit/game/scripts/cards/SWSH10/HisuianLilligantVSTAR_18.py +++ b/spirit/game/scripts/cards/SWSH10/HisuianLilligantVSTAR_18.py @@ -1,5 +1,44 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_pokemon_card +from spirit.game.card_effects.trainers import is_grass_energy_card + + +def _is_grass_pokemon(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_pokemon_card(card) and PokemonTypes.GRASS.value in types + + +def _is_grass_pokemon_or_energy(card): + return _is_grass_pokemon(card) or is_grass_energy_card(card) + + +async def star_perfume(ctx): + if await ctx.ask_yes_no( + "Search your deck for up to 5 in any combination of Grass " + "Pokémon and Grass Energy cards?" + ): + picks = await ctx.search_deck( + _is_grass_pokemon_or_energy, count=5, minimum=0, + prompt="Choose up to 5 Grass Pokémon and Grass Energy cards.", + ) + await ctx.put_in_hand(picks, reveal=True) + await ctx.shuffle_deck() + + +async def parallel_spin(ctx): + energies = ctx.attached_energies(ctx.source) + bonus = 0 + if energies: + picks = await ctx.choose_cards( + energies, 1, minimum=0, + prompt="Put an Energy attached to this Pokémon into your hand?", + ) + if picks: + await ctx.put_in_hand(picks, reveal=False) + bonus = 100 + await ctx.deal_damage(130 + bonus) + card = PokemonCardDef( guid="4816d51d-d3b7-5122-b7ce-93e30fbdbd30", @@ -22,7 +61,8 @@ card = PokemonCardDef( Ability( title="Star Perfume", game_text="During your turn, you may search your deck for up to 5 in any combination of Grass Pok\u00e9mon and Grass Energy cards, reveal them, and put them into your hand. Then, shuffle your deck. (You can't use more than 1 VSTAR Power in a game.)", - effect=unimplemented, + vstar=True, + effect=star_perfume, ), Attack( title="Parallel Spin", @@ -30,7 +70,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 2, PokemonTypes.COLORLESS: 1}, damage=130, damage_operator="+", - effect=unimplemented, + effect=parallel_spin, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/HisuianLilligantVSTAR_190.py b/spirit/game/scripts/cards/SWSH10/HisuianLilligantVSTAR_190.py index bfd7a87..17895e9 100644 --- a/spirit/game/scripts/cards/SWSH10/HisuianLilligantVSTAR_190.py +++ b/spirit/game/scripts/cards/SWSH10/HisuianLilligantVSTAR_190.py @@ -1,5 +1,44 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_pokemon_card +from spirit.game.card_effects.trainers import is_grass_energy_card + + +def _is_grass_pokemon(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_pokemon_card(card) and PokemonTypes.GRASS.value in types + + +def _is_grass_pokemon_or_energy(card): + return _is_grass_pokemon(card) or is_grass_energy_card(card) + + +async def star_perfume(ctx): + if await ctx.ask_yes_no( + "Search your deck for up to 5 in any combination of Grass " + "Pokémon and Grass Energy cards?" + ): + picks = await ctx.search_deck( + _is_grass_pokemon_or_energy, count=5, minimum=0, + prompt="Choose up to 5 Grass Pokémon and Grass Energy cards.", + ) + await ctx.put_in_hand(picks, reveal=True) + await ctx.shuffle_deck() + + +async def parallel_spin(ctx): + energies = ctx.attached_energies(ctx.source) + bonus = 0 + if energies: + picks = await ctx.choose_cards( + energies, 1, minimum=0, + prompt="Put an Energy attached to this Pokémon into your hand?", + ) + if picks: + await ctx.put_in_hand(picks, reveal=False) + bonus = 100 + await ctx.deal_damage(130 + bonus) + card = PokemonCardDef( guid="1fa64f92-6224-5312-923a-a41cc2668a20", @@ -22,7 +61,8 @@ card = PokemonCardDef( Ability( title="Star Perfume", game_text="During your turn, you may search your deck for up to 5 in any combination of Grass Pok\u00e9mon and Grass Energy cards, reveal them, and put them into your hand. Then, shuffle your deck. (You can't use more than 1 VSTAR Power in a game.)", - effect=unimplemented, + vstar=True, + effect=star_perfume, ), Attack( title="Parallel Spin", @@ -30,7 +70,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 2, PokemonTypes.COLORLESS: 1}, damage=130, damage_operator="+", - effect=unimplemented, + effect=parallel_spin, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/HisuianLilligantV_162.py b/spirit/game/scripts/cards/SWSH10/HisuianLilligantV_162.py index 1d4f804..996f906 100644 --- a/spirit/game/scripts/cards/SWSH10/HisuianLilligantV_162.py +++ b/spirit/game/scripts/cards/SWSH10/HisuianLilligantV_162.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import draw_until_effect card = PokemonCardDef( guid="f56c45f0-5bbe-58e5-9b53-05ca31c117b5", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Dance Gracefully", game_text="Draw cards until you have 6 cards in your hand.", cost={}, - effect=unimplemented, + effect=draw_until_effect(6), ), Attack( title="Leaf Step", diff --git a/spirit/game/scripts/cards/SWSH10/HisuianLilligantV_163.py b/spirit/game/scripts/cards/SWSH10/HisuianLilligantV_163.py index beba23f..cf08dcc 100644 --- a/spirit/game/scripts/cards/SWSH10/HisuianLilligantV_163.py +++ b/spirit/game/scripts/cards/SWSH10/HisuianLilligantV_163.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import draw_until_effect card = PokemonCardDef( guid="9fe421fb-04e7-51e4-9623-98b60531b1cb", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Dance Gracefully", game_text="Draw cards until you have 6 cards in your hand.", cost={}, - effect=unimplemented, + effect=draw_until_effect(6), ), Attack( title="Leaf Step", diff --git a/spirit/game/scripts/cards/SWSH10/HisuianLilligantV_17.py b/spirit/game/scripts/cards/SWSH10/HisuianLilligantV_17.py index e2cd332..d7e9893 100644 --- a/spirit/game/scripts/cards/SWSH10/HisuianLilligantV_17.py +++ b/spirit/game/scripts/cards/SWSH10/HisuianLilligantV_17.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import draw_until_effect card = PokemonCardDef( guid="e2205d31-0aff-56a6-a859-8649abc57a10", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Dance Gracefully", game_text="Draw cards until you have 6 cards in your hand.", cost={}, - effect=unimplemented, + effect=draw_until_effect(6), ), Attack( title="Leaf Step", diff --git a/spirit/game/scripts/cards/SWSH10/HisuianLilligant_16.py b/spirit/game/scripts/cards/SWSH10/HisuianLilligant_16.py index b2912d9..76654f9 100644 --- a/spirit/game/scripts/cards/SWSH10/HisuianLilligant_16.py +++ b/spirit/game/scripts/cards/SWSH10/HisuianLilligant_16.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_all_opponents + + +async def _switch_self(ctx): + bench = ctx.my_bench() + if not bench: + return + target = await ctx.choose_pokemon(bench, "Choose your new Active Pokémon") + if target is not None: + await ctx.switch_active(ctx.player_id, target) card = PokemonCardDef( guid="eba08eb0-65c6-5add-8c74-f63d942baddd", @@ -23,7 +33,7 @@ card = PokemonCardDef( title="Twister Lutz", game_text="This attack does 20 damage to each of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.) Switch this Pok\u00e9mon with 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + effect=damage_all_opponents(20, also=_switch_self), ), Attack( title="Leaf Step", diff --git a/spirit/game/scripts/cards/SWSH10/HisuianOverqwil_90.py b/spirit/game/scripts/cards/SWSH10/HisuianOverqwil_90.py index ae4ccb9..0476e5c 100644 --- a/spirit/game/scripts/cards/SWSH10/HisuianOverqwil_90.py +++ b/spirit/game/scripts/cards/SWSH10/HisuianOverqwil_90.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="d3517913-f5fb-55bc-8a56-f6385addf39d", @@ -23,14 +24,14 @@ card = PokemonCardDef( title="Tormenting Poison", game_text="Your opponent's Active Pok\u00e9mon is now Poisoned. During Pok\u00e9mon Checkup, put 5 damage counters on that Pok\u00e9mon instead of 1.", cost={}, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED, counters=5), ), Attack( title="Pinning", game_text="During your opponent's next turn, the Defending Pok\u00e9mon can't retreat.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 1}, damage=50, - effect=unimplemented, + effect=condition_attack(no_retreat=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/HisuianOverqwil_91.py b/spirit/game/scripts/cards/SWSH10/HisuianOverqwil_91.py index a18217d..26a55ce 100644 --- a/spirit/game/scripts/cards/SWSH10/HisuianOverqwil_91.py +++ b/spirit/game/scripts/cards/SWSH10/HisuianOverqwil_91.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, count_energy card = PokemonCardDef( guid="e5de5d4a-5604-5394-a129-80c522af8ded", @@ -25,7 +26,9 @@ card = PokemonCardDef( cost={PokemonTypes.DARKNESS: 1}, damage=30, damage_operator="+", - effect=unimplemented, + effect=bonus_if( + lambda ctx: count_energy("mine", energy_type=PokemonTypes.DARKNESS)(ctx) >= 3, + 90, base=30), ), Attack( title="Pierce", diff --git a/spirit/game/scripts/cards/SWSH10/HisuianQwilfish_89.py b/spirit/game/scripts/cards/SWSH10/HisuianQwilfish_89.py index f1936ca..3dd3fb9 100644 --- a/spirit/game/scripts/cards/SWSH10/HisuianQwilfish_89.py +++ b/spirit/game/scripts/cards/SWSH10/HisuianQwilfish_89.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="497dad7c-2df9-5d4c-b87d-0daa3a2cf087", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={}, damage=10, damage_operator="x", - effect=unimplemented, + effect=flip_damage(until_tails=True, per_heads=10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/HisuianSamurottVSTAR_102.py b/spirit/game/scripts/cards/SWSH10/HisuianSamurottVSTAR_102.py index 22b8a41..d5f6b62 100644 --- a/spirit/game/scripts/cards/SWSH10/HisuianSamurottVSTAR_102.py +++ b/spirit/game/scripts/cards/SWSH10/HisuianSamurottVSTAR_102.py @@ -1,6 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import bonus_if, has_damage +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def moon_cleave_star(ctx): + """VSTAR Power: you may put 4 damage counters on 1 of your opponent's Pokemon.""" + if not await ctx.ask_yes_no("Put 4 damage counters on 1 of your opponent's Pokémon?"): + return + targets = ctx.opponent_pokemon_in_play() + if not targets: + return + target = await ctx.choose_pokemon(targets, "Choose 1 of your opponent's Pokémon") + if target is not None: + await ctx.deal_damage(40, target=target, apply_modifiers=False, as_counters=True) + + card = PokemonCardDef( guid="1eef51e4-cb3f-598c-a676-381c64aa7200", key="SWSH10", @@ -21,16 +35,18 @@ card = PokemonCardDef( abilities=[ Ability( title="Moon Cleave Star", - game_text="During your turn, you may put 4 damage counters on 1 of your opponent's Pok\u00e9mon. (You can't use more than 1 VSTAR Power in a game.)", - effect=unimplemented, + game_text="During your turn, you may put 4 damage counters on 1 of your opponent's Pokémon. (You can't use more than 1 VSTAR Power in a game.)", + activation=Activations.ONCE_PER_TURN, + vstar=True, + effect=moon_cleave_star, ), Attack( title="Merciless Blade", - game_text="If your opponent's Active Pok\u00e9mon already has any damage counters on it, this attack does 110 more damage.", + game_text="If your opponent's Active Pokémon already has any damage counters on it, this attack does 110 more damage.", cost={PokemonTypes.DARKNESS: 2}, damage=110, damage_operator="+", - effect=unimplemented, + effect=bonus_if(has_damage(), 110), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH10/HisuianSamurottVSTAR_197.py b/spirit/game/scripts/cards/SWSH10/HisuianSamurottVSTAR_197.py index 26c7ea2..ace0e2b 100644 --- a/spirit/game/scripts/cards/SWSH10/HisuianSamurottVSTAR_197.py +++ b/spirit/game/scripts/cards/SWSH10/HisuianSamurottVSTAR_197.py @@ -1,6 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import bonus_if, has_damage +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def moon_cleave_star(ctx): + """VSTAR Power: you may put 4 damage counters on 1 of your opponent's Pokemon.""" + if not await ctx.ask_yes_no("Put 4 damage counters on 1 of your opponent's Pokémon?"): + return + targets = ctx.opponent_pokemon_in_play() + if not targets: + return + target = await ctx.choose_pokemon(targets, "Choose 1 of your opponent's Pokémon") + if target is not None: + await ctx.deal_damage(40, target=target, apply_modifiers=False, as_counters=True) + + card = PokemonCardDef( guid="3a0cbc59-d52b-5cb0-b1c2-13f79ea5e104", key="SWSH10", @@ -21,16 +35,18 @@ card = PokemonCardDef( abilities=[ Ability( title="Moon Cleave Star", - game_text="During your turn, you may put 4 damage counters on 1 of your opponent's Pok\u00e9mon. (You can't use more than 1 VSTAR Power in a game.)", - effect=unimplemented, + game_text="During your turn, you may put 4 damage counters on 1 of your opponent's Pokémon. (You can't use more than 1 VSTAR Power in a game.)", + activation=Activations.ONCE_PER_TURN, + vstar=True, + effect=moon_cleave_star, ), Attack( title="Merciless Blade", - game_text="If your opponent's Active Pok\u00e9mon already has any damage counters on it, this attack does 110 more damage.", + game_text="If your opponent's Active Pokémon already has any damage counters on it, this attack does 110 more damage.", cost={PokemonTypes.DARKNESS: 2}, damage=110, damage_operator="+", - effect=unimplemented, + effect=bonus_if(has_damage(), 110), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH10/HisuianSamurottVSTAR_209.py b/spirit/game/scripts/cards/SWSH10/HisuianSamurottVSTAR_209.py index f1dfcb6..f7060dd 100644 --- a/spirit/game/scripts/cards/SWSH10/HisuianSamurottVSTAR_209.py +++ b/spirit/game/scripts/cards/SWSH10/HisuianSamurottVSTAR_209.py @@ -1,6 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import bonus_if, has_damage +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def moon_cleave_star(ctx): + """VSTAR Power: you may put 4 damage counters on 1 of your opponent's Pokemon.""" + if not await ctx.ask_yes_no("Put 4 damage counters on 1 of your opponent's Pokémon?"): + return + targets = ctx.opponent_pokemon_in_play() + if not targets: + return + target = await ctx.choose_pokemon(targets, "Choose 1 of your opponent's Pokémon") + if target is not None: + await ctx.deal_damage(40, target=target, apply_modifiers=False, as_counters=True) + + card = PokemonCardDef( guid="3c5e8458-586e-5182-a337-28ed4dec82a5", key="SWSH10", @@ -21,16 +35,18 @@ card = PokemonCardDef( abilities=[ Ability( title="Moon Cleave Star", - game_text="During your turn, you may put 4 damage counters on 1 of your opponent's Pok\u00e9mon. (You can't use more than 1 VSTAR Power in a game.)", - effect=unimplemented, + game_text="During your turn, you may put 4 damage counters on 1 of your opponent's Pokémon. (You can't use more than 1 VSTAR Power in a game.)", + activation=Activations.ONCE_PER_TURN, + vstar=True, + effect=moon_cleave_star, ), Attack( title="Merciless Blade", - game_text="If your opponent's Active Pok\u00e9mon already has any damage counters on it, this attack does 110 more damage.", + game_text="If your opponent's Active Pokémon already has any damage counters on it, this attack does 110 more damage.", cost={PokemonTypes.DARKNESS: 2}, damage=110, damage_operator="+", - effect=unimplemented, + effect=bonus_if(has_damage(), 110), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH10/HisuianSamurottV_101.py b/spirit/game/scripts/cards/SWSH10/HisuianSamurottV_101.py index fdcd15f..5268daa 100644 --- a/spirit/game/scripts/cards/SWSH10/HisuianSamurottV_101.py +++ b/spirit/game/scripts/cards/SWSH10/HisuianSamurottV_101.py @@ -1,5 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import self_energy_discard_attack + + +async def basket_crash(ctx): + """Discard up to 2 Pokémon Tools from your opponent's Pokémon.""" + tools = [t for t, p in ctx.tools_in_play() if p.owning_player_id == ctx.opponent_id] + if not tools: + return + picks = await ctx.choose_cards( + tools, min(2, len(tools)), minimum=0, + prompt="Discard up to 2 Pokémon Tools from your opponent's Pokémon.", + ) + if picks: + await ctx.discard_cards(picks) + card = PokemonCardDef( guid="3600427a-72e5-5b32-9bf5-0dfbc1846b39", @@ -22,14 +37,14 @@ card = PokemonCardDef( title="Basket Crash", game_text="Discard up to 2 Pok\u00e9mon Tools from your opponent's Pok\u00e9mon.", cost={PokemonTypes.DARKNESS: 1}, - effect=unimplemented, + effect=basket_crash, ), Attack( title="Shadow Slash", game_text="Discard an Energy from this Pok\u00e9mon.", cost={PokemonTypes.DARKNESS: 3}, damage=180, - effect=unimplemented, + effect=self_energy_discard_attack(count=1), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/HisuianSamurottV_176.py b/spirit/game/scripts/cards/SWSH10/HisuianSamurottV_176.py index 69fcbc8..464bcc8 100644 --- a/spirit/game/scripts/cards/SWSH10/HisuianSamurottV_176.py +++ b/spirit/game/scripts/cards/SWSH10/HisuianSamurottV_176.py @@ -1,5 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import self_energy_discard_attack + + +async def basket_crash(ctx): + """Discard up to 2 Pokémon Tools from your opponent's Pokémon.""" + tools = [t for t, p in ctx.tools_in_play() if p.owning_player_id == ctx.opponent_id] + if not tools: + return + picks = await ctx.choose_cards( + tools, min(2, len(tools)), minimum=0, + prompt="Discard up to 2 Pokémon Tools from your opponent's Pokémon.", + ) + if picks: + await ctx.discard_cards(picks) + card = PokemonCardDef( guid="4cefcb22-08aa-5ce0-8724-a9282f7df9e4", @@ -22,14 +37,14 @@ card = PokemonCardDef( title="Basket Crash", game_text="Discard up to 2 Pok\u00e9mon Tools from your opponent's Pok\u00e9mon.", cost={PokemonTypes.DARKNESS: 1}, - effect=unimplemented, + effect=basket_crash, ), Attack( title="Shadow Slash", game_text="Discard an Energy from this Pok\u00e9mon.", cost={PokemonTypes.DARKNESS: 3}, damage=180, - effect=unimplemented, + effect=self_energy_discard_attack(count=1), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/HisuianSamurott_100.py b/spirit/game/scripts/cards/SWSH10/HisuianSamurott_100.py index 1d3b8c4..6eada2e 100644 --- a/spirit/game/scripts/cards/SWSH10/HisuianSamurott_100.py +++ b/spirit/game/scripts/cards/SWSH10/HisuianSamurott_100.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy +from spirit.game.card_effects.support_common import discard_then_draw, requires_hand card = PokemonCardDef( guid="e7a0a6ef-bc85-5897-a9f5-ff5142325f63", @@ -22,7 +24,9 @@ card = PokemonCardDef( Ability( title="Wily Stance", game_text="You must discard a card from your hand in order to use this Ability. Once during your turn, you may draw 3 cards.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=requires_hand(n=1), + effect=discard_then_draw(1, 3, optional=False), ), Attack( title="Dark Mastery", @@ -30,7 +34,7 @@ card = PokemonCardDef( cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 2}, damage=60, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("mine"), 20, base=60), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/HisuianSneaslerV_174.py b/spirit/game/scripts/cards/SWSH10/HisuianSneaslerV_174.py index c20dc90..28f0b45 100644 --- a/spirit/game/scripts/cards/SWSH10/HisuianSneaslerV_174.py +++ b/spirit/game/scripts/cards/SWSH10/HisuianSneaslerV_174.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions, AttrID +from spirit.game.card_effects.attacks_common import condition_attack, damage_per + + +def _defender_condition_count(ctx): + defender = ctx.defender + if defender is None: + return 0 + return len(defender.get_attribute(AttrID.SPECIAL_CONDITIONS) or []) card = PokemonCardDef( guid="d346d35d-f9d9-53d8-b123-0ae051efbd2d", @@ -22,7 +30,7 @@ card = PokemonCardDef( title="Poison Claws", game_text="Your opponent's Active Pok\u00e9mon is now Poisoned.", cost={}, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED), ), Attack( title="Dire Claw", @@ -30,7 +38,7 @@ card = PokemonCardDef( cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 1}, damage=80, damage_operator="x", - effect=unimplemented, + effect=damage_per(_defender_condition_count, 80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/HisuianSneaslerV_175.py b/spirit/game/scripts/cards/SWSH10/HisuianSneaslerV_175.py index f170ca9..269fc50 100644 --- a/spirit/game/scripts/cards/SWSH10/HisuianSneaslerV_175.py +++ b/spirit/game/scripts/cards/SWSH10/HisuianSneaslerV_175.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions, AttrID +from spirit.game.card_effects.attacks_common import condition_attack, damage_per + + +def _defender_condition_count(ctx): + defender = ctx.defender + if defender is None: + return 0 + return len(defender.get_attribute(AttrID.SPECIAL_CONDITIONS) or []) card = PokemonCardDef( guid="1f899139-84ed-585c-bedb-fb747ec1eca5", @@ -22,7 +30,7 @@ card = PokemonCardDef( title="Poison Claws", game_text="Your opponent's Active Pok\u00e9mon is now Poisoned.", cost={}, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED), ), Attack( title="Dire Claw", @@ -30,7 +38,7 @@ card = PokemonCardDef( cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 1}, damage=80, damage_operator="x", - effect=unimplemented, + effect=damage_per(_defender_condition_count, 80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/HisuianSneaslerV_94.py b/spirit/game/scripts/cards/SWSH10/HisuianSneaslerV_94.py index bb6a36d..e172f73 100644 --- a/spirit/game/scripts/cards/SWSH10/HisuianSneaslerV_94.py +++ b/spirit/game/scripts/cards/SWSH10/HisuianSneaslerV_94.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions, AttrID +from spirit.game.card_effects.attacks_common import condition_attack, damage_per + + +def _defender_condition_count(ctx): + defender = ctx.defender + if defender is None: + return 0 + return len(defender.get_attribute(AttrID.SPECIAL_CONDITIONS) or []) card = PokemonCardDef( guid="0698688d-e2ff-5667-bcc6-41cff81c5c64", @@ -22,7 +30,7 @@ card = PokemonCardDef( title="Poison Claws", game_text="Your opponent's Active Pok\u00e9mon is now Poisoned.", cost={}, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED), ), Attack( title="Dire Claw", @@ -30,7 +38,7 @@ card = PokemonCardDef( cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 1}, damage=80, damage_operator="x", - effect=unimplemented, + effect=damage_per(_defender_condition_count, 80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/HisuianSneasler_93.py b/spirit/game/scripts/cards/SWSH10/HisuianSneasler_93.py index 66e625d..91be21c 100644 --- a/spirit/game/scripts/cards/SWSH10/HisuianSneasler_93.py +++ b/spirit/game/scripts/cards/SWSH10/HisuianSneasler_93.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import retreat_discount, is_in_active_spot card = PokemonCardDef( guid="735e5851-e086-5a35-9884-6c632b8a286f", @@ -22,7 +23,14 @@ card = PokemonCardDef( Ability( title="Carry and Climb", game_text="As long as this Pok\u00e9mon is on your Bench, your Active Pok\u00e9mon's Retreat Cost is ColorlessColorless less.", - effect=unimplemented, + passive=retreat_discount( + 2, + target_pred=lambda pokemon, carrier: ( + pokemon.owning_player_id == carrier.owning_player_id + and is_in_active_spot(pokemon) + and not is_in_active_spot(carrier) + ), + ), ), Attack( title="Claw Slash", diff --git a/spirit/game/scripts/cards/SWSH10/HisuianTyphlosionVSTAR_193.py b/spirit/game/scripts/cards/SWSH10/HisuianTyphlosionVSTAR_193.py index 5a7c82b..8c39c6e 100644 --- a/spirit/game/scripts/cards/SWSH10/HisuianTyphlosionVSTAR_193.py +++ b/spirit/game/scripts/cards/SWSH10/HisuianTyphlosionVSTAR_193.py @@ -1,6 +1,23 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import damage_counters_on +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def hollow_flame(ctx): + """180. Put 3 damage counters on your opponent's Benched Pokemon in any way you like.""" + await ctx.deal_damage() + bench = ctx.opponent_bench() + if bench: + await ctx.place_damage_counters(3, bench) + + +async def shimmering_star(ctx): + """VSTAR Power: if the opponent's Active has exactly 4 damage counters, KO it.""" + target = ctx.opponent_active() + if target is not None and damage_counters_on("defender")(ctx) == 4: + await ctx.knock_out(target) + + card = PokemonCardDef( guid="7320f688-c15a-54e5-a296-d3e4291cb88e", key="SWSH10", @@ -22,16 +39,17 @@ card = PokemonCardDef( abilities=[ Attack( title="Hollow Flame", - game_text="Put 3 damage counters on your opponent's Benched Pok\u00e9mon in any way you like.", + game_text="Put 3 damage counters on your opponent's Benched Pokémon in any way you like.", cost={PokemonTypes.PSYCHIC: 2, PokemonTypes.COLORLESS: 1}, damage=180, - effect=unimplemented, + effect=hollow_flame, ), Attack( title="Shimmering Star", - game_text="If your opponent's Active Pok\u00e9mon has exactly 4 damage counters on it, that Pok\u00e9mon is Knocked Out. (You can't use more than 1 VSTAR Power in a game.)", + game_text="If your opponent's Active Pokémon has exactly 4 damage counters on it, that Pokémon is Knocked Out. (You can't use more than 1 VSTAR Power in a game.)", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + vstar=True, + effect=shimmering_star, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH10/HisuianTyphlosionVSTAR_54.py b/spirit/game/scripts/cards/SWSH10/HisuianTyphlosionVSTAR_54.py index 8579623..a60abdd 100644 --- a/spirit/game/scripts/cards/SWSH10/HisuianTyphlosionVSTAR_54.py +++ b/spirit/game/scripts/cards/SWSH10/HisuianTyphlosionVSTAR_54.py @@ -1,6 +1,23 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import damage_counters_on +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def hollow_flame(ctx): + """180. Put 3 damage counters on your opponent's Benched Pokemon in any way you like.""" + await ctx.deal_damage() + bench = ctx.opponent_bench() + if bench: + await ctx.place_damage_counters(3, bench) + + +async def shimmering_star(ctx): + """VSTAR Power: if the opponent's Active has exactly 4 damage counters, KO it.""" + target = ctx.opponent_active() + if target is not None and damage_counters_on("defender")(ctx) == 4: + await ctx.knock_out(target) + + card = PokemonCardDef( guid="a78f3598-996f-509d-af11-f3865ae13061", key="SWSH10", @@ -22,16 +39,17 @@ card = PokemonCardDef( abilities=[ Attack( title="Hollow Flame", - game_text="Put 3 damage counters on your opponent's Benched Pok\u00e9mon in any way you like.", + game_text="Put 3 damage counters on your opponent's Benched Pokémon in any way you like.", cost={PokemonTypes.PSYCHIC: 2, PokemonTypes.COLORLESS: 1}, damage=180, - effect=unimplemented, + effect=hollow_flame, ), Attack( title="Shimmering Star", - game_text="If your opponent's Active Pok\u00e9mon has exactly 4 damage counters on it, that Pok\u00e9mon is Knocked Out. (You can't use more than 1 VSTAR Power in a game.)", + game_text="If your opponent's Active Pokémon has exactly 4 damage counters on it, that Pokémon is Knocked Out. (You can't use more than 1 VSTAR Power in a game.)", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + vstar=True, + effect=shimmering_star, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH10/HisuianTyphlosionV_169.py b/spirit/game/scripts/cards/SWSH10/HisuianTyphlosionV_169.py index 9d4af81..7098322 100644 --- a/spirit/game/scripts/cards/SWSH10/HisuianTyphlosionV_169.py +++ b/spirit/game/scripts/cards/SWSH10/HisuianTyphlosionV_169.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +import random + +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack + + +async def _petrifying_flame(ctx): + await ctx.deal_damage() + hand = ctx.hand(ctx.opponent_id) + if hand: + pick = random.choice(hand) + await ctx.reveal_cards([pick]) + await ctx.shuffle_into_deck([pick], player_id=ctx.opponent_id) card = PokemonCardDef( guid="796df167-8415-5502-aa7a-51e5f5c06645", @@ -23,14 +35,14 @@ card = PokemonCardDef( title="Singe", game_text="Your opponent's Active Pok\u00e9mon is now Burned.", cost={}, - effect=unimplemented, + effect=condition_attack(SpecialConditions.BURNED), ), Attack( title="Petrifying Flame", game_text="Choose a random card from your opponent's hand. Your opponent reveals that card and shuffles it into their deck.", cost={PokemonTypes.PSYCHIC: 2, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + effect=_petrifying_flame, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/HisuianTyphlosionV_53.py b/spirit/game/scripts/cards/SWSH10/HisuianTyphlosionV_53.py index 1951601..49bc5bd 100644 --- a/spirit/game/scripts/cards/SWSH10/HisuianTyphlosionV_53.py +++ b/spirit/game/scripts/cards/SWSH10/HisuianTyphlosionV_53.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +import random + +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack + + +async def _petrifying_flame(ctx): + await ctx.deal_damage() + hand = ctx.hand(ctx.opponent_id) + if hand: + pick = random.choice(hand) + await ctx.reveal_cards([pick]) + await ctx.shuffle_into_deck([pick], player_id=ctx.opponent_id) card = PokemonCardDef( guid="b3fc8959-c8de-55b3-89e5-9fc25546a119", @@ -23,14 +35,14 @@ card = PokemonCardDef( title="Singe", game_text="Your opponent's Active Pok\u00e9mon is now Burned.", cost={}, - effect=unimplemented, + effect=condition_attack(SpecialConditions.BURNED), ), Attack( title="Petrifying Flame", game_text="Choose a random card from your opponent's hand. Your opponent reveals that card and shuffles it into their deck.", cost={PokemonTypes.PSYCHIC: 2, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + effect=_petrifying_flame, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/HisuianTyphlosion_52.py b/spirit/game/scripts/cards/SWSH10/HisuianTyphlosion_52.py index c034061..eebce57 100644 --- a/spirit/game/scripts/cards/SWSH10/HisuianTyphlosion_52.py +++ b/spirit/game/scripts/cards/SWSH10/HisuianTyphlosion_52.py @@ -1,5 +1,25 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions, AttrID +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.support_common import requires_hand +from spirit.game.card_effects.trainers import is_basic_energy_card + + +def _is_psychic_energy(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_basic_energy_card(card) and PokemonTypes.PSYCHIC.value in types + + +async def _supernatural_orb(ctx): + discarded = await ctx.discard_from_hand( + 1, prompt="Discard a Psychic Energy card for Supernatural Orb", + predicate=_is_psychic_energy, + ) + if not discarded: + return + await ctx.apply_special_condition(ctx.defender, SpecialConditions.BURNED) + await ctx.apply_special_condition(ctx.defender, SpecialConditions.CONFUSED) + card = PokemonCardDef( guid="322b0073-6630-5cfb-9494-ed708adfb6d9", @@ -23,14 +43,16 @@ card = PokemonCardDef( Ability( title="Supernatural Orb", game_text="You must discard a Psychic Energy card from your hand in order to use this Ability. Once during your turn, you may make your opponent's Active Pok\u00e9mon Burned and Confused.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=requires_hand(_is_psychic_energy, n=1), + effect=_supernatural_orb, ), Attack( title="Shadow Bind", game_text="During your opponent's next turn, the Defending Pok\u00e9mon can't retreat.", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=90, - effect=unimplemented, + effect=condition_attack(no_retreat=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/HisuianVoltorb_2.py b/spirit/game/scripts/cards/SWSH10/HisuianVoltorb_2.py index 122ec73..bb4bfb4 100644 --- a/spirit/game/scripts/cards/SWSH10/HisuianVoltorb_2.py +++ b/spirit/game/scripts/cards/SWSH10/HisuianVoltorb_2.py @@ -1,5 +1,30 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.trainers import is_basic_energy_card + + +def _only_turn_2(board, player_id, pokemon): + return board.turn_state.turn_number == 2 + + +async def cheerful_charge(ctx): + bench = ctx.my_bench() + if not bench: + return + chosen = await ctx.choose_cards( + bench, 2, minimum=0, prompt="Choose up to 2 Benched Pokémon.", + ) + if not chosen: + return + for pokemon in chosen: + picks = await ctx.search_deck( + is_basic_energy_card, count=1, minimum=0, + prompt="Choose a basic Energy card to attach.", + ) + if picks: + await ctx.attach_energy(picks[0], pokemon) + await ctx.shuffle_deck() + card = PokemonCardDef( guid="fe1c3af4-e99a-5407-b5b7-098f66828cb2", @@ -22,7 +47,8 @@ card = PokemonCardDef( title="Cheerful Charge", game_text="You can use this attack only if you go second, and only during your first turn. Choose up to 2 of your Benched Pok\u00e9mon. For each of those Pok\u00e9mon, search your deck for a basic Energy card and attach it to that Pok\u00e9mon. Then, shuffle your deck.", cost={}, - effect=unimplemented, + condition=_only_turn_2, + effect=cheerful_charge, ), Attack( title="Ram", diff --git a/spirit/game/scripts/cards/SWSH10/Hoothoot_120.py b/spirit/game/scripts/cards/SWSH10/Hoothoot_120.py index 0c0e8b3..033068b 100644 --- a/spirit/game/scripts/cards/SWSH10/Hoothoot_120.py +++ b/spirit/game/scripts/cards/SWSH10/Hoothoot_120.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.session.passives import Passive +from spirit.game.card_effects.trainers import is_basic_energy_card + + +class StandSentryPassive(Passive): + def blocks_discard(self, card, carrier): + if not is_basic_energy_card(card): + return False + holder = getattr(card, "parent", None) + if holder is None or holder.owning_player_id != carrier.owning_player_id: + return False + area = getattr(holder, "parent", None) + return bool(area) and area.get_attribute(AttrID.NAME) == "bench" + card = PokemonCardDef( guid="10c015bf-0144-5d72-b794-08a571c9cf2a", @@ -22,7 +36,7 @@ card = PokemonCardDef( Ability( title="Stand Sentry", game_text="Basic Energy attached to your Benched Pok\u00e9mon can't be discarded by an effect of your opponent's Item or Supporter cards.", - effect=unimplemented, + passive=StandSentryPassive(), ), Attack( title="Flap", diff --git a/spirit/game/scripts/cards/SWSH10/Irida_147.py b/spirit/game/scripts/cards/SWSH10/Irida_147.py index af4686b..1bd07da 100644 --- a/spirit/game/scripts/cards/SWSH10/Irida_147.py +++ b/spirit/game/scripts/cards/SWSH10/Irida_147.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.card_effects.trainers import irida +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities card = SupporterCardDef( @@ -11,5 +12,5 @@ card = SupporterCardDef( collector_number=147, set_code="SWSH10", rarity=Rarities.RareHolo, - effect=unimplemented + effect=irida ) diff --git a/spirit/game/scripts/cards/SWSH10/Irida_204.py b/spirit/game/scripts/cards/SWSH10/Irida_204.py index f136b26..decf47b 100644 --- a/spirit/game/scripts/cards/SWSH10/Irida_204.py +++ b/spirit/game/scripts/cards/SWSH10/Irida_204.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.card_effects.trainers import irida +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities card = SupporterCardDef( @@ -11,5 +12,5 @@ card = SupporterCardDef( collector_number=204, set_code="SWSH10", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=irida ) diff --git a/spirit/game/scripts/cards/SWSH10/JirachiV_170.py b/spirit/game/scripts/cards/SWSH10/JirachiV_170.py index fbf4705..3a74051 100644 --- a/spirit/game/scripts/cards/SWSH10/JirachiV_170.py +++ b/spirit/game/scripts/cards/SWSH10/JirachiV_170.py @@ -1,5 +1,38 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers, subtypes_for +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.trainers import is_basic_energy_card + + +def _is_basic_pokemon_v(pokemon): + types = subtypes_for(pokemon.archetype_id) + return "Basic" in types and "V" in types + + +async def wish_connector(ctx): + """Ally Basic Pokemon V KO'd by an opponent's attack: you may move a + basic Energy from it to another of your Pokemon.""" + if not ctx.ko_from_attack or ctx.ko_pokemon is None: + return + if not _is_basic_pokemon_v(ctx.ko_pokemon): + return + basics = [e for e in ctx.attached_energies(ctx.ko_pokemon) if is_basic_energy_card(e)] + if not basics: + return + others = [p for p in ctx.my_pokemon_in_play() if p is not ctx.ko_pokemon] + if not others: + return + if not await ctx.ask_yes_no( + "Move a basic Energy card from the Knocked Out Pokémon to another of your Pokémon?" + ): + return + picks = await ctx.choose_cards(basics, 1, prompt="Choose a basic Energy card to move") + if not picks: + return + target = await ctx.choose_pokemon(others, "Choose a Pokémon to move the Energy to") + if target is not None: + await ctx.move_energy(picks[0], target) + card = PokemonCardDef( guid="ded4a63a-004b-56c4-9eba-c4086feb7e91", @@ -21,15 +54,16 @@ card = PokemonCardDef( abilities=[ Ability( title="Wish Connector", - game_text="When 1 of your Basic Pok\u00e9mon V is Knocked Out by damage from an attack from your opponent's Pok\u00e9mon, you may move a basic Energy card from that Pok\u00e9mon to another of your Pok\u00e9mon.", - effect=unimplemented, + game_text="When 1 of your Basic Pokémon V is Knocked Out by damage from an attack from your opponent's Pokémon, you may move a basic Energy card from that Pokémon to another of your Pokémon.", + trigger=Triggers.ON_ALLY_KNOCKED_OUT, + effect=wish_connector, ), Attack( title="Hypnostrike", - game_text="Both Active Pok\u00e9mon are now Asleep.", + game_text="Both Active Pokémon are now Asleep.", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=60, - effect=unimplemented, + effect=condition_attack(SpecialConditions.ASLEEP, both_actives=True), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH10/JubilifeVillage_148.py b/spirit/game/scripts/cards/SWSH10/JubilifeVillage_148.py index 8ec9a7e..907494c 100644 --- a/spirit/game/scripts/cards/SWSH10/JubilifeVillage_148.py +++ b/spirit/game/scripts/cards/SWSH10/JubilifeVillage_148.py @@ -1,6 +1,21 @@ -from spirit.game.data_utils import StadiumCardDef, unimplemented +from spirit.game.data_utils import StadiumCardDef, Ability, Activations from spirit.game.attributes import Rarities + +async def jubilife_village(ctx): + """Shuffle your hand into your deck and draw 5 cards. Your turn ends.""" + await ctx.shuffle_into_deck(ctx.hand(), ctx.player_id) + await ctx.draw_cards(5) + ctx.ends_turn = True + + +JUBILIFE_VILLAGE_ABILITY = Ability( + title="Jubilife Village", + game_text="Once during each player's turn, that player may shuffle their hand into their deck and draw 5 cards. If they do, their turn ends.", + activation=Activations.ONCE_PER_TURN, + effect=jubilife_village, +) + card = StadiumCardDef( guid="c8a8c5b4-17b9-5a9d-b3a9-75510d15411f", key="SWSH10", @@ -11,5 +26,5 @@ card = StadiumCardDef( collector_number=148, set_code="SWSH10", rarity=Rarities.Uncommon, - effect=unimplemented + ability=JUBILIFE_VILLAGE_ABILITY, ) diff --git a/spirit/game/scripts/cards/SWSH10/JubilifeVillage_212.py b/spirit/game/scripts/cards/SWSH10/JubilifeVillage_212.py index 3433520..f85e760 100644 --- a/spirit/game/scripts/cards/SWSH10/JubilifeVillage_212.py +++ b/spirit/game/scripts/cards/SWSH10/JubilifeVillage_212.py @@ -1,6 +1,21 @@ -from spirit.game.data_utils import StadiumCardDef, unimplemented +from spirit.game.data_utils import StadiumCardDef, Ability, Activations from spirit.game.attributes import Rarities + +async def jubilife_village(ctx): + """Shuffle your hand into your deck and draw 5 cards. Your turn ends.""" + await ctx.shuffle_into_deck(ctx.hand(), ctx.player_id) + await ctx.draw_cards(5) + ctx.ends_turn = True + + +JUBILIFE_VILLAGE_ABILITY = Ability( + title="Jubilife Village", + game_text="Once during each player's turn, that player may shuffle their hand into their deck and draw 5 cards. If they do, their turn ends.", + activation=Activations.ONCE_PER_TURN, + effect=jubilife_village, +) + card = StadiumCardDef( guid="0748aebd-48b8-5322-8299-248536d71436", key="SWSH10", @@ -11,5 +26,5 @@ card = StadiumCardDef( collector_number=212, set_code="SWSH10", rarity=Rarities.RareSecret, - effect=unimplemented + ability=JUBILIFE_VILLAGE_ABILITY, ) diff --git a/spirit/game/scripts/cards/SWSH10/Keldeo_45.py b/spirit/game/scripts/cards/SWSH10/Keldeo_45.py index 16bf2cc..e1347ce 100644 --- a/spirit/game/scripts/cards/SWSH10/Keldeo_45.py +++ b/spirit/game/scripts/cards/SWSH10/Keldeo_45.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_bench card = PokemonCardDef( guid="84e11265-f186-5fc2-ab73-4abe8a0626d2", @@ -29,7 +30,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 1}, damage=10, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_bench("mine"), 20, base=10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/Kirlia_61.py b/spirit/game/scripts/cards/SWSH10/Kirlia_61.py index 9160635..fee7fc4 100644 --- a/spirit/game/scripts/cards/SWSH10/Kirlia_61.py +++ b/spirit/game/scripts/cards/SWSH10/Kirlia_61.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import switch_self_attack card = PokemonCardDef( guid="dd5103a7-46e0-5631-b159-c11b68b649e4", @@ -25,7 +26,7 @@ card = PokemonCardDef( game_text="Switch this Pok\u00e9mon with 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.PSYCHIC: 1}, damage=30, - effect=unimplemented, + effect=switch_self_attack(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/KleavorVSTAR_196.py b/spirit/game/scripts/cards/SWSH10/KleavorVSTAR_196.py index 3b9f98f..32ac867 100644 --- a/spirit/game/scripts/cards/SWSH10/KleavorVSTAR_196.py +++ b/spirit/game/scripts/cards/SWSH10/KleavorVSTAR_196.py @@ -1,5 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, is_pokemon_v from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_discard +from spirit.game.session.effects import is_pokemon_card + + +async def axe_break(ctx): + """120 damage; also 60 to 1 opposing Benched Pokemon V (no W/R).""" + await ctx.deal_damage() + bench = [p for p in ctx.opponent_bench() if is_pokemon_v(p.archetype_id)] + if bench: + target = await ctx.choose_pokemon( + bench, "Choose 1 of your opponent's Benched Pokémon V" + ) + if target is not None: + await ctx.deal_damage(60, target=target, apply_modifiers=False) + card = PokemonCardDef( guid="51cf594f-bd24-5bf1-9da5-7815dae5be1e", @@ -24,7 +39,7 @@ card = PokemonCardDef( game_text="This attack also does 60 damage to 1 of your opponent's Benched Pok\u00e9mon V. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + effect=axe_break, ), Attack( title="Rampaging Star", @@ -32,7 +47,8 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1}, damage=30, damage_operator="x", - effect=unimplemented, + vstar=True, + effect=damage_per(count_discard("mine", pred=is_pokemon_card), 30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/KleavorV_87.py b/spirit/game/scripts/cards/SWSH10/KleavorV_87.py index 2ac0037..9cad803 100644 --- a/spirit/game/scripts/cards/SWSH10/KleavorV_87.py +++ b/spirit/game/scripts/cards/SWSH10/KleavorV_87.py @@ -1,6 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def axe_slash(ctx): + """Discard an Energy from this Pokémon. If you do, discard an Energy from your opponent's Active Pokémon.""" + await ctx.deal_damage() + discarded = await ctx.discard_energy_from(ctx.attacker, 1) + if not discarded: + return + defender = ctx.opponent_active() + if defender is None or ctx.effects_blocked(defender): + return + await ctx.discard_energy_from(defender, 1) + + card = PokemonCardDef( guid="77f605f7-0d49-54c0-a30c-86433a18731b", key="SWSH10", @@ -28,7 +41,7 @@ card = PokemonCardDef( game_text="Discard an Energy from this Pok\u00e9mon. If you do, discard an Energy from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=150, - effect=unimplemented, + effect=axe_slash, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/Kleavor_85.py b/spirit/game/scripts/cards/SWSH10/Kleavor_85.py index dc67b16..18b649b 100644 --- a/spirit/game/scripts/cards/SWSH10/Kleavor_85.py +++ b/spirit/game/scripts/cards/SWSH10/Kleavor_85.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_bench, recoil_attack card = PokemonCardDef( guid="3ffacdb3-a8a6-5cd8-97ba-6608c4bf837b", @@ -25,14 +26,14 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=10, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_bench("opponent"), 30, base=10), ), Attack( title="Rocky Tackle", game_text="This Pok\u00e9mon also does 50 damage to itself.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 2}, damage=150, - effect=unimplemented, + effect=recoil_attack(50), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/Kleavor_86.py b/spirit/game/scripts/cards/SWSH10/Kleavor_86.py index 9417d6e..2c3daf3 100644 --- a/spirit/game/scripts/cards/SWSH10/Kleavor_86.py +++ b/spirit/game/scripts/cards/SWSH10/Kleavor_86.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack + + +async def _timber_cleave(ctx): + """Flip 2 coins. If both are heads, the opponent's Active is Knocked Out.""" + coins = await ctx.flip_coins(2, "Timber Cleave") + if all(coins): + await ctx.knock_out(ctx.defender) card = PokemonCardDef( guid="99d1fae8-9bcc-5f8b-80a0-670c1f2188c7", @@ -23,14 +31,14 @@ card = PokemonCardDef( title="Timber Cleave", game_text="Flip 2 coins. If both of them are heads, your opponent's Active Pok\u00e9mon is Knocked Out.", cost={PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + effect=_timber_cleave, ), Attack( title="Berserker Tackle", game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.FIGHTING: 2}, damage=120, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/Kricketot_9.py b/spirit/game/scripts/cards/SWSH10/Kricketot_9.py index 25774b1..9d374a5 100644 --- a/spirit/game/scripts/cards/SWSH10/Kricketot_9.py +++ b/spirit/game/scripts/cards/SWSH10/Kricketot_9.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus card = PokemonCardDef( guid="807e0c67-bd5c-5e6c-8f54-d81433be1d35", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1}, damage=10, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/Kricketune_10.py b/spirit/game/scripts/cards/SWSH10/Kricketune_10.py index 7f1bc65..07fdd6e 100644 --- a/spirit/game/scripts/cards/SWSH10/Kricketune_10.py +++ b/spirit/game/scripts/cards/SWSH10/Kricketune_10.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.session.passives import Passive + + +class SwellingTunePassive(Passive): + stacking_key = "SwellingTune" + + def max_hp_bonus(self, pokemon, carrier): + if pokemon.owning_player_id != carrier.owning_player_id: + return 0 + if pokemon.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) == "Kricketune": + return 0 + types = pokemon.get_attribute(AttrID.POKEMON_TYPES) or [] + if PokemonTypes.GRASS.value not in types: + return 0 + return 40 + card = PokemonCardDef( guid="5192187e-fe12-525b-8fb7-c6beffdc4049", @@ -21,8 +37,8 @@ card = PokemonCardDef( abilities=[ Ability( title="Swelling Tune", - game_text="Your Grass Pok\u00e9mon in play, except any Kricketune, get +40 HP. You can't apply more than 1 Swelling Tune Ability at a time.", - effect=unimplemented, + game_text="Your Grass Pokémon in play, except any Kricketune, get +40 HP. You can't apply more than 1 Swelling Tune Ability at a time.", + passive=SwellingTunePassive(), ), Attack( title="Slash", diff --git a/spirit/game/scripts/cards/SWSH10/Leafeon_13.py b/spirit/game/scripts/cards/SWSH10/Leafeon_13.py index 62a2189..8fad4c9 100644 --- a/spirit/game/scripts/cards/SWSH10/Leafeon_13.py +++ b/spirit/game/scripts/cards/SWSH10/Leafeon_13.py @@ -1,5 +1,42 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.card_effects.attacks_common import lock_all_attacks + + +async def energy_garden(ctx): + """Search for up to 3 basic Energy of different types and attach them freely; shuffle.""" + deck_cards = list(ctx.deck(ctx.player_id)) + reps = [] + seen_types = [] + for card in deck_cards: + if not is_basic_energy_card(card): + continue + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + if not types or types[0] in seen_types: + continue + seen_types.append(types[0]) + reps.append(card) + if reps: + picks = await ctx.choose_cards( + reps, 3, minimum=0, + prompt="Choose up to 3 basic Energy cards of different types.", + display_cards=deck_cards, + ) + for energy in picks: + target = await ctx.choose_pokemon( + ctx.my_pokemon_in_play(), "Choose a Pokémon to attach the Energy to" + ) + if target is not None: + await ctx.attach_energy(energy, target) + await ctx.shuffle_deck() + + +async def leafy_cyclone(ctx): + """120 damage; this Pokémon can't attack during your next turn.""" + await ctx.deal_damage() + lock_all_attacks(ctx, ctx.attacker) + card = PokemonCardDef( guid="40c6ef9d-605f-5519-8a8b-b0e685b89145", @@ -23,14 +60,14 @@ card = PokemonCardDef( title="Energy Garden", game_text="Search your deck for up to 3 basic Energy cards of different types and attach them to your Pok\u00e9mon in any way you like. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=energy_garden, ), Attack( title="Leafy Cyclone", game_text="During your next turn, this Pok\u00e9mon can't attack.", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 2}, damage=120, - effect=unimplemented, + effect=leafy_cyclone, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/LucarioV_78.py b/spirit/game/scripts/cards/SWSH10/LucarioV_78.py index fc07f6d..578697c 100644 --- a/spirit/game/scripts/cards/SWSH10/LucarioV_78.py +++ b/spirit/game/scripts/cards/SWSH10/LucarioV_78.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import discard_opponent_energy_attack card = PokemonCardDef( guid="e58dee9d-1c90-50f0-b9c5-0cd00a1c5751", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Discard a Special Energy from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 2}, damage=50, - effect=unimplemented, + effect=discard_opponent_energy_attack(special_only=True), ), Attack( title="Cyclone Kick", diff --git a/spirit/game/scripts/cards/SWSH10/LuxrayV_168.py b/spirit/game/scripts/cards/SWSH10/LuxrayV_168.py index 7ac3b4c..904efc0 100644 --- a/spirit/game/scripts/cards/SWSH10/LuxrayV_168.py +++ b/spirit/game/scripts/cards/SWSH10/LuxrayV_168.py @@ -1,5 +1,30 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.session.effects import is_trainer_card + + +async def fang_snipe(ctx): + """30 damage. Opponent reveals their hand; discard a Trainer card found there.""" + await ctx.deal_damage() + hand = await ctx.reveal_hand(of_player=ctx.opponent_id) + matches = [c for c in hand if is_trainer_card(c)] + if not matches: + return + picks = await ctx.choose_cards( + matches, 1, minimum=1, + prompt="Choose a Trainer card to discard from your opponent's hand.", + display_cards=hand, + ) + await ctx.discard_cards(picks) + + +async def radiating_pulse(ctx): + """Discard 2 Energy from this Pokémon. 120 damage. Opponent's Active is + now Paralyzed.""" + await ctx.discard_energy_from(ctx.attacker, 2) + await ctx.deal_damage() + await ctx.apply_special_condition(ctx.defender, SpecialConditions.PARALYZED) + card = PokemonCardDef( guid="ce5226bb-a7da-5f95-b467-6a7b2a91de07", @@ -23,14 +48,14 @@ card = PokemonCardDef( game_text="Your opponent reveals their hand. Discard a Trainer card you find there.", cost={PokemonTypes.COLORLESS: 2}, damage=30, - effect=unimplemented, + effect=fang_snipe, ), Attack( title="Radiating Pulse", game_text="Discard 2 Energy from this Pok\u00e9mon. Your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + effect=radiating_pulse, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/LuxrayV_50.py b/spirit/game/scripts/cards/SWSH10/LuxrayV_50.py index 98825b1..98f52fe 100644 --- a/spirit/game/scripts/cards/SWSH10/LuxrayV_50.py +++ b/spirit/game/scripts/cards/SWSH10/LuxrayV_50.py @@ -1,5 +1,30 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.session.effects import is_trainer_card + + +async def fang_snipe(ctx): + """30 damage. Opponent reveals their hand; discard a Trainer card found there.""" + await ctx.deal_damage() + hand = await ctx.reveal_hand(of_player=ctx.opponent_id) + matches = [c for c in hand if is_trainer_card(c)] + if not matches: + return + picks = await ctx.choose_cards( + matches, 1, minimum=1, + prompt="Choose a Trainer card to discard from your opponent's hand.", + display_cards=hand, + ) + await ctx.discard_cards(picks) + + +async def radiating_pulse(ctx): + """Discard 2 Energy from this Pokémon. 120 damage. Opponent's Active is + now Paralyzed.""" + await ctx.discard_energy_from(ctx.attacker, 2) + await ctx.deal_damage() + await ctx.apply_special_condition(ctx.defender, SpecialConditions.PARALYZED) + card = PokemonCardDef( guid="73ec0fc6-3960-54a0-82c5-f0a63808b832", @@ -23,14 +48,14 @@ card = PokemonCardDef( game_text="Your opponent reveals their hand. Discard a Trainer card you find there.", cost={PokemonTypes.COLORLESS: 2}, damage=30, - effect=unimplemented, + effect=fang_snipe, ), Attack( title="Radiating Pulse", game_text="Discard 2 Energy from this Pok\u00e9mon. Your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + effect=radiating_pulse, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/MachampVMAX_194.py b/spirit/game/scripts/cards/SWSH10/MachampVMAX_194.py index bfeebdd..8e9764e 100644 --- a/spirit/game/scripts/cards/SWSH10/MachampVMAX_194.py +++ b/spirit/game/scripts/cards/SWSH10/MachampVMAX_194.py @@ -1,5 +1,10 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.attacks_common import bonus_if + + +def _bench_has_damage(ctx): + return any(p.get_attribute(AttrID.HP, 0) < ctx.max_hp(p) for p in ctx.my_bench()) card = PokemonCardDef( guid="30f60ece-43ec-5581-9836-51e31176b8a3", @@ -25,14 +30,14 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 1}, damage=80, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_bench_has_damage, 140), ), Attack( title="G-Max Chi Strike", game_text="During your next turn, this Pok\u00e9mon can't use G-Max Chi Strike.", cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=240, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/MachampVMAX_73.py b/spirit/game/scripts/cards/SWSH10/MachampVMAX_73.py index df73e36..f80806c 100644 --- a/spirit/game/scripts/cards/SWSH10/MachampVMAX_73.py +++ b/spirit/game/scripts/cards/SWSH10/MachampVMAX_73.py @@ -1,5 +1,10 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.attacks_common import bonus_if + + +def _bench_has_damage(ctx): + return any(p.get_attribute(AttrID.HP, 0) < ctx.max_hp(p) for p in ctx.my_bench()) card = PokemonCardDef( guid="dfb9d6ba-8009-5ad8-a787-557811761e37", @@ -25,14 +30,14 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 1}, damage=80, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_bench_has_damage, 140), ), Attack( title="G-Max Chi Strike", game_text="During your next turn, this Pok\u00e9mon can't use G-Max Chi Strike.", cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=240, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/MachampV_171.py b/spirit/game/scripts/cards/SWSH10/MachampV_171.py index 97d49c6..f5211a8 100644 --- a/spirit/game/scripts/cards/SWSH10/MachampV_171.py +++ b/spirit/game/scripts/cards/SWSH10/MachampV_171.py @@ -1,5 +1,10 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.attacks_common import bonus_if + + +def _bench_has_damage(ctx): + return any(p.get_attribute(AttrID.HP, 0) < ctx.max_hp(p) for p in ctx.my_bench()) card = PokemonCardDef( guid="9f074faa-8e18-534a-86d8-570653e94688", @@ -24,7 +29,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 1}, damage=50, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_bench_has_damage, 50), ), Attack( title="Seismic Toss", diff --git a/spirit/game/scripts/cards/SWSH10/MachampV_172.py b/spirit/game/scripts/cards/SWSH10/MachampV_172.py index b1ad3c5..984e7e0 100644 --- a/spirit/game/scripts/cards/SWSH10/MachampV_172.py +++ b/spirit/game/scripts/cards/SWSH10/MachampV_172.py @@ -1,5 +1,10 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.attacks_common import bonus_if + + +def _bench_has_damage(ctx): + return any(p.get_attribute(AttrID.HP, 0) < ctx.max_hp(p) for p in ctx.my_bench()) card = PokemonCardDef( guid="2afe269f-8636-57f8-9ebb-e66437bfb9f1", @@ -24,7 +29,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 1}, damage=50, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_bench_has_damage, 50), ), Attack( title="Seismic Toss", diff --git a/spirit/game/scripts/cards/SWSH10/MachampV_72.py b/spirit/game/scripts/cards/SWSH10/MachampV_72.py index 3ea5676..0006b74 100644 --- a/spirit/game/scripts/cards/SWSH10/MachampV_72.py +++ b/spirit/game/scripts/cards/SWSH10/MachampV_72.py @@ -1,5 +1,10 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.attacks_common import bonus_if + + +def _bench_has_damage(ctx): + return any(p.get_attribute(AttrID.HP, 0) < ctx.max_hp(p) for p in ctx.my_bench()) card = PokemonCardDef( guid="33224d10-7690-5f62-84fd-50970cc7e064", @@ -24,7 +29,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 1}, damage=50, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_bench_has_damage, 50), ), Attack( title="Seismic Toss", diff --git a/spirit/game/scripts/cards/SWSH10/Magnemite_105.py b/spirit/game/scripts/cards/SWSH10/Magnemite_105.py index a6867be..73c4da2 100644 --- a/spirit/game/scripts/cards/SWSH10/Magnemite_105.py +++ b/spirit/game/scripts/cards/SWSH10/Magnemite_105.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.trainers import is_metal_energy_card +from spirit.game.card_effects.support_common import recover_from_discard card = PokemonCardDef( guid="d5dd9abe-8828-52ef-b7bc-23880731b7c8", @@ -23,7 +25,10 @@ card = PokemonCardDef( title="Magnetic Catch", game_text="Shuffle up to 3 Metal Energy cards from your discard pile into your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=recover_from_discard( + predicate=is_metal_energy_card, count=3, to="deck_shuffle", + prompt="Choose up to 3 Metal Energy to shuffle into your deck.", + ), ), Attack( title="Rolling Attack", diff --git a/spirit/game/scripts/cards/SWSH10/Magneton_106.py b/spirit/game/scripts/cards/SWSH10/Magneton_106.py index deccbf6..c2fb933 100644 --- a/spirit/game/scripts/cards/SWSH10/Magneton_106.py +++ b/spirit/game/scripts/cards/SWSH10/Magneton_106.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import opponent_switches + + +async def bounce_back(ctx): + await ctx.deal_damage() + await opponent_switches(ctx) card = PokemonCardDef( guid="c712c118-0f49-5835-a227-20b56447f27a", @@ -25,7 +31,7 @@ card = PokemonCardDef( game_text="Your opponent switches their Active Pok\u00e9mon with 1 of their Benched Pok\u00e9mon.", cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 1}, damage=50, - effect=unimplemented, + effect=bounce_back, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/Magnezone_107.py b/spirit/game/scripts/cards/SWSH10/Magnezone_107.py index c817f39..c5966f4 100644 --- a/spirit/game/scripts/cards/SWSH10/Magnezone_107.py +++ b/spirit/game/scripts/cards/SWSH10/Magnezone_107.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.trainers import is_metal_energy_card +from spirit.game.card_effects.support_common import look_top_attach_energy card = PokemonCardDef( guid="02797afc-381d-566d-bdaa-10efd93d833b", @@ -23,7 +25,8 @@ card = PokemonCardDef( Ability( title="Giga Magnet", game_text="Once during your turn, you may look at the top 6 cards of your deck and attach any number of Metal Energy cards you find there to your Pok\u00e9mon in any way you like. Shuffle the other cards back into your deck.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + effect=look_top_attach_energy(6, predicate=is_metal_energy_card), ), Attack( title="Power Beam", diff --git a/spirit/game/scripts/cards/SWSH10/Mamoswine_33.py b/spirit/game/scripts/cards/SWSH10/Mamoswine_33.py index e52c87b..cd5f4ae 100644 --- a/spirit/game/scripts/cards/SWSH10/Mamoswine_33.py +++ b/spirit/game/scripts/cards/SWSH10/Mamoswine_33.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import ( + spread_damage, self_energy_discard_attack, lock_defender_attacks, +) + + +async def _iceberg_lock(ctx): + lock_defender_attacks(ctx) + card = PokemonCardDef( guid="828361bc-84ac-503f-a608-3a22b66ce148", @@ -24,14 +32,14 @@ card = PokemonCardDef( game_text="This attack also does 10 damage to each of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=80, - effect=unimplemented, + effect=spread_damage(10, side="opponent", also_base=True), ), Attack( title="Iceberg Press", game_text="Discard an Energy from this Pok\u00e9mon. During your opponent's next turn, the Defending Pok\u00e9mon can't attack.", cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 2}, damage=170, - effect=unimplemented, + effect=self_energy_discard_attack(count=1, also=_iceberg_lock), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/Mantine_34.py b/spirit/game/scripts/cards/SWSH10/Mantine_34.py index f9ed175..62a0527 100644 --- a/spirit/game/scripts/cards/SWSH10/Mantine_34.py +++ b/spirit/game/scripts/cards/SWSH10/Mantine_34.py @@ -1,5 +1,39 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.constants import BENCH_CAPACITY +from spirit.game.session.effects import is_basic_pokemon + + +def _borne_ashore_candidates(board): + candidates = [] + for pid in board.player_ids: + bench = board.find_player_area(pid, "bench") + if bench is None or len(bench.children) >= BENCH_CAPACITY: + continue + discard = board.find_player_area(pid, "discard") + if discard: + candidates.extend(c for c in discard.children if is_basic_pokemon(c)) + return candidates + + +def borne_ashore_condition(board, player_id, pokemon): + return bool(_borne_ashore_candidates(board)) + + +async def borne_ashore(ctx): + """Put a Basic Pokémon from either player's discard pile onto that + player's Bench.""" + candidates = _borne_ashore_candidates(ctx.board) + if not candidates: + return + picks = await ctx.choose_cards( + candidates, 1, minimum=1, + prompt="Choose a Basic Pokémon from either discard pile to put onto that player's Bench.", + ) + if not picks: + return + await ctx.bench_pokemon(picks[0]) + card = PokemonCardDef( guid="18d77c2d-ac89-5d17-8cd7-45a2fcf6bcda", @@ -22,7 +56,8 @@ card = PokemonCardDef( title="Borne Ashore", game_text="Put a Basic Pok\u00e9mon from either player's discard pile onto that player's Bench.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + condition=borne_ashore_condition, + effect=borne_ashore, ), Attack( title="Aqua Edge", diff --git a/spirit/game/scripts/cards/SWSH10/Mesprit_66.py b/spirit/game/scripts/cards/SWSH10/Mesprit_66.py index 061ab5c..3f13da0 100644 --- a/spirit/game/scripts/cards/SWSH10/Mesprit_66.py +++ b/spirit/game/scripts/cards/SWSH10/Mesprit_66.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.session.passives import Passive + + +class MentalShroudPassive(Passive): + """If Uxie and Azelf are also in play, your Pokemon have no Weakness.""" + + def modify_weakness(self, calc, carrier): + if calc.target.owning_player_id != carrier.owning_player_id: + return + present = { + p.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) + for p in calc.board.pokemon_in_play(carrier.owning_player_id) + } + if {"Uxie", "Azelf"} <= present: + calc.weakness_applies = False + card = PokemonCardDef( guid="b492025b-05a7-5886-a8e5-1c7b35e60b31", @@ -21,8 +37,8 @@ card = PokemonCardDef( abilities=[ Ability( title="Mental Shroud", - game_text="If you have Uxie and Azelf in play, each of your Pok\u00e9mon has no Weakness.", - effect=unimplemented, + game_text="If you have Uxie and Azelf in play, each of your Pokémon has no Weakness.", + passive=MentalShroudPassive(), ), Attack( title="Zen Headbutt", @@ -30,4 +46,4 @@ card = PokemonCardDef( damage=30, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH10/Mightyena_96.py b/spirit/game/scripts/cards/SWSH10/Mightyena_96.py index 4653f91..fc3e3e0 100644 --- a/spirit/game/scripts/cards/SWSH10/Mightyena_96.py +++ b/spirit/game/scripts/cards/SWSH10/Mightyena_96.py @@ -1,5 +1,31 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack +from spirit.game.card_effects.pokemon import is_pokemon_vmax +from spirit.game.session.passives import Passive, carrier_pokemon + + +class HustleBarkPassive(Passive): + """If the opponent has any Pokemon VMAX in play, this Pokemon's attacks + cost [C][C][C] less.""" + + def modify_attack_cost(self, cost, pokemon, carrier, board): + if carrier_pokemon(carrier) is not pokemon: + return cost + opponent = next((p for p in board.player_ids + if p != carrier.owning_player_id), None) + if opponent is None or "Colorless" not in cost: + return cost + if not any(is_pokemon_vmax(p.archetype_id) + for p in board.pokemon_in_play(opponent)): + return cost + remaining = cost["Colorless"] - 3 + if remaining > 0: + cost["Colorless"] = remaining + else: + del cost["Colorless"] + return cost + card = PokemonCardDef( guid="719ef6ce-7bb8-527d-b401-7b4db84e19ca", @@ -21,15 +47,15 @@ card = PokemonCardDef( abilities=[ Ability( title="Hustle Bark", - game_text="If your opponent has any Pok\u00e9mon VMAX in play, this Pok\u00e9mon's attacks cost ColorlessColorlessColorless less.", - effect=unimplemented, + game_text="If your opponent has any Pokémon VMAX in play, this Pokémon's attacks cost ColorlessColorlessColorless less.", + passive=HustleBarkPassive(), ), Attack( title="Wild Tackle", - game_text="This Pok\u00e9mon also does 50 damage to itself.", + game_text="This Pokémon also does 50 damage to itself.", cost={PokemonTypes.COLORLESS: 3}, damage=160, - effect=unimplemented, + effect=recoil_attack(50), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH10/Miltank_126.py b/spirit/game/scripts/cards/SWSH10/Miltank_126.py index 9ad9fc8..20c9dd6 100644 --- a/spirit/game/scripts/cards/SWSH10/Miltank_126.py +++ b/spirit/game/scripts/cards/SWSH10/Miltank_126.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, is_pokemon_v from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import count_bench, damage_per +from spirit.game.card_effects.passives_common import prevent_damage_when + + +def _v_attack_on_carrier(calc, carrier) -> bool: + return ( + calc.target is carrier + and calc.attacker is not None + and is_pokemon_v(calc.attacker.archetype_id) + ) + card = PokemonCardDef( guid="ec687afe-5c9c-5062-af4d-dc3f3f51211b", @@ -21,7 +32,7 @@ card = PokemonCardDef( Ability( title="Miracle Body", game_text="Prevent all damage done to this Pok\u00e9mon by attacks from your opponent's Pok\u00e9mon V.", - effect=unimplemented, + passive=prevent_damage_when(_v_attack_on_carrier), ), Attack( title="Rout", @@ -29,7 +40,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=10, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_bench("opponent"), 20, base=10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/Mismagius_59.py b/spirit/game/scripts/cards/SWSH10/Mismagius_59.py index d77bd37..9d2b875 100644 --- a/spirit/game/scripts/cards/SWSH10/Mismagius_59.py +++ b/spirit/game/scripts/cards/SWSH10/Mismagius_59.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack + + +async def ominous_prose(ctx): + """Opponent reveals their hand. If 4+ cards, choose all but 3 to shuffle + into their deck.""" + hand = await ctx.reveal_hand(of_player=ctx.opponent_id, to_player=ctx.player_id) + if len(hand) < 4: + return + count = len(hand) - 3 + picks = await ctx.choose_cards( + hand, count, minimum=count, + prompt="Choose cards to shuffle into your opponent's deck.", + player_id=ctx.player_id, + ) + await ctx.shuffle_into_deck(picks, player_id=ctx.opponent_id) + card = PokemonCardDef( guid="5ed2291a-5cbc-5c99-98fb-b3478a403378", @@ -24,14 +41,14 @@ card = PokemonCardDef( title="Ominous Prose", game_text="Your opponent reveals their hand. If they have 4 or more cards in their hand, choose all but 3, and your opponent shuffles the chosen cards into their deck.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=ominous_prose, ), Attack( title="Psybeam", game_text="Your opponent's Active Pok\u00e9mon is now Confused.", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=50, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/Noctowl_121.py b/spirit/game/scripts/cards/SWSH10/Noctowl_121.py index 8ff4bf8..9d87d7c 100644 --- a/spirit/game/scripts/cards/SWSH10/Noctowl_121.py +++ b/spirit/game/scripts/cards/SWSH10/Noctowl_121.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import self_energy_discard_attack + + +async def silent_wing(ctx): + await ctx.deal_damage() + await ctx.reveal_hand(of_player=ctx.opponent_id) + card = PokemonCardDef( guid="4e924105-d173-5a29-8aab-9ea8e6b0875e", @@ -25,14 +32,14 @@ card = PokemonCardDef( game_text="Your opponent reveals their hand.", cost={PokemonTypes.COLORLESS: 2}, damage=50, - effect=unimplemented, + effect=silent_wing, ), Attack( title="Air Slash", game_text="Discard an Energy from this Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 3}, damage=120, - effect=unimplemented, + effect=self_energy_discard_attack(count=1), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/OranguruV_133.py b/spirit/game/scripts/cards/SWSH10/OranguruV_133.py index e049586..b2da0bc 100644 --- a/spirit/game/scripts/cards/SWSH10/OranguruV_133.py +++ b/spirit/game/scripts/cards/SWSH10/OranguruV_133.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities, TrainerType +from spirit.game.card_effects.attacks_common import damage_per, count_energy +from spirit.game.card_effects.pokemon import in_active_spot +from spirit.game.card_effects.support_common import search_to_hand + + +def _is_tool_card(card) -> bool: + return card.get_attribute(AttrID.TRAINER_TYPE) in ( + TrainerType.POKEMON_TOOL.value, TrainerType.POKEMON_TOOL_F.value) + card = PokemonCardDef( guid="89fdfe37-7ef1-5be1-abfa-05bb78b92153", @@ -21,7 +30,10 @@ card = PokemonCardDef( Ability( title="Back Order", game_text="Once during your turn, if this Pok\u00e9mon is in the Active Spot, you may search your deck for up to 2 Pok\u00e9mon Tool cards, reveal them, and put them into your hand. Then, shuffle your deck.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=in_active_spot, + effect=search_to_hand(predicate=_is_tool_card, count=2, reveal=True, + prompt="Choose up to 2 Pok\u00e9mon Tool cards to put into your hand."), ), Attack( title="Psychic", @@ -29,7 +41,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=30, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("defender"), 50, base=30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/OranguruV_179.py b/spirit/game/scripts/cards/SWSH10/OranguruV_179.py index 9f958a8..04e6749 100644 --- a/spirit/game/scripts/cards/SWSH10/OranguruV_179.py +++ b/spirit/game/scripts/cards/SWSH10/OranguruV_179.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities, TrainerType +from spirit.game.card_effects.attacks_common import damage_per, count_energy +from spirit.game.card_effects.pokemon import in_active_spot +from spirit.game.card_effects.support_common import search_to_hand + + +def _is_tool_card(card) -> bool: + return card.get_attribute(AttrID.TRAINER_TYPE) in ( + TrainerType.POKEMON_TOOL.value, TrainerType.POKEMON_TOOL_F.value) + card = PokemonCardDef( guid="b09d0bf8-9921-5aad-a090-0766d88cd682", @@ -21,7 +30,10 @@ card = PokemonCardDef( Ability( title="Back Order", game_text="Once during your turn, if this Pok\u00e9mon is in the Active Spot, you may search your deck for up to 2 Pok\u00e9mon Tool cards, reveal them, and put them into your hand. Then, shuffle your deck.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=in_active_spot, + effect=search_to_hand(predicate=_is_tool_card, count=2, reveal=True, + prompt="Choose up to 2 Pok\u00e9mon Tool cards to put into your hand."), ), Attack( title="Psychic", @@ -29,7 +41,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=30, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("defender"), 50, base=30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/OriginFormeDialgaVSTAR_114.py b/spirit/game/scripts/cards/SWSH10/OriginFormeDialgaVSTAR_114.py index c16dea0..5ad3241 100644 --- a/spirit/game/scripts/cards/SWSH10/OriginFormeDialgaVSTAR_114.py +++ b/spirit/game/scripts/cards/SWSH10/OriginFormeDialgaVSTAR_114.py @@ -1,5 +1,9 @@ from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.pokemon import star_chronos from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy + +metal_blast = damage_per(count_energy("self", energy_type=PokemonTypes.METAL), 40, base=40) card = PokemonCardDef( guid="e838db0e-5c6f-57ff-afee-d78ce2598a91", @@ -26,14 +30,15 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 1}, damage=40, damage_operator="+", - effect=unimplemented, + effect=metal_blast, ), Attack( title="Star Chronos", game_text="Take another turn after this one. (Skip Pok\u00e9mon Checkup.) (You can't use more than 1 VSTAR Power in a game.)", cost={PokemonTypes.METAL: 4, PokemonTypes.COLORLESS: 1}, damage=220, - effect=unimplemented, + vstar=True, + effect=star_chronos, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/OriginFormeDialgaVSTAR_198.py b/spirit/game/scripts/cards/SWSH10/OriginFormeDialgaVSTAR_198.py index f849cd1..e8326d1 100644 --- a/spirit/game/scripts/cards/SWSH10/OriginFormeDialgaVSTAR_198.py +++ b/spirit/game/scripts/cards/SWSH10/OriginFormeDialgaVSTAR_198.py @@ -1,5 +1,9 @@ from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.pokemon import star_chronos from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy + +metal_blast = damage_per(count_energy("self", energy_type=PokemonTypes.METAL), 40, base=40) card = PokemonCardDef( guid="a5fd9650-cdf2-5428-8cae-29858d2ab174", @@ -26,14 +30,15 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 1}, damage=40, damage_operator="+", - effect=unimplemented, + effect=metal_blast, ), Attack( title="Star Chronos", game_text="Take another turn after this one. (Skip Pok\u00e9mon Checkup.) (You can't use more than 1 VSTAR Power in a game.)", cost={PokemonTypes.METAL: 4, PokemonTypes.COLORLESS: 1}, damage=220, - effect=unimplemented, + vstar=True, + effect=star_chronos, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/OriginFormeDialgaVSTAR_210.py b/spirit/game/scripts/cards/SWSH10/OriginFormeDialgaVSTAR_210.py index efe1613..ae23131 100644 --- a/spirit/game/scripts/cards/SWSH10/OriginFormeDialgaVSTAR_210.py +++ b/spirit/game/scripts/cards/SWSH10/OriginFormeDialgaVSTAR_210.py @@ -1,5 +1,9 @@ from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.pokemon import star_chronos from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy + +metal_blast = damage_per(count_energy("self", energy_type=PokemonTypes.METAL), 40, base=40) card = PokemonCardDef( guid="3e981652-1d67-5bc4-bec5-04ab269aa03e", @@ -26,14 +30,15 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 1}, damage=40, damage_operator="+", - effect=unimplemented, + effect=metal_blast, ), Attack( title="Star Chronos", game_text="Take another turn after this one. (Skip Pok\u00e9mon Checkup.) (You can't use more than 1 VSTAR Power in a game.)", cost={PokemonTypes.METAL: 4, PokemonTypes.COLORLESS: 1}, damage=220, - effect=unimplemented, + vstar=True, + effect=star_chronos, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/OriginFormeDialgaV_113.py b/spirit/game/scripts/cards/SWSH10/OriginFormeDialgaV_113.py index ec38134..d3f122b 100644 --- a/spirit/game/scripts/cards/SWSH10/OriginFormeDialgaV_113.py +++ b/spirit/game/scripts/cards/SWSH10/OriginFormeDialgaV_113.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import attach_from_discard +from spirit.game.card_effects.trainers import is_metal_energy_card card = PokemonCardDef( guid="6d9455dc-f39f-57be-91dd-6080c6fac1c7", @@ -23,7 +25,7 @@ card = PokemonCardDef( title="Metal Coating", game_text="Attach up to 2 Metal Energy cards from your discard pile to this Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=attach_from_discard(predicate=is_metal_energy_card, count=2, minimum=0), ), Attack( title="Temporal Rupture", diff --git a/spirit/game/scripts/cards/SWSH10/OriginFormeDialgaV_177.py b/spirit/game/scripts/cards/SWSH10/OriginFormeDialgaV_177.py index 23d97e5..75fd846 100644 --- a/spirit/game/scripts/cards/SWSH10/OriginFormeDialgaV_177.py +++ b/spirit/game/scripts/cards/SWSH10/OriginFormeDialgaV_177.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import attach_from_discard +from spirit.game.card_effects.trainers import is_metal_energy_card card = PokemonCardDef( guid="b8e06064-7163-5025-a0c8-c71699c59c0d", @@ -23,7 +25,7 @@ card = PokemonCardDef( title="Metal Coating", game_text="Attach up to 2 Metal Energy cards from your discard pile to this Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=attach_from_discard(predicate=is_metal_energy_card, count=2, minimum=0), ), Attack( title="Temporal Rupture", diff --git a/spirit/game/scripts/cards/SWSH10/OriginFormePalkiaVSTAR_192.py b/spirit/game/scripts/cards/SWSH10/OriginFormePalkiaVSTAR_192.py index 5c535a8..6a120e2 100644 --- a/spirit/game/scripts/cards/SWSH10/OriginFormePalkiaVSTAR_192.py +++ b/spirit/game/scripts/cards/SWSH10/OriginFormePalkiaVSTAR_192.py @@ -1,5 +1,38 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_bench +from spirit.game.card_effects.support_common import distribute_energy, requires_discard +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.card_effects.pokemon import energy_provides_type +from spirit.game.session.effects import is_water_pokemon + + +def _is_water_energy(card): + return is_basic_energy_card(card) and energy_provides_type(card, PokemonTypes.WATER.value) + + +async def star_portal(ctx): + pool = [c for c in ctx.discard_pile() if _is_water_energy(c)] + if not pool: + return + picks = await ctx.choose_cards( + pool, 3, minimum=1, + prompt="Choose up to 3 Water Energy cards to attach from your discard pile.", + ) + if not picks: + return + targets = [p for p in ctx.my_pokemon_in_play() if is_water_pokemon(p)] + if not targets: + return + await distribute_energy(ctx, picks, targets) + + +def _star_portal_condition(board, player_id, pokemon=None): + return requires_discard(_is_water_energy)(board, player_id, pokemon) and \ + any(is_water_pokemon(p) for p in board.pokemon_in_play(player_id)) + + +subspace_swell = damage_per(count_bench("both"), 20, base=60) card = PokemonCardDef( guid="10c0a177-9b69-5844-9358-871d2eb9e71f", @@ -22,7 +55,10 @@ card = PokemonCardDef( Ability( title="Star Portal", game_text="During your turn, you may attach up to 3 Water Energy cards from your discard pile to your Water Pok\u00e9mon in any way you like. (You can't use more than 1 VSTAR Power in a game.)", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + vstar=True, + effect=star_portal, + condition=_star_portal_condition, ), Attack( title="Subspace Swell", @@ -30,7 +66,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 2}, damage=60, damage_operator="+", - effect=unimplemented, + effect=subspace_swell, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/OriginFormePalkiaVSTAR_208.py b/spirit/game/scripts/cards/SWSH10/OriginFormePalkiaVSTAR_208.py index de2b747..b674d55 100644 --- a/spirit/game/scripts/cards/SWSH10/OriginFormePalkiaVSTAR_208.py +++ b/spirit/game/scripts/cards/SWSH10/OriginFormePalkiaVSTAR_208.py @@ -1,5 +1,38 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_bench +from spirit.game.card_effects.support_common import distribute_energy, requires_discard +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.card_effects.pokemon import energy_provides_type +from spirit.game.session.effects import is_water_pokemon + + +def _is_water_energy(card): + return is_basic_energy_card(card) and energy_provides_type(card, PokemonTypes.WATER.value) + + +async def star_portal(ctx): + pool = [c for c in ctx.discard_pile() if _is_water_energy(c)] + if not pool: + return + picks = await ctx.choose_cards( + pool, 3, minimum=1, + prompt="Choose up to 3 Water Energy cards to attach from your discard pile.", + ) + if not picks: + return + targets = [p for p in ctx.my_pokemon_in_play() if is_water_pokemon(p)] + if not targets: + return + await distribute_energy(ctx, picks, targets) + + +def _star_portal_condition(board, player_id, pokemon=None): + return requires_discard(_is_water_energy)(board, player_id, pokemon) and \ + any(is_water_pokemon(p) for p in board.pokemon_in_play(player_id)) + + +subspace_swell = damage_per(count_bench("both"), 20, base=60) card = PokemonCardDef( guid="321b46d1-9d32-5ff4-bb5c-efc2cc599292", @@ -22,7 +55,10 @@ card = PokemonCardDef( Ability( title="Star Portal", game_text="During your turn, you may attach up to 3 Water Energy cards from your discard pile to your Water Pok\u00e9mon in any way you like. (You can't use more than 1 VSTAR Power in a game.)", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + vstar=True, + effect=star_portal, + condition=_star_portal_condition, ), Attack( title="Subspace Swell", @@ -30,7 +66,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 2}, damage=60, damage_operator="+", - effect=unimplemented, + effect=subspace_swell, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/OriginFormePalkiaVSTAR_40.py b/spirit/game/scripts/cards/SWSH10/OriginFormePalkiaVSTAR_40.py index 3c8d33b..948c1b6 100644 --- a/spirit/game/scripts/cards/SWSH10/OriginFormePalkiaVSTAR_40.py +++ b/spirit/game/scripts/cards/SWSH10/OriginFormePalkiaVSTAR_40.py @@ -1,5 +1,38 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_bench +from spirit.game.card_effects.support_common import distribute_energy, requires_discard +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.card_effects.pokemon import energy_provides_type +from spirit.game.session.effects import is_water_pokemon + + +def _is_water_energy(card): + return is_basic_energy_card(card) and energy_provides_type(card, PokemonTypes.WATER.value) + + +async def star_portal(ctx): + pool = [c for c in ctx.discard_pile() if _is_water_energy(c)] + if not pool: + return + picks = await ctx.choose_cards( + pool, 3, minimum=1, + prompt="Choose up to 3 Water Energy cards to attach from your discard pile.", + ) + if not picks: + return + targets = [p for p in ctx.my_pokemon_in_play() if is_water_pokemon(p)] + if not targets: + return + await distribute_energy(ctx, picks, targets) + + +def _star_portal_condition(board, player_id, pokemon=None): + return requires_discard(_is_water_energy)(board, player_id, pokemon) and \ + any(is_water_pokemon(p) for p in board.pokemon_in_play(player_id)) + + +subspace_swell = damage_per(count_bench("both"), 20, base=60) card = PokemonCardDef( guid="b399fcd4-7536-5bbb-a660-588d2050ed2b", @@ -22,7 +55,10 @@ card = PokemonCardDef( Ability( title="Star Portal", game_text="During your turn, you may attach up to 3 Water Energy cards from your discard pile to your Water Pok\u00e9mon in any way you like. (You can't use more than 1 VSTAR Power in a game.)", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + vstar=True, + effect=star_portal, + condition=_star_portal_condition, ), Attack( title="Subspace Swell", @@ -30,7 +66,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 2}, damage=60, damage_operator="+", - effect=unimplemented, + effect=subspace_swell, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/OriginFormePalkiaV_167.py b/spirit/game/scripts/cards/SWSH10/OriginFormePalkiaV_167.py index d65a4d7..0ec4c18 100644 --- a/spirit/game/scripts/cards/SWSH10/OriginFormePalkiaV_167.py +++ b/spirit/game/scripts/cards/SWSH10/OriginFormePalkiaV_167.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities, TrainerType +from spirit.game.session.effects import is_trainer_card +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.card_effects.attacks_common import lock_all_attacks + + +def _is_stadium_card(card): + return ( + is_trainer_card(card) + and card.get_attribute(AttrID.TRAINER_TYPE) == TrainerType.STADIUM.value + ) + + +async def hydro_break(ctx): + """During your next turn, this Pokémon can't attack.""" + await ctx.deal_damage() + lock_all_attacks(ctx, ctx.attacker) + card = PokemonCardDef( guid="40f96e02-b834-5e06-9de1-9e625c108cd8", @@ -22,14 +39,15 @@ card = PokemonCardDef( title="Rule the Region", game_text="Search your deck for a Stadium card, reveal it, and put it into your hand. Then, shuffle your deck.", cost={PokemonTypes.WATER: 1}, - effect=unimplemented, + effect=search_to_hand(_is_stadium_card, count=1, minimum=0, reveal=True, + prompt="Choose a Stadium card to put into your hand."), ), Attack( title="Hydro Break", - game_text="During your next turn, this Pok\u00e9mon can't attack.", + game_text="During your next turn, this Pokémon can't attack.", cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, damage=200, - effect=unimplemented, + effect=hydro_break, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH10/OriginFormePalkiaV_39.py b/spirit/game/scripts/cards/SWSH10/OriginFormePalkiaV_39.py index ca1a6b4..5355ed8 100644 --- a/spirit/game/scripts/cards/SWSH10/OriginFormePalkiaV_39.py +++ b/spirit/game/scripts/cards/SWSH10/OriginFormePalkiaV_39.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities, TrainerType +from spirit.game.session.effects import is_trainer_card +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.card_effects.attacks_common import lock_all_attacks + + +def _is_stadium_card(card): + return ( + is_trainer_card(card) + and card.get_attribute(AttrID.TRAINER_TYPE) == TrainerType.STADIUM.value + ) + + +async def hydro_break(ctx): + """During your next turn, this Pokémon can't attack.""" + await ctx.deal_damage() + lock_all_attacks(ctx, ctx.attacker) + card = PokemonCardDef( guid="5122f526-892d-531f-8e3b-073a265b6089", @@ -22,14 +39,15 @@ card = PokemonCardDef( title="Rule the Region", game_text="Search your deck for a Stadium card, reveal it, and put it into your hand. Then, shuffle your deck.", cost={PokemonTypes.WATER: 1}, - effect=unimplemented, + effect=search_to_hand(_is_stadium_card, count=1, minimum=0, reveal=True, + prompt="Choose a Stadium card to put into your hand."), ), Attack( title="Hydro Break", - game_text="During your next turn, this Pok\u00e9mon can't attack.", + game_text="During your next turn, this Pokémon can't attack.", cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, damage=200, - effect=unimplemented, + effect=hydro_break, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH10/Pawniard_115.py b/spirit/game/scripts/cards/SWSH10/Pawniard_115.py index 13c8ce2..1e428b7 100644 --- a/spirit/game/scripts/cards/SWSH10/Pawniard_115.py +++ b/spirit/game/scripts/cards/SWSH10/Pawniard_115.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import recoil_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="This Pok\u00e9mon also does 10 damage to itself.", cost={PokemonTypes.METAL: 1}, damage=30, - effect=unimplemented, + effect=recoil_attack(10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/Petilil_15.py b/spirit/game/scripts/cards/SWSH10/Petilil_15.py index a985950..10b7700 100644 --- a/spirit/game/scripts/cards/SWSH10/Petilil_15.py +++ b/spirit/game/scripts/cards/SWSH10/Petilil_15.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.support_common import switch_self_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Switch this Pok\u00e9mon with 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.GRASS: 1}, damage=10, - effect=unimplemented, + effect=switch_self_attack(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/Piloswine_32.py b/spirit/game/scripts/cards/SWSH10/Piloswine_32.py index 6c59767..c44f62a 100644 --- a/spirit/game/scripts/cards/SWSH10/Piloswine_32.py +++ b/spirit/game/scripts/cards/SWSH10/Piloswine_32.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import spread_damage card = PokemonCardDef( guid="e4d1f822-f537-5667-950d-84811408c101", @@ -29,7 +30,7 @@ card = PokemonCardDef( game_text="This attack also does 10 damage to each of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=50, - effect=unimplemented, + effect=spread_damage(10, side="opponent", also_base=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/Poochyena_95.py b/spirit/game/scripts/cards/SWSH10/Poochyena_95.py index 3f8dcce..c561f84 100644 --- a/spirit/game/scripts/cards/SWSH10/Poochyena_95.py +++ b/spirit/game/scripts/cards/SWSH10/Poochyena_95.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack card = PokemonCardDef( guid="721513bf-517e-5c47-9153-e8d81e6b91fa", @@ -28,7 +29,7 @@ card = PokemonCardDef( game_text="This Pok\u00e9mon also does 10 damage to itself.", cost={PokemonTypes.COLORLESS: 2}, damage=30, - effect=unimplemented, + effect=recoil_attack(10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/Psyduck_28.py b/spirit/game/scripts/cards/SWSH10/Psyduck_28.py index bd242c2..cf03233 100644 --- a/spirit/game/scripts/cards/SWSH10/Psyduck_28.py +++ b/spirit/game/scripts/cards/SWSH10/Psyduck_28.py @@ -1,6 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def spacing_out(ctx): + """Flip a coin. If heads, heal 10 damage from this Pokémon.""" + heads = (await ctx.flip_coins(1, ctx.ability.title))[0] + if heads: + await ctx.heal(10, ctx.attacker) + + card = PokemonCardDef( guid="a0848ba3-b75b-583e-ab69-f4fe9946e74e", key="SWSH10", @@ -22,7 +30,7 @@ card = PokemonCardDef( title="Spacing Out", game_text="Flip a coin. If heads, heal 10 damage from this Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=spacing_out, ), Attack( title="Ram", diff --git a/spirit/game/scripts/cards/SWSH10/Purugly_128.py b/spirit/game/scripts/cards/SWSH10/Purugly_128.py index dce7a02..3f7e717 100644 --- a/spirit/game/scripts/cards/SWSH10/Purugly_128.py +++ b/spirit/game/scripts/cards/SWSH10/Purugly_128.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="278a5ce7-da81-50e0-83f5-7b0b8c6e51be", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), Attack( title="Slashing Claw", diff --git a/spirit/game/scripts/cards/SWSH10/RadiantHawlucha_81.py b/spirit/game/scripts/cards/SWSH10/RadiantHawlucha_81.py index ee83f34..ef075bb 100644 --- a/spirit/game/scripts/cards/SWSH10/RadiantHawlucha_81.py +++ b/spirit/game/scripts/cards/SWSH10/RadiantHawlucha_81.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.pokemon import is_pokemon_vmax +from spirit.game.session.passives import Passive + + +class BigMatchPassive(Passive): + def modify_damage_dealt(self, calc, carrier): + if not (calc.is_attack and calc.is_opposing and calc.to_active): + return + if calc.attacker is None or calc.attacker.owning_player_id != carrier.owning_player_id: + return + if not is_pokemon_vmax(calc.target.archetype_id): + return + parent = carrier.parent + if not parent or parent.get_attribute(AttrID.NAME) != "bench": + return + calc.amount += 30 + card = PokemonCardDef( guid="9fb82859-0659-57eb-b00c-89c6d8215c99", @@ -21,7 +38,7 @@ card = PokemonCardDef( Ability( title="Big Match", game_text="As long as this Pok\u00e9mon is on your Bench, your Pok\u00e9mon's attacks do 30 more damage to your opponent's Active Pok\u00e9mon VMAX (before applying Weakness and Resistance).", - effect=unimplemented, + passive=BigMatchPassive(), ), Attack( title="Spiral Kick", diff --git a/spirit/game/scripts/cards/SWSH10/RadiantHeatran_27.py b/spirit/game/scripts/cards/SWSH10/RadiantHeatran_27.py index 095ff34..9a5ee1f 100644 --- a/spirit/game/scripts/cards/SWSH10/RadiantHeatran_27.py +++ b/spirit/game/scripts/cards/SWSH10/RadiantHeatran_27.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, damage_counters_on card = PokemonCardDef( guid="32d3df0a-a32e-59fd-9b9c-5df50971c393", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 2}, damage=70, damage_operator="x", - effect=unimplemented, + effect=damage_per(damage_counters_on("self"), 70), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/Ralts_60.py b/spirit/game/scripts/cards/SWSH10/Ralts_60.py index f6c3ecb..27f646a 100644 --- a/spirit/game/scripts/cards/SWSH10/Ralts_60.py +++ b/spirit/game/scripts/cards/SWSH10/Ralts_60.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.support_common import switch_self_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Switch this Pok\u00e9mon with 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.PSYCHIC: 1}, damage=10, - effect=unimplemented, + effect=switch_self_attack(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/Rampardos_77.py b/spirit/game/scripts/cards/SWSH10/Rampardos_77.py index 151e189..107e9ee 100644 --- a/spirit/game/scripts/cards/SWSH10/Rampardos_77.py +++ b/spirit/game/scripts/cards/SWSH10/Rampardos_77.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if card = PokemonCardDef( guid="56d87599-be1a-598a-943b-2c454f96db44", @@ -29,7 +30,7 @@ card = PokemonCardDef( game_text="If your opponent has 3 or fewer cards in their hand, this attack does nothing.", cost={PokemonTypes.FIGHTING: 2}, damage=240, - effect=unimplemented, + effect=bonus_if(lambda ctx: ctx.hand_size(ctx.opponent_id) > 3, 0, else_nothing=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/Rapidash_22.py b/spirit/game/scripts/cards/SWSH10/Rapidash_22.py index a51a43f..f9db051 100644 --- a/spirit/game/scripts/cards/SWSH10/Rapidash_22.py +++ b/spirit/game/scripts/cards/SWSH10/Rapidash_22.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="bdf97bef-4102-5295-9d9c-28f12d29e67a", @@ -29,7 +30,7 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Burned. During your opponent's next turn, that Pok\u00e9mon can't retreat.", cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 1}, damage=50, - effect=unimplemented, + effect=condition_attack(SpecialConditions.BURNED, no_retreat=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/Rowlet_19.py b/spirit/game/scripts/cards/SWSH10/Rowlet_19.py index 8333e66..dc7da1a 100644 --- a/spirit/game/scripts/cards/SWSH10/Rowlet_19.py +++ b/spirit/game/scripts/cards/SWSH10/Rowlet_19.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus card = PokemonCardDef( guid="fa3d2751-6fd1-59db-bc0b-b3bc32616453", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 1}, damage=10, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/Rufflet_131.py b/spirit/game/scripts/cards/SWSH10/Rufflet_131.py index d8c8f76..56e6fdf 100644 --- a/spirit/game/scripts/cards/SWSH10/Rufflet_131.py +++ b/spirit/game/scripts/cards/SWSH10/Rufflet_131.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_or_nothing card = PokemonCardDef( guid="cee90342-b8a4-5b12-8b39-5359a2d947c0", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Flip a coin. If tails, this attack does nothing.", cost={PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=flip_or_nothing(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/Scyther_5.py b/spirit/game/scripts/cards/SWSH10/Scyther_5.py index b3074af..00424ef 100644 --- a/spirit/game/scripts/cards/SWSH10/Scyther_5.py +++ b/spirit/game/scripts/cards/SWSH10/Scyther_5.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus card = PokemonCardDef( guid="083fe3b0-415b-524c-98d1-1d795941360a", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=30, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/Shaymin_14.py b/spirit/game/scripts/cards/SWSH10/Shaymin_14.py index 994f79f..d2caf4c 100644 --- a/spirit/game/scripts/cards/SWSH10/Shaymin_14.py +++ b/spirit/game/scripts/cards/SWSH10/Shaymin_14.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_hand + + +def _only_turn_2(board, player_id, pokemon): + return board.turn_state.turn_number == 2 + card = PokemonCardDef( guid="5db6b80a-4feb-5c5a-8b22-2164a12a96a7", @@ -22,7 +28,9 @@ card = PokemonCardDef( title="Encouraging Gift", game_text="You can use this attack only if you go second, and only during your first turn. Search your deck for up to 3 cards and put them into your hand. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + condition=_only_turn_2, + effect=search_to_hand(count=3, minimum=0, reveal=False, + prompt="Choose up to 3 cards."), ), Attack( title="Flop", diff --git a/spirit/game/scripts/cards/SWSH10/Shieldon_109.py b/spirit/game/scripts/cards/SWSH10/Shieldon_109.py index 39aeaa5..273610e 100644 --- a/spirit/game/scripts/cards/SWSH10/Shieldon_109.py +++ b/spirit/game/scripts/cards/SWSH10/Shieldon_109.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import flip_protection card = PokemonCardDef( guid="040da654-743a-5a6b-8b6a-02bbc45230d7", @@ -25,7 +26,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, during your opponent's next turn, prevent all damage from and effects of attacks done to this Pok\u00e9mon.", cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=flip_protection(prevent=True, effects_too=True, title="Hard Headbutt"), ), Attack( title="Confront", diff --git a/spirit/game/scripts/cards/SWSH10/SpicySeasonedCurry_151.py b/spirit/game/scripts/cards/SWSH10/SpicySeasonedCurry_151.py index fe5680d..38017db 100644 --- a/spirit/game/scripts/cards/SWSH10/SpicySeasonedCurry_151.py +++ b/spirit/game/scripts/cards/SWSH10/SpicySeasonedCurry_151.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import ItemCardDef +from spirit.game.attributes import Rarities, SpecialConditions + + +async def _spicy_seasoned_curry(ctx): + active = ctx.my_active() + if active is None: + return + await ctx.apply_special_condition(active, SpecialConditions.BURNED) + await ctx.heal(40, target=active) + card = ItemCardDef( guid="1fc1ca9a-1280-5569-8ce4-9b06412630d7", @@ -11,5 +20,5 @@ card = ItemCardDef( collector_number=151, set_code="SWSH10", rarity=Rarities.Uncommon, - effect=unimplemented + effect=_spicy_seasoned_curry, ) diff --git a/spirit/game/scripts/cards/SWSH10/Stantler_125.py b/spirit/game/scripts/cards/SWSH10/Stantler_125.py index c494ee7..9eabb7d 100644 --- a/spirit/game/scripts/cards/SWSH10/Stantler_125.py +++ b/spirit/game/scripts/cards/SWSH10/Stantler_125.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import protect_next_turn card = PokemonCardDef( guid="b462b471-537c-5238-a240-be46b893bb03", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, this Pok\u00e9mon takes 30 less damage from attacks (after applying Weakness and Resistance).", cost={PokemonTypes.COLORLESS: 2}, damage=30, - effect=unimplemented, + effect=protect_next_turn(reduce=30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/StarmieV_166.py b/spirit/game/scripts/cards/SWSH10/StarmieV_166.py index 012e5e2..635bec2 100644 --- a/spirit/game/scripts/cards/SWSH10/StarmieV_166.py +++ b/spirit/game/scripts/cards/SWSH10/StarmieV_166.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy + + +async def swift(ctx): + """50 damage; isn't affected by Weakness/Resistance or any effects on the opponent's Active.""" + await ctx.deal_damage(apply_modifiers=False, ignore_target_effects=True) + card = PokemonCardDef( guid="0cd42222-0589-5774-96c9-8e9d4221593b", @@ -20,18 +27,18 @@ card = PokemonCardDef( abilities=[ Attack( title="Swift", - game_text="This attack's damage isn't affected by Weakness or Resistance, or by any effects on your opponent's Active Pok\u00e9mon.", + game_text="This attack's damage isn't affected by Weakness or Resistance, or by any effects on your opponent's Active Pokémon.", cost={PokemonTypes.COLORLESS: 2}, damage=50, - effect=unimplemented, + effect=swift, ), Attack( title="Energy Spiral", - game_text="This attack does 50 damage for each Energy attached to all of your opponent's Pok\u00e9mon.", + game_text="This attack does 50 damage for each Energy attached to all of your opponent's Pokémon.", cost={PokemonTypes.WATER: 2}, damage=50, damage_operator="x", - effect=unimplemented, + effect=damage_per(count_energy("opponent"), 50), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH10/StarmieV_30.py b/spirit/game/scripts/cards/SWSH10/StarmieV_30.py index daabea2..6952991 100644 --- a/spirit/game/scripts/cards/SWSH10/StarmieV_30.py +++ b/spirit/game/scripts/cards/SWSH10/StarmieV_30.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy + + +async def swift(ctx): + """50 damage; isn't affected by Weakness/Resistance or any effects on the opponent's Active.""" + await ctx.deal_damage(apply_modifiers=False, ignore_target_effects=True) + card = PokemonCardDef( guid="b5ce0f8a-bda4-5308-a892-280f6b06e567", @@ -20,18 +27,18 @@ card = PokemonCardDef( abilities=[ Attack( title="Swift", - game_text="This attack's damage isn't affected by Weakness or Resistance, or by any effects on your opponent's Active Pok\u00e9mon.", + game_text="This attack's damage isn't affected by Weakness or Resistance, or by any effects on your opponent's Active Pokémon.", cost={PokemonTypes.COLORLESS: 2}, damage=50, - effect=unimplemented, + effect=swift, ), Attack( title="Energy Spiral", - game_text="This attack does 50 damage for each Energy attached to all of your opponent's Pok\u00e9mon.", + game_text="This attack does 50 damage for each Energy attached to all of your opponent's Pokémon.", cost={PokemonTypes.WATER: 2}, damage=50, damage_operator="x", - effect=unimplemented, + effect=damage_per(count_energy("opponent"), 50), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH10/Sudowoodo_74.py b/spirit/game/scripts/cards/SWSH10/Sudowoodo_74.py index 57aab82..0deb85b 100644 --- a/spirit/game/scripts/cards/SWSH10/Sudowoodo_74.py +++ b/spirit/game/scripts/cards/SWSH10/Sudowoodo_74.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.attacks_common import recoil_attack +from spirit.game.card_effects.passives_common import retreat_free_when + + +def _vamoose(pokemon, carrier): + if pokemon is not carrier: + return False + max_hp = pokemon.attribute_originals.get( + AttrID.HP.value, pokemon.get_attribute(AttrID.HP, 0) + ) + return pokemon.get_attribute(AttrID.HP, 0) < max_hp + card = PokemonCardDef( guid="6b14cf73-53b8-5f9c-acfc-234ea47d5a32", @@ -21,14 +33,14 @@ card = PokemonCardDef( Ability( title="Vamoose", game_text="If this Pok\u00e9mon has any damage counters on it, it has no Retreat Cost.", - effect=unimplemented, + passive=retreat_free_when(_vamoose), ), Attack( title="Double-Edge", game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.FIGHTING: 2}, damage=90, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/SupereffectiveGlasses_152.py b/spirit/game/scripts/cards/SWSH10/SupereffectiveGlasses_152.py index ca3a0e3..cb0ba4f 100644 --- a/spirit/game/scripts/cards/SWSH10/SupereffectiveGlasses_152.py +++ b/spirit/game/scripts/cards/SWSH10/SupereffectiveGlasses_152.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented +from spirit.game.data_utils import PokemonToolCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.passives_common import weakness_multiplier_passive card = PokemonToolCardDef( guid="f6a03e23-0faa-5f6d-ba7f-8a2c8af8f825", @@ -11,5 +12,5 @@ card = PokemonToolCardDef( collector_number=152, set_code="SWSH10", rarity=Rarities.Uncommon, - effect=unimplemented + passive=weakness_multiplier_passive(3), ) diff --git a/spirit/game/scripts/cards/SWSH10/SweetHoney_153.py b/spirit/game/scripts/cards/SWSH10/SweetHoney_153.py index e8b0b80..09058ca 100644 --- a/spirit/game/scripts/cards/SWSH10/SweetHoney_153.py +++ b/spirit/game/scripts/cards/SWSH10/SweetHoney_153.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.support_common import requires_damaged_pokemon + + +async def sweet_honey(ctx): + """Choose 1 of your Pokemon, flip a coin until tails, heal 40 per heads.""" + target = await ctx.choose_pokemon( + ctx.my_pokemon_in_play(), "Choose 1 of your Pokémon." + ) + if target is None: + return + heads = await ctx.flip_until_tails("Sweet Honey") + if heads: + await ctx.heal(40 * heads, target=target) + card = ItemCardDef( guid="d4fd0d96-df66-5e9d-ad5d-3cae2843aeaa", @@ -11,5 +25,6 @@ card = ItemCardDef( collector_number=153, set_code="SWSH10", rarity=Rarities.Uncommon, - effect=unimplemented + effect=sweet_honey, + condition=requires_damaged_pokemon(), ) diff --git a/spirit/game/scripts/cards/SWSH10/Swinub_31.py b/spirit/game/scripts/cards/SWSH10/Swinub_31.py index 03548ce..c790190 100644 --- a/spirit/game/scripts/cards/SWSH10/Swinub_31.py +++ b/spirit/game/scripts/cards/SWSH10/Swinub_31.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="c16656c9-2bdf-5ab1-9bfa-ec6d09faf21a", @@ -28,7 +29,7 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Asleep.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=40, - effect=unimplemented, + effect=condition_attack(SpecialConditions.ASLEEP), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/Teddiursa_122.py b/spirit/game/scripts/cards/SWSH10/Teddiursa_122.py index c98b6f5..e2aa52c 100644 --- a/spirit/game/scripts/cards/SWSH10/Teddiursa_122.py +++ b/spirit/game/scripts/cards/SWSH10/Teddiursa_122.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_item_card + + +async def gather_food(ctx): + """Flip a coin. If heads, put an Item card from your discard pile into your hand.""" + heads = await ctx.flip_coins(1, "Gather Food") + if not heads[0]: + return + items = [c for c in ctx.discard_pile() if is_item_card(c)] + if not items: + return + picks = await ctx.choose_cards( + items, 1, minimum=0, + prompt="Choose an Item card to put into your hand", + ) + if picks: + await ctx.put_in_hand(picks, reveal=False) card = PokemonCardDef( guid="3a219f96-c79a-5587-a827-38583c333b0f", @@ -22,7 +39,7 @@ card = PokemonCardDef( title="Gather Food", game_text="Flip a coin. If heads, put an Item card from your discard pile into your hand.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=gather_food, ), Attack( title="Dig Claws", diff --git a/spirit/game/scripts/cards/SWSH10/TempleofSinnoh_155.py b/spirit/game/scripts/cards/SWSH10/TempleofSinnoh_155.py index 6645f95..085a2bb 100644 --- a/spirit/game/scripts/cards/SWSH10/TempleofSinnoh_155.py +++ b/spirit/game/scripts/cards/SWSH10/TempleofSinnoh_155.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import StadiumCardDef, unimplemented +from spirit.game.data_utils import StadiumCardDef from spirit.game.attributes import Rarities +from spirit.game.session.passives import Passive + + +class TempleOfSinnohPassive(Passive): + """All Special Energy (both players') provide only Colorless Energy.""" + + def suppresses_special_energy(self, energy, carrier) -> bool: + return True + card = StadiumCardDef( guid="a2fd5726-8e67-5bd1-af21-1a6e3705abc4", @@ -11,5 +20,5 @@ card = StadiumCardDef( collector_number=155, set_code="SWSH10", rarity=Rarities.Uncommon, - effect=unimplemented + passive=TempleOfSinnohPassive(), ) diff --git a/spirit/game/scripts/cards/SWSH10/TempleofSinnoh_214.py b/spirit/game/scripts/cards/SWSH10/TempleofSinnoh_214.py index cd8e92e..076e23f 100644 --- a/spirit/game/scripts/cards/SWSH10/TempleofSinnoh_214.py +++ b/spirit/game/scripts/cards/SWSH10/TempleofSinnoh_214.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import StadiumCardDef, unimplemented +from spirit.game.data_utils import StadiumCardDef from spirit.game.attributes import Rarities +from spirit.game.session.passives import Passive + + +class TempleOfSinnohPassive(Passive): + """All Special Energy (both players') provide only Colorless Energy.""" + + def suppresses_special_energy(self, energy, carrier) -> bool: + return True + card = StadiumCardDef( guid="c7bb055b-6045-5562-b0f6-de5998fade2e", @@ -11,5 +20,5 @@ card = StadiumCardDef( collector_number=214, set_code="SWSH10", rarity=Rarities.RareSecret, - effect=unimplemented + passive=TempleOfSinnohPassive(), ) diff --git a/spirit/game/scripts/cards/SWSH10/Thievul_104.py b/spirit/game/scripts/cards/SWSH10/Thievul_104.py index 4a0a765..1db6e15 100644 --- a/spirit/game/scripts/cards/SWSH10/Thievul_104.py +++ b/spirit/game/scripts/cards/SWSH10/Thievul_104.py @@ -1,5 +1,25 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, is_pokemon_v +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import trainer_effect_shield_passive +from spirit.game.session.passives import carrier_pokemon + + +def _benched_pokemon_v(entity, carrier): + holder = carrier_pokemon(entity) if entity is not None else None + if holder is None: + return False + parent = getattr(holder, "parent", None) + return ( + bool(parent) and parent.get_attribute(AttrID.NAME) == "bench" + and is_pokemon_v(holder.archetype_id) + ) + + +def _opponent_two_or_fewer_prizes(board, carrier): + opponent = next( + (p for p in board.player_ids if p != carrier.owning_player_id), None) + area = board.find_player_area(opponent, "prizePile") if opponent else None + return area is not None and len(area.children) <= 2 card = PokemonCardDef( guid="9a1b14b5-c3d8-5bbd-87e6-3d00407f6dbf", @@ -22,7 +42,9 @@ card = PokemonCardDef( Ability( title="Baffling", game_text="If your opponent has 2 or fewer Prize cards remaining, whenever your opponent plays a Supporter card from their hand, prevent all effects of that card done to your Benched Pok\u00e9mon V.", - effect=unimplemented, + passive=trainer_effect_shield_passive( + protects=_benched_pokemon_v, + condition=_opponent_two_or_fewer_prizes), ), Attack( title="Sharp Fang", diff --git a/spirit/game/scripts/cards/SWSH10/Togekiss_57.py b/spirit/game/scripts/cards/SWSH10/Togekiss_57.py index a34f4bf..17ff4a7 100644 --- a/spirit/game/scripts/cards/SWSH10/Togekiss_57.py +++ b/spirit/game/scripts/cards/SWSH10/Togekiss_57.py @@ -1,6 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def shine_of_happiness(ctx): + """On evolve: you may heal 90 damage from your Active Pokémon.""" + active = ctx.my_active() + if active is None: + return + if await ctx.ask_yes_no("Heal 90 damage from your Active Pokémon?"): + await ctx.heal(90, active) + card = PokemonCardDef( guid="9ae40432-4e80-511c-a9c7-10233a55008c", key="SWSH10", @@ -22,7 +31,8 @@ card = PokemonCardDef( Ability( title="Shine of Happiness", game_text="When you play this Pok\u00e9mon from your hand to evolve 1 of your Pok\u00e9mon during your turn, you may heal 90 damage from your Active Pok\u00e9mon.", - effect=unimplemented, + trigger=Triggers.ON_EVOLVE, + effect=shine_of_happiness, ), Attack( title="Magical Shot", diff --git a/spirit/game/scripts/cards/SWSH10/Togepi_55.py b/spirit/game/scripts/cards/SWSH10/Togepi_55.py index f43d654..a4b68b5 100644 --- a/spirit/game/scripts/cards/SWSH10/Togepi_55.py +++ b/spirit/game/scripts/cards/SWSH10/Togepi_55.py @@ -1,6 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def touch_of_happiness(ctx): + """On play from hand to Bench: you may heal 10 damage from your Active Pokémon.""" + active = ctx.my_active() + if active is None: + return + if await ctx.ask_yes_no("Heal 10 damage from your Active Pokémon?"): + await ctx.heal(10, active) + card = PokemonCardDef( guid="7b844792-7fb1-5bea-838c-34688347d4a1", key="SWSH10", @@ -21,7 +30,8 @@ card = PokemonCardDef( Ability( title="Touch of Happiness", game_text="When you play this Pok\u00e9mon from your hand onto your Bench during your turn, you may heal 10 damage from your Active Pok\u00e9mon.", - effect=unimplemented, + trigger=Triggers.ON_PLAY, + effect=touch_of_happiness, ), Attack( title="Rollout", diff --git a/spirit/game/scripts/cards/SWSH10/Togetic_56.py b/spirit/game/scripts/cards/SWSH10/Togetic_56.py index 32e8630..756172a 100644 --- a/spirit/game/scripts/cards/SWSH10/Togetic_56.py +++ b/spirit/game/scripts/cards/SWSH10/Togetic_56.py @@ -1,6 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def voice_of_happiness(ctx): + """On evolve: you may heal 30 damage from your Active Pokémon.""" + active = ctx.my_active() + if active is None: + return + if await ctx.ask_yes_no("Heal 30 damage from your Active Pokémon?"): + await ctx.heal(30, active) + card = PokemonCardDef( guid="3ac6d2dc-03ad-5b33-b2e7-0b25fee6ebd9", key="SWSH10", @@ -22,7 +31,8 @@ card = PokemonCardDef( Ability( title="Voice of Happiness", game_text="When you play this Pok\u00e9mon from your hand to evolve 1 of your Pok\u00e9mon during your turn, you may heal 30 damage from your Active Pok\u00e9mon.", - effect=unimplemented, + trigger=Triggers.ON_EVOLVE, + effect=voice_of_happiness, ), Attack( title="Fairy Wind", diff --git a/spirit/game/scripts/cards/SWSH10/TrekkingShoes_215.py b/spirit/game/scripts/cards/SWSH10/TrekkingShoes_215.py index f4e6c92..5d8ee95 100644 --- a/spirit/game/scripts/cards/SWSH10/TrekkingShoes_215.py +++ b/spirit/game/scripts/cards/SWSH10/TrekkingShoes_215.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import trekking_shoes card = ItemCardDef( guid="26078098-6684-50ff-89c5-815ba6957938", @@ -11,5 +12,5 @@ card = ItemCardDef( collector_number=215, set_code="SWSH10", rarity=Rarities.RareSecret, - effect=unimplemented + effect=trekking_shoes ) diff --git a/spirit/game/scripts/cards/SWSH10/UnidentifiedFossil_157.py b/spirit/game/scripts/cards/SWSH10/UnidentifiedFossil_157.py index af24b69..de1eb68 100644 --- a/spirit/game/scripts/cards/SWSH10/UnidentifiedFossil_157.py +++ b/spirit/game/scripts/cards/SWSH10/UnidentifiedFossil_157.py @@ -1,7 +1,8 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import FossilItemCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import FossilBodyPassive, fossil_discard_ability -card = ItemCardDef( +card = FossilItemCardDef( guid="b789c95c-2960-5f2c-ad54-88f7042fc565", key="SWSH10", name="com.direwolfdigital.cake.data.archetypes.trainer.UnidentifiedFossil.Name", @@ -11,5 +12,7 @@ card = ItemCardDef( collector_number=157, set_code="SWSH10", rarity=Rarities.Uncommon, - effect=unimplemented + hp=60, + passive=FossilBodyPassive(), + abilities=[fossil_discard_ability()], ) diff --git a/spirit/game/scripts/cards/SWSH10/Ursaluna_124.py b/spirit/game/scripts/cards/SWSH10/Ursaluna_124.py index 32eac23..91e7c7b 100644 --- a/spirit/game/scripts/cards/SWSH10/Ursaluna_124.py +++ b/spirit/game/scripts/cards/SWSH10/Ursaluna_124.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import recover_from_discard +from spirit.game.card_effects.attacks_common import self_energy_discard_attack card = PokemonCardDef( guid="e18a6ce6-7f10-5359-b989-8e038949f486", @@ -23,14 +25,14 @@ card = PokemonCardDef( title="Peat Hunt", game_text="Put up to 2 cards from your discard pile into your hand.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=recover_from_discard(count=2, to="hand"), ), Attack( title="Bulky Bump", game_text="Discard 2 Energy from this Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 3}, damage=200, - effect=unimplemented, + effect=self_energy_discard_attack(count=2), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/Ursaring_123.py b/spirit/game/scripts/cards/SWSH10/Ursaring_123.py index 018d0a8..cd07ea5 100644 --- a/spirit/game/scripts/cards/SWSH10/Ursaring_123.py +++ b/spirit/game/scripts/cards/SWSH10/Ursaring_123.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="d66dfae3-597f-588f-8331-06c26a43f393", @@ -25,7 +26,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 1}, damage=40, damage_operator="x", - effect=unimplemented, + effect=flip_damage(until_tails=True, per_heads=40), ), Attack( title="Strength", diff --git a/spirit/game/scripts/cards/SWSH10/Uxie_65.py b/spirit/game/scripts/cards/SWSH10/Uxie_65.py index c2ad9b7..0be5d62 100644 --- a/spirit/game/scripts/cards/SWSH10/Uxie_65.py +++ b/spirit/game/scripts/cards/SWSH10/Uxie_65.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_hand card = PokemonCardDef( guid="23f46731-1a90-5169-85cd-698638080abc", @@ -23,7 +24,7 @@ card = PokemonCardDef( title="Wise Guidance", game_text="Search your deck for a card and put it into your hand. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_to_hand(count=1, minimum=0, reveal=False), ), Attack( title="Psyshot", diff --git a/spirit/game/scripts/cards/SWSH10/Vespiquen_12.py b/spirit/game/scripts/cards/SWSH10/Vespiquen_12.py index 1f8857c..9b50e79 100644 --- a/spirit/game/scripts/cards/SWSH10/Vespiquen_12.py +++ b/spirit/game/scripts/cards/SWSH10/Vespiquen_12.py @@ -1,6 +1,29 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, def_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +def _is_sweet_honey(card): + d = def_for(card.archetype_id) + return bool(d) and d.display_name == "Sweet Honey" + + +async def honey_rush(ctx): + """Reveal any number of Sweet Honey cards from your hand. This attack + does 60 damage for each card revealed this way.""" + honey = [c for c in ctx.hand() if _is_sweet_honey(c)] + picks = [] + if honey: + picks = await ctx.choose_cards( + honey, len(honey), minimum=0, + prompt="Reveal any number of Sweet Honey cards.", + ) + if picks: + await ctx.reveal_cards(picks) + amount = 60 * len(picks) + if amount > 0: + await ctx.deal_damage(amount) + + card = PokemonCardDef( guid="3f8da2c4-4d4d-5be0-b622-1ada25962532", key="SWSH10", @@ -25,7 +48,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1}, damage=60, damage_operator="x", - effect=unimplemented, + effect=honey_rush, ), Attack( title="Pierce", diff --git a/spirit/game/scripts/cards/SWSH10/VirizionV_164.py b/spirit/game/scripts/cards/SWSH10/VirizionV_164.py index 2afea62..f0d61d8 100644 --- a/spirit/game/scripts/cards/SWSH10/VirizionV_164.py +++ b/spirit/game/scripts/cards/SWSH10/VirizionV_164.py @@ -1,5 +1,34 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import energy_provides_type +from spirit.game.card_effects.passives_common import condition_immunity_passive +from spirit.game.card_effects.attacks_common import lock_all_attacks +from spirit.game.models.board import BoardState + + +def _grass_energy_immune(target, carrier): + if target.owning_player_id != carrier.owning_player_id: + return False + return any(energy_provides_type(e, PokemonTypes.GRASS.value) + for e in BoardState.attached_energies(target)) + + +async def verdant_wind(ctx): + """Cures Special Conditions the moment one of your Pokemon picks up + Grass Energy; the passive keeps it immune from then on.""" + receiver = ctx.energy_receiver + if receiver is None or receiver.owning_player_id != ctx.player_id: + return + if not energy_provides_type(ctx.attached_energy, PokemonTypes.GRASS.value): + return + await ctx.cure_all_conditions(receiver) + + +async def emerald_blade(ctx): + """200 damage. During your next turn, this Pokemon can't attack.""" + await ctx.deal_damage() + lock_all_attacks(ctx, ctx.attacker) + card = PokemonCardDef( guid="e06f7936-d07c-565d-ae63-5967b6ba2380", @@ -21,14 +50,16 @@ card = PokemonCardDef( Ability( title="Verdant Wind", game_text="Each of your Pok\u00e9mon that has any Grass Energy attached to it can't be affected by any Special Conditions. (Remove any Special Conditions affecting those Pok\u00e9mon.)", - effect=unimplemented, + trigger=Triggers.ON_ENERGY_ATTACHED, + effect=verdant_wind, + passive=condition_immunity_passive(protects=_grass_energy_immune), ), Attack( title="Emerald Blade", game_text="During your next turn, this Pok\u00e9mon can't attack.", cost={PokemonTypes.GRASS: 2, PokemonTypes.COLORLESS: 1}, damage=200, - effect=unimplemented, + effect=emerald_blade, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/WaitandSeeTurbo_158.py b/spirit/game/scripts/cards/SWSH10/WaitandSeeTurbo_158.py index 831c5d8..091a34b 100644 --- a/spirit/game/scripts/cards/SWSH10/WaitandSeeTurbo_158.py +++ b/spirit/game/scripts/cards/SWSH10/WaitandSeeTurbo_158.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.support_common import search_attach_energy +from spirit.game.card_effects.trainers import is_basic_energy_card + + +def _wait_and_see_turbo_condition(board, player_id): + """Only usable if you go second, during your first turn (turn 2).""" + turn_state = getattr(board, "turn_state", None) + return turn_state is not None and turn_state.turn_number == 2 + + +async def wait_and_see_turbo(ctx): + """Search your deck for a basic Energy card and attach it to 1 of your + Pokemon. Shuffle your deck. Your turn ends.""" + await search_attach_energy(predicate=is_basic_energy_card, count=1)(ctx) + ctx.ends_turn = True + card = ItemCardDef( guid="75fe9eb1-0726-5b62-8930-852a8812098a", @@ -11,5 +27,6 @@ card = ItemCardDef( collector_number=158, set_code="SWSH10", rarity=Rarities.Uncommon, - effect=unimplemented + condition=_wait_and_see_turbo_condition, + effect=wait_and_see_turbo, ) diff --git a/spirit/game/scripts/cards/SWSH10/Whiscash_36.py b/spirit/game/scripts/cards/SWSH10/Whiscash_36.py index 277b5a0..b7f358d 100644 --- a/spirit/game/scripts/cards/SWSH10/Whiscash_36.py +++ b/spirit/game/scripts/cards/SWSH10/Whiscash_36.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="fc00cf30-d33d-56fd-ab61-082fc03570dc", @@ -30,7 +31,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 3}, damage=120, damage_operator="+", - effect=unimplemented, + effect=flip_damage(bonus=60, tails_self_damage=60), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/WyrdeerV_134.py b/spirit/game/scripts/cards/SWSH10/WyrdeerV_134.py index 3800f23..e8a8259 100644 --- a/spirit/game/scripts/cards/SWSH10/WyrdeerV_134.py +++ b/spirit/game/scripts/cards/SWSH10/WyrdeerV_134.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy + + +async def frontier_road(ctx): + """Once per turn, on move to Active: you may move any amount of Energy + from your other Pokemon to it.""" + if not await ctx.ask_yes_no( + "Move any amount of Energy from your other Pokémon to this Pokémon?" + ): + return + sources = [p for p in ctx.my_pokemon_in_play() if p is not ctx.source] + if not sources: + return + await ctx.move_energy_freely( + sources, [ctx.source], prompt="Choose an Energy to move") + card = PokemonCardDef( guid="1128014f-5fb1-51ad-a978-499755b49fcd", @@ -20,16 +36,17 @@ card = PokemonCardDef( abilities=[ Ability( title="Frontier Road", - game_text="Once during your turn, when this Pok\u00e9mon moves from your Bench to the Active Spot, you may move any amount of Energy from your other Pok\u00e9mon to it.", - effect=unimplemented, + game_text="Once during your turn, when this Pokémon moves from your Bench to the Active Spot, you may move any amount of Energy from your other Pokémon to it.", + trigger=Triggers.ON_MOVE_TO_ACTIVE, + effect=frontier_road, ), Attack( title="Psyshield Bash", - game_text="This attack does 40 damage for each Energy attached to this Pok\u00e9mon.", + game_text="This attack does 40 damage for each Energy attached to this Pokémon.", cost={PokemonTypes.COLORLESS: 3}, damage=40, damage_operator="x", - effect=unimplemented, + effect=damage_per(count_energy("self"), 40), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH10/WyrdeerV_180.py b/spirit/game/scripts/cards/SWSH10/WyrdeerV_180.py index 2f52742..ff8bc4c 100644 --- a/spirit/game/scripts/cards/SWSH10/WyrdeerV_180.py +++ b/spirit/game/scripts/cards/SWSH10/WyrdeerV_180.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy + + +async def frontier_road(ctx): + """Once per turn, on move to Active: you may move any amount of Energy + from your other Pokemon to it.""" + if not await ctx.ask_yes_no( + "Move any amount of Energy from your other Pokémon to this Pokémon?" + ): + return + sources = [p for p in ctx.my_pokemon_in_play() if p is not ctx.source] + if not sources: + return + await ctx.move_energy_freely( + sources, [ctx.source], prompt="Choose an Energy to move") + card = PokemonCardDef( guid="22aa9879-2682-505a-8f4c-5cf215e93d80", @@ -20,16 +36,17 @@ card = PokemonCardDef( abilities=[ Ability( title="Frontier Road", - game_text="Once during your turn, when this Pok\u00e9mon moves from your Bench to the Active Spot, you may move any amount of Energy from your other Pok\u00e9mon to it.", - effect=unimplemented, + game_text="Once during your turn, when this Pokémon moves from your Bench to the Active Spot, you may move any amount of Energy from your other Pokémon to it.", + trigger=Triggers.ON_MOVE_TO_ACTIVE, + effect=frontier_road, ), Attack( title="Psyshield Bash", - game_text="This attack does 40 damage for each Energy attached to this Pok\u00e9mon.", + game_text="This attack does 40 damage for each Energy attached to this Pokémon.", cost={PokemonTypes.COLORLESS: 3}, damage=40, damage_operator="x", - effect=unimplemented, + effect=damage_per(count_energy("self"), 40), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH10/Wyrdeer_69.py b/spirit/game/scripts/cards/SWSH10/Wyrdeer_69.py index 6720a3e..227bd98 100644 --- a/spirit/game/scripts/cards/SWSH10/Wyrdeer_69.py +++ b/spirit/game/scripts/cards/SWSH10/Wyrdeer_69.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if + + +async def hurried_gait(ctx): + """Once during your turn, you may draw a card.""" + if await ctx.ask_yes_no("Draw a card?"): + await ctx.draw_cards(1) + + +def _same_hand_size(ctx): + return ctx.hand_size() == ctx.hand_size(ctx.opponent_id) + card = PokemonCardDef( guid="b2d9bd21-030a-5bcd-887e-93db6255c9cb", @@ -23,7 +35,8 @@ card = PokemonCardDef( Ability( title="Hurried Gait", game_text="Once during your turn, you may draw a card.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + effect=hurried_gait, ), Attack( title="Extrasensory", @@ -31,7 +44,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=40, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_same_hand_size, 80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH10/Yanmega_7.py b/spirit/game/scripts/cards/SWSH10/Yanmega_7.py index 96d7f71..2be426e 100644 --- a/spirit/game/scripts/cards/SWSH10/Yanmega_7.py +++ b/spirit/game/scripts/cards/SWSH10/Yanmega_7.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if + +_more_cards_in_hand = lambda ctx: ctx.hand_size(ctx.player_id) > ctx.hand_size(ctx.opponent_id) card = PokemonCardDef( guid="8e900cf3-217f-544e-ac79-aad797981c06", @@ -30,7 +33,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=40, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_more_cards_in_hand, 80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Abomasnow_43.py b/spirit/game/scripts/cards/SWSH11/Abomasnow_43.py index e9a7141..fba6c19 100644 --- a/spirit/game/scripts/cards/SWSH11/Abomasnow_43.py +++ b/spirit/game/scripts/cards/SWSH11/Abomasnow_43.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack card = PokemonCardDef( guid="a7f8c720-d638-5170-b6e6-be8e529c29c3", @@ -29,7 +30,7 @@ card = PokemonCardDef( game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, damage=160, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/AerodactylVSTAR_199.py b/spirit/game/scripts/cards/SWSH11/AerodactylVSTAR_199.py index db8c190..15019db 100644 --- a/spirit/game/scripts/cards/SWSH11/AerodactylVSTAR_199.py +++ b/spirit/game/scripts/cards/SWSH11/AerodactylVSTAR_199.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import lost_dive, ancient_star card = PokemonCardDef( guid="5cf7a202-67a0-50ac-9616-b354a4f743a7", @@ -24,13 +25,14 @@ card = PokemonCardDef( game_text="Put the top 3 cards of your deck in the Lost Zone.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 2}, damage=240, - effect=unimplemented, + effect=lost_dive, ), Attack( title="Ancient Star", - game_text="Until this Pok\u00e9mon leaves play, it gains an Ability that has the effect \"Your opponent's Pok\u00e9mon V in play, except any Aerodactyl VSTAR, have no Abilities.\" (You can't use more than 1 VSTAR Power in a game.)", + game_text="Until this Pokémon leaves play, it gains an Ability that has the effect \"Your opponent's Pokémon V in play, except any Aerodactyl VSTAR, have no Abilities.\" (You can't use more than 1 VSTAR Power in a game.)", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + vstar=True, + effect=ancient_star, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH11/Aipom_144.py b/spirit/game/scripts/cards/SWSH11/Aipom_144.py index 9ddce4a..98b8ddb 100644 --- a/spirit/game/scripts/cards/SWSH11/Aipom_144.py +++ b/spirit/game/scripts/cards/SWSH11/Aipom_144.py @@ -1,6 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def mischievous_tail(ctx): + """Look at the top card of your opponent's deck. You may have your + opponent shuffle their deck.""" + top = ctx.deck_top(1, player_id=ctx.opponent_id) + if top: + await ctx.session.prompt_view_cards( + ctx.player_id, ctx.source.entity_id, top, + prompt="Top card of your opponent's deck", + ) + if await ctx.ask_yes_no("Have your opponent shuffle their deck?"): + await ctx.shuffle_deck(ctx.opponent_id) + + card = PokemonCardDef( guid="37870c3e-4161-59bd-80c0-6612f507fbf9", key="SWSH11", @@ -22,7 +36,7 @@ card = PokemonCardDef( title="Mischievous Tail", game_text="Look at the top card of your opponent's deck. You may have your opponent shuffle their deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=mischievous_tail, ), Attack( title="Scratch", diff --git a/spirit/game/scripts/cards/SWSH11/Ambipom_145.py b/spirit/game/scripts/cards/SWSH11/Ambipom_145.py index 09e4291..e17ac0a 100644 --- a/spirit/game/scripts/cards/SWSH11/Ambipom_145.py +++ b/spirit/game/scripts/cards/SWSH11/Ambipom_145.py @@ -1,5 +1,25 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import discard_random_from_hand + + +async def all_you_can_grab(ctx): + """Flip until tails; search that many cards into hand, then shuffle.""" + heads = await ctx.flip_until_tails("All-You-Can-Grab") + if heads > 0: + picks = await ctx.search_deck( + count=heads, minimum=0, + prompt=f"Choose up to {heads} cards to put into your hand.", + ) + await ctx.put_in_hand(picks, reveal=False) + await ctx.shuffle_deck() + + +async def knock_off(ctx): + """50 damage, then discard a random card from the opponent's hand.""" + await ctx.deal_damage() + await discard_random_from_hand(ctx, ctx.opponent_id, count=1) + card = PokemonCardDef( guid="12794499-aaf7-5ab6-9b04-0c01e7ade512", @@ -23,14 +43,14 @@ card = PokemonCardDef( title="All-You-Can-Grab", game_text="Flip a coin until you get tails. Search your deck for a number of cards up to the number of heads and put them into your hand. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=all_you_can_grab, ), Attack( title="Knock Off", game_text="Discard a random card from your opponent's hand.", cost={PokemonTypes.COLORLESS: 2}, damage=50, - effect=unimplemented, + effect=knock_off, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/ArcPhone_152.py b/spirit/game/scripts/cards/SWSH11/ArcPhone_152.py index ba4fc0f..6f68569 100644 --- a/spirit/game/scripts/cards/SWSH11/ArcPhone_152.py +++ b/spirit/game/scripts/cards/SWSH11/ArcPhone_152.py @@ -1,5 +1,58 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import ItemCardDef +from spirit.game.attributes import GameSequence, Rarities +from spirit.game.card_effects.trainers import deck_nonempty + + +async def _arc_phone(ctx): + """Look at the top card of your deck; you may switch it with 1 of your + face-down Prize cards (the cards stay face down).""" + session = ctx.session + top = ctx.deck_top(1) + if not top: + return + card = top[0] + prize_area = ctx.board.find_player_area(ctx.player_id, "prizePile") + prizes = list(prize_area.children) if prize_area else [] + if not prizes: + await ctx.present_card_choice(card, "Top card of your deck", ["OK"]) + return + idx = await ctx.present_card_choice( + card, "Switch this card with 1 of your face-down Prize cards?", + ["Switch with a Prize card", "Put it back on top"], + ) + if idx != 0: + return + picked_ids = await session._prompt_prize_pick( + ctx.player_id, [c.entity_id for c in prizes], 1, + prompt="Choose a face-down Prize card to switch.", + ) + picked = ctx.board.get_entity(picked_ids[0]) if picked_ids else None + if picked is None or picked.parent is not prize_area: + return + slot = getattr(picked, "board_slot", None) + if slot is None: + slot = prizes.index(picked) + deck = ctx.board.find_player_area(ctx.player_id, "deck") + both = list(session.players.values()) + # Prize -> top of deck rides WithOpenPrizeCards: after a pick, r.B defers + # the fan teardown to this bracket; no intro = the card stays face down. + pos = len(deck.children) + if not ctx.board.move_card(picked.entity_id, deck.entity_id): + return + move = session._entity_moved_msg(picked.entity_id, deck.entity_id, pos) + for viewer in both: + await session.send_game_sequence( + [viewer], GameSequence.WITH_OPEN_PRIZE_CARDS, [move]) + # The peeked deck card takes the vacated face-down Prize slot. + if ctx.board.move_card(card.entity_id, prize_area.entity_id, slot): + await session.send_game_sequence( + both, GameSequence.GROUPED_MOVE, + [session._entity_moved_msg(card.entity_id, prize_area.entity_id, slot)]) + # "The cards stay face down": re-hide the peeked face for the owner. + await session.send_game_sequence( + [session.players[ctx.player_id]], GameSequence.GROUPED_MOVE, + [session._attributes_reset_msg(card.entity_id)]) + card = ItemCardDef( guid="233044df-118b-5892-8421-f89a8780e5d8", @@ -11,5 +64,6 @@ card = ItemCardDef( collector_number=152, set_code="SWSH11", rarity=Rarities.Uncommon, - effect=unimplemented + condition=deck_nonempty, + effect=_arc_phone, ) diff --git a/spirit/game/scripts/cards/SWSH11/Arezu_153.py b/spirit/game/scripts/cards/SWSH11/Arezu_153.py index e8e4489..2482e5e 100644 --- a/spirit/game/scripts/cards/SWSH11/Arezu_153.py +++ b/spirit/game/scripts/cards/SWSH11/Arezu_153.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef, has_rule_box from spirit.game.attributes import Rarities +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.session.effects import is_evolution_pokemon + + +def _arezu_predicate(card): + return is_evolution_pokemon(card) and not has_rule_box( + getattr(card, "archetype_id", None) or "" + ) + card = SupporterCardDef( guid="0596d5c6-2a6a-5ea0-8af2-9e80f8d1561f", @@ -11,5 +20,8 @@ card = SupporterCardDef( collector_number=153, set_code="SWSH11", rarity=Rarities.Uncommon, - effect=unimplemented + effect=search_to_hand( + _arezu_predicate, count=3, + prompt="Choose up to 3 Evolution Pokémon that don't have a Rule Box.", + ), ) diff --git a/spirit/game/scripts/cards/SWSH11/Arezu_189.py b/spirit/game/scripts/cards/SWSH11/Arezu_189.py index 4b03456..957ca96 100644 --- a/spirit/game/scripts/cards/SWSH11/Arezu_189.py +++ b/spirit/game/scripts/cards/SWSH11/Arezu_189.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef, has_rule_box from spirit.game.attributes import Rarities +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.session.effects import is_evolution_pokemon + + +def _arezu_predicate(card): + return is_evolution_pokemon(card) and not has_rule_box( + getattr(card, "archetype_id", None) or "" + ) + card = SupporterCardDef( guid="ea8382d2-f228-5179-aea5-2da44bfd9d06", @@ -11,5 +20,8 @@ card = SupporterCardDef( collector_number=189, set_code="SWSH11", rarity=Rarities.RareUltra, - effect=unimplemented + effect=search_to_hand( + _arezu_predicate, count=3, + prompt="Choose up to 3 Evolution Pokémon that don't have a Rule Box.", + ), ) diff --git a/spirit/game/scripts/cards/SWSH11/Arezu_204.py b/spirit/game/scripts/cards/SWSH11/Arezu_204.py index 576825d..ab4f70a 100644 --- a/spirit/game/scripts/cards/SWSH11/Arezu_204.py +++ b/spirit/game/scripts/cards/SWSH11/Arezu_204.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef, has_rule_box from spirit.game.attributes import Rarities +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.session.effects import is_evolution_pokemon + + +def _arezu_predicate(card): + return is_evolution_pokemon(card) and not has_rule_box( + getattr(card, "archetype_id", None) or "" + ) + card = SupporterCardDef( guid="9fad0340-d129-55f0-b5ba-aa7d83282356", @@ -11,5 +20,8 @@ card = SupporterCardDef( collector_number=204, set_code="SWSH11", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=search_to_hand( + _arezu_predicate, count=3, + prompt="Choose up to 3 Evolution Pokémon that don't have a Rule Box.", + ), ) diff --git a/spirit/game/scripts/cards/SWSH11/Ariados_113.py b/spirit/game/scripts/cards/SWSH11/Ariados_113.py index 20e492c..beb57e4 100644 --- a/spirit/game/scripts/cards/SWSH11/Ariados_113.py +++ b/spirit/game/scripts/cards/SWSH11/Ariados_113.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.data_utils import PokemonCardDef, Attack +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities, SpecialConditions + + +async def string_bind(ctx): + """10, plus 30 for each [C] in the opponent's Active's Retreat Cost.""" + active = ctx.opponent_active() + retreat = int(active.get_attribute(AttrID.RETREAT_COST) or 0) if active else 0 + await ctx.deal_damage(10 + 30 * retreat) + card = PokemonCardDef( guid="fe72c2d9-d868-5e86-8182-ba9e0b792544", @@ -21,18 +30,18 @@ card = PokemonCardDef( abilities=[ Attack( title="String Bind", - game_text="This attack does 30 more damage for each Colorless in your opponent's Active Pok\u00e9mon's Retreat Cost.", + game_text="This attack does 30 more damage for each Colorless in your opponent's Active Pokémon's Retreat Cost.", cost={PokemonTypes.DARKNESS: 1}, damage=10, damage_operator="+", - effect=unimplemented, + effect=string_bind, ), Attack( title="Venomous Fang", - game_text="Your opponent's Active Pok\u00e9mon is now Poisoned.", + game_text="Your opponent's Active Pokémon is now Poisoned.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 2}, damage=80, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH11/Banette_73.py b/spirit/game/scripts/cards/SWSH11/Banette_73.py index 811116f..554b566 100644 --- a/spirit/game/scripts/cards/SWSH11/Banette_73.py +++ b/spirit/game/scripts/cards/SWSH11/Banette_73.py @@ -1,5 +1,38 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import requires_discard +from spirit.game.session.effects import is_supporter_card, full_stack + + +async def puppet_offering(ctx): + """Once per turn: you may put a Supporter from discard into hand. If you do, Lost Zone this Pokemon (discard attachments).""" + pokemon = ctx.source + supporters = [c for c in ctx.discard_pile() if is_supporter_card(c)] + if not supporters: + return + if not await ctx.ask_yes_no("Put a Supporter card from your discard pile into your hand?"): + return + picks = await ctx.choose_cards( + supporters, 1, minimum=1, + prompt="Choose a Supporter card to put into your hand.", + ) + if not picks: + return + await ctx.put_in_hand(picks, reveal=False) + was_active = pokemon is ctx.my_active() + attachments = [c for c in full_stack(pokemon) if c is not pokemon] + if attachments: + await ctx.discard_cards(attachments) + await ctx.move_to_lost_zone([pokemon]) + if was_active: + async def _promote(): + if not await ctx.session._promote_new_active(ctx.player_id): + screen_name = ctx.session.players[ctx.player_id].screen_name + await ctx.session.end_game( + ctx.opponent_id, f"{screen_name} has no Pokémon left" + ) + ctx.deferred_actions.append(_promote) + card = PokemonCardDef( guid="ff63c087-6d3f-56ef-84ef-7edc9bd0429b", @@ -22,8 +55,10 @@ card = PokemonCardDef( abilities=[ Ability( title="Puppet Offering", - game_text="Once during your turn, you may put a Supporter card from your discard pile into your hand. If you do, put this Pok\u00e9mon in the Lost Zone. (Discard all attached cards.)", - effect=unimplemented, + game_text="Once during your turn, you may put a Supporter card from your discard pile into your hand. If you do, put this Pokémon in the Lost Zone. (Discard all attached cards.)", + activation=Activations.ONCE_PER_TURN, + condition=requires_discard(is_supporter_card), + effect=puppet_offering, ), Attack( title="Spooky Shot", @@ -31,4 +66,4 @@ card = PokemonCardDef( damage=50, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH11/Barbaracle_107.py b/spirit/game/scripts/cards/SWSH11/Barbaracle_107.py index e3a1a85..3401959 100644 --- a/spirit/game/scripts/cards/SWSH11/Barbaracle_107.py +++ b/spirit/game/scripts/cards/SWSH11/Barbaracle_107.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.passives import Passive + + +class LostBlockPassive(Passive): + def prize_destination(self, pokemon, ctx, carrier): + if pokemon.owning_player_id == carrier.owning_player_id: + return "lostZone" + return None + card = PokemonCardDef( guid="c19dbbcf-dd24-59a0-b223-b8a3ad95f377", @@ -22,7 +31,7 @@ card = PokemonCardDef( Ability( title="Lost Block", game_text="Your opponent puts any Prize cards they would take in the Lost Zone instead of into their hand.", - effect=unimplemented, + passive=LostBlockPassive(), ), Attack( title="Dynamic Chop", @@ -30,4 +39,4 @@ card = PokemonCardDef( damage=100, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH11/Beautifly_8.py b/spirit/game/scripts/cards/SWSH11/Beautifly_8.py index b544848..ddfee09 100644 --- a/spirit/game/scripts/cards/SWSH11/Beautifly_8.py +++ b/spirit/game/scripts/cards/SWSH11/Beautifly_8.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import heal_attack + + +def _stoked_straw_condition(board, player_id, pokemon): + hand = board.find_player_area(player_id, "hand") + hand_size = len(hand.children) if hand else 0 + return hand_size < 6 + + +async def stoked_straw(ctx): + """Once during your turn: you may draw cards until you have 6 in hand.""" + if await ctx.ask_yes_no("Draw cards until you have 6 cards in your hand?"): + await ctx.draw_until(6) + card = PokemonCardDef( guid="be095b10-ce3a-5665-993f-617585f29c64", @@ -22,14 +36,16 @@ card = PokemonCardDef( Ability( title="Stoked Straw", game_text="Once during your turn, you may draw cards until you have 6 cards in your hand.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=_stoked_straw_condition, + effect=stoked_straw, ), Attack( title="Mega Drain", game_text="Heal 30 damage from this Pok\u00e9mon.", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=70, - effect=unimplemented, + effect=heal_attack(30, target="self"), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Bouffalant_148.py b/spirit/game/scripts/cards/SWSH11/Bouffalant_148.py index 7132321..83928b9 100644 --- a/spirit/game/scripts/cards/SWSH11/Bouffalant_148.py +++ b/spirit/game/scripts/cards/SWSH11/Bouffalant_148.py @@ -1,6 +1,24 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def lost_headbutt(ctx): + """50. Put an Energy attached to your opponent's Active Pokemon in the Lost Zone.""" + await ctx.deal_damage() + target = ctx.defender + if target is None or ctx.effects_blocked(target): + return + energies = ctx.attached_energies(target) + if not energies: + return + picks = await ctx.choose_cards( + energies, 1, minimum=1, + prompt="Choose an Energy to put in the Lost Zone", + ) + if picks: + await ctx.move_to_lost_zone(picks) + + card = PokemonCardDef( guid="f86d0c9b-7034-5b93-bc19-f06cf5e58e10", key="SWSH11", @@ -20,10 +38,10 @@ card = PokemonCardDef( abilities=[ Attack( title="Lost Headbutt", - game_text="Put an Energy attached to your opponent's Active Pok\u00e9mon in the Lost Zone.", + game_text="Put an Energy attached to your opponent's Active Pokémon in the Lost Zone.", cost={PokemonTypes.COLORLESS: 3}, damage=50, - effect=unimplemented, + effect=lost_headbutt, ), Attack( title="Superpowered Horns", @@ -31,4 +49,4 @@ card = PokemonCardDef( damage=120, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH11/BoxofDisaster_154.py b/spirit/game/scripts/cards/SWSH11/BoxofDisaster_154.py index 8b64341..2417673 100644 --- a/spirit/game/scripts/cards/SWSH11/BoxofDisaster_154.py +++ b/spirit/game/scripts/cards/SWSH11/BoxofDisaster_154.py @@ -1,15 +1,38 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented +from spirit.game.data_utils import PokemonToolCardDef, Ability, Triggers, is_pokemon_v from spirit.game.attributes import Rarities + +async def box_of_disaster(ctx): + """If the V holder had full HP and is Knocked Out by an opposing attack, put 8 counters on the attacker.""" + pokemon = ctx.source + if not is_pokemon_v(pokemon.archetype_id): + return + if ctx.pre_hit_hp != ctx.max_hp(pokemon): + return + if ctx.damage_amount < ctx.pre_hit_hp: + return + attacker = ctx.damaged_by + if attacker is None or attacker.owning_player_id == pokemon.owning_player_id: + return + await ctx.deal_damage(80, target=attacker, apply_modifiers=False, as_counters=True) + + card = PokemonToolCardDef( guid="568284e9-61ba-506a-b1e2-c41ec5276fa2", key="SWSH11", name="com.direwolfdigital.cake.data.archetypes.trainer.BoxofDisaster.Name", display_name="Box of Disaster", - searchable_by=["Box of Disaster", "Item", "Pok\u00c3\u00a9mon Tool"], - subtypes=["Item", "Pok\u00e9mon Tool"], + searchable_by=["Box of Disaster", "Item", "Pokémon Tool"], + subtypes=["Item", "Pokémon Tool"], collector_number=154, set_code="SWSH11", rarity=Rarities.Uncommon, - effect=unimplemented + granted_abilities=[ + Ability( + title="Box of Disaster", + game_text="If the Pokémon V this card is attached to has full HP and is Knocked Out by damage from an attack from your opponent's Pokémon, put 8 damage counters on the Attacking Pokémon.", + trigger=Triggers.ON_DAMAGED_BY_ATTACK, + effect=box_of_disaster, + ), + ], ) diff --git a/spirit/game/scripts/cards/SWSH11/BoxofDisaster_214.py b/spirit/game/scripts/cards/SWSH11/BoxofDisaster_214.py index b1153ba..ed9841a 100644 --- a/spirit/game/scripts/cards/SWSH11/BoxofDisaster_214.py +++ b/spirit/game/scripts/cards/SWSH11/BoxofDisaster_214.py @@ -1,15 +1,38 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented +from spirit.game.data_utils import PokemonToolCardDef, Ability, Triggers, is_pokemon_v from spirit.game.attributes import Rarities + +async def box_of_disaster(ctx): + """If the V holder had full HP and is Knocked Out by an opposing attack, put 8 counters on the attacker.""" + pokemon = ctx.source + if not is_pokemon_v(pokemon.archetype_id): + return + if ctx.pre_hit_hp != ctx.max_hp(pokemon): + return + if ctx.damage_amount < ctx.pre_hit_hp: + return + attacker = ctx.damaged_by + if attacker is None or attacker.owning_player_id == pokemon.owning_player_id: + return + await ctx.deal_damage(80, target=attacker, apply_modifiers=False, as_counters=True) + + card = PokemonToolCardDef( guid="9bae7cf7-58df-5332-8607-39bf48170632", key="SWSH11", name="com.direwolfdigital.cake.data.archetypes.trainer.BoxofDisaster.Name", display_name="Box of Disaster", - searchable_by=["Box of Disaster", "Item", "Pok\u00c3\u00a9mon Tool"], - subtypes=["Item", "Pok\u00e9mon Tool"], + searchable_by=["Box of Disaster", "Item", "Pokémon Tool"], + subtypes=["Item", "Pokémon Tool"], collector_number=214, set_code="SWSH11", rarity=Rarities.RareSecret, - effect=unimplemented + granted_abilities=[ + Ability( + title="Box of Disaster", + game_text="If the Pokémon V this card is attached to has full HP and is Knocked Out by damage from an attack from your opponent's Pokémon, put 8 damage counters on the Attacking Pokémon.", + trigger=Triggers.ON_DAMAGED_BY_ATTACK, + effect=box_of_disaster, + ), + ], ) diff --git a/spirit/game/scripts/cards/SWSH11/Bronzong_126.py b/spirit/game/scripts/cards/SWSH11/Bronzong_126.py index bc35dee..ef792db 100644 --- a/spirit/game/scripts/cards/SWSH11/Bronzong_126.py +++ b/spirit/game/scripts/cards/SWSH11/Bronzong_126.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.attacks_common import damage_per + + +def _defender_retreat_cost(ctx): + defender = ctx.defender + return defender.get_attribute(AttrID.RETREAT_COST, 0) if defender else 0 + card = PokemonCardDef( guid="6bf4ce6d-4783-5551-b301-543d0ecf5713", @@ -31,7 +38,7 @@ card = PokemonCardDef( cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 2}, damage=40, damage_operator="+", - effect=unimplemented, + effect=damage_per(_defender_retreat_cost, 40, base=0), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Carbink_108.py b/spirit/game/scripts/cards/SWSH11/Carbink_108.py index 9a78750..0aaf7ed 100644 --- a/spirit/game/scripts/cards/SWSH11/Carbink_108.py +++ b/spirit/game/scripts/cards/SWSH11/Carbink_108.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_item_card +from spirit.game.card_effects.support_common import search_to_hand card = PokemonCardDef( guid="bb9def3a-b727-524f-a567-f9996ea18cf2", @@ -22,7 +24,10 @@ card = PokemonCardDef( title="Lucky Find", game_text="Search your deck for up to 2 Item cards, reveal them, and put them into your hand. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_to_hand( + is_item_card, count=2, + prompt="Choose up to 2 Item cards to put into your hand.", + ), ), Attack( title="Power Gem", diff --git a/spirit/game/scripts/cards/SWSH11/Cascoon_9.py b/spirit/game/scripts/cards/SWSH11/Cascoon_9.py index 293aaf5..c0a88b0 100644 --- a/spirit/game/scripts/cards/SWSH11/Cascoon_9.py +++ b/spirit/game/scripts/cards/SWSH11/Cascoon_9.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import prevent_damage_when + + +async def harden(ctx): + """During the opponent's next turn, prevent all attack damage to this + Pokemon that is 60 or less.""" + ctx.add_passive_through_opponents_turn( + ctx.source, prevent_damage_when(lambda calc, c: calc.amount <= 60) + ) + card = PokemonCardDef( guid="0ea9c8a0-5b43-55db-b364-706d453c128e", @@ -23,7 +33,7 @@ card = PokemonCardDef( title="Harden", game_text="During your opponent's next turn, prevent all damage done to this Pok\u00e9mon by attacks if that damage is 60 or less.", cost={PokemonTypes.GRASS: 1}, - effect=unimplemented, + effect=harden, ), Attack( title="Rolling Tackle", diff --git a/spirit/game/scripts/cards/SWSH11/Chandelure_26.py b/spirit/game/scripts/cards/SWSH11/Chandelure_26.py index d943d3a..f5b2607 100644 --- a/spirit/game/scripts/cards/SWSH11/Chandelure_26.py +++ b/spirit/game/scripts/cards/SWSH11/Chandelure_26.py @@ -1,6 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def mountain_roasting(ctx): + """On evolve: you may discard the top 3 cards of your opponent's deck.""" + if await ctx.ask_yes_no("Discard the top 3 cards of your opponent's deck?"): + await ctx.discard_cards(ctx.deck_top(3, player_id=ctx.opponent_id)) + + card = PokemonCardDef( guid="7fcef5a2-86ab-5518-9f30-2f948ed75d8f", key="SWSH11", @@ -22,7 +29,8 @@ card = PokemonCardDef( Ability( title="Mountain Roasting", game_text="When you play this Pok\u00e9mon from your hand to evolve 1 of your Pok\u00e9mon during your turn, you may discard the top 3 cards of your opponent's deck.", - effect=unimplemented, + trigger=Triggers.ON_EVOLVE, + effect=mountain_roasting, ), Attack( title="Heat Blast", diff --git a/spirit/game/scripts/cards/SWSH11/Clefable_63.py b/spirit/game/scripts/cards/SWSH11/Clefable_63.py index 1035e4e..3bd2375 100644 --- a/spirit/game/scripts/cards/SWSH11/Clefable_63.py +++ b/spirit/game/scripts/cards/SWSH11/Clefable_63.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.passives_common import takes_less_passive + + +def _is_dragon(pokemon): + return PokemonTypes.DRAGON.value in (pokemon.get_attribute(AttrID.POKEMON_TYPES) or []) + card = PokemonCardDef( guid="14b805a3-9860-5e1c-b8ca-df2ba4b47988", @@ -22,7 +28,9 @@ card = PokemonCardDef( Ability( title="Spirit Charm", game_text="All of your Pok\u00e9mon take 30 less damage from attacks from your opponent's Dragon Pok\u00e9mon (after applying Weakness and Resistance). You can't apply more than 1 Spirit Charm Ability at a time.", - effect=unimplemented, + passive=takes_less_passive( + 30, protects="team", attacker_pred=_is_dragon, stack_key="SpiritCharm" + ), ), Attack( title="Moon Impact", diff --git a/spirit/game/scripts/cards/SWSH11/Clefairy_62.py b/spirit/game/scripts/cards/SWSH11/Clefairy_62.py index e00f08e..d29a8a5 100644 --- a/spirit/game/scripts/cards/SWSH11/Clefairy_62.py +++ b/spirit/game/scripts/cards/SWSH11/Clefairy_62.py @@ -1,6 +1,47 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import count_energy, damage_per +from spirit.game.card_effects.pokemon import energy_provides_type, in_active_spot +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations, def_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +def _is_clefairy(pokemon) -> bool: + d = def_for(pokemon.archetype_id) + return bool(d) and d.display_name == "Clefairy" + + +def _has_benched_clefairy(board, player_id) -> bool: + bench = board.find_player_area(player_id, "bench") + return bool(bench) and any(_is_clefairy(c) for c in bench.children) + + +def moon_watching_party_condition(board, player_id, pokemon): + return in_active_spot(board, player_id, pokemon) and \ + _has_benched_clefairy(board, player_id) + + +async def moon_watching_party(ctx): + """Once per turn, in the Active Spot: for each Benched Clefairy, you may + search a Psychic Energy card and attach it to that Clefairy.""" + targets = [p for p in ctx.my_bench() if _is_clefairy(p)] + if not targets: + return + searched_any = False + for target in targets: + if not await ctx.ask_yes_no( + "Search your deck for a Psychic Energy card to attach to your Benched Clefairy?"): + continue + picks = await ctx.search_deck( + lambda c: energy_provides_type(c, PokemonTypes.PSYCHIC.value), + count=1, minimum=0, + prompt="Choose a Psychic Energy card to attach.", + ) + if picks: + await ctx.attach_energy(picks[0], target) + searched_any = True + if searched_any: + await ctx.shuffle_deck() + + card = PokemonCardDef( guid="77c480af-bb9a-5b71-b32f-41a2c325236e", key="SWSH11", @@ -21,7 +62,9 @@ card = PokemonCardDef( Ability( title="Moon-Watching Party", game_text="Once during your turn, if this Pok\u00e9mon is in the Active Spot, for each of your Benched Clefairy, you may search your deck for a Psychic Energy card and attach it to that Clefairy. Then, shuffle your deck.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=moon_watching_party_condition, + effect=moon_watching_party, ), Attack( title="Wonder Storm", @@ -29,7 +72,9 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=20, damage_operator="x", - effect=unimplemented, + effect=damage_per( + count_energy("mine", energy_type=PokemonTypes.PSYCHIC), 20, + ), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/CollapsedStadium_215.py b/spirit/game/scripts/cards/SWSH11/CollapsedStadium_215.py index c5c8c8e..ebaa7e6 100644 --- a/spirit/game/scripts/cards/SWSH11/CollapsedStadium_215.py +++ b/spirit/game/scripts/cards/SWSH11/CollapsedStadium_215.py @@ -1,7 +1,9 @@ -from spirit.game.data_utils import StadiumCardDef, unimplemented +from spirit.game.card_effects.trainers import CollapsedStadiumPassive +from spirit.game.data_utils import StadiumCardDef from spirit.game.attributes import Rarities card = StadiumCardDef( + passive=CollapsedStadiumPassive(), guid="0fcfbb09-dce2-5b59-b0ea-7d90778a6fdd", key="SWSH11", name="com.direwolfdigital.cake.data.archetypes.trainer.CollapsedStadium.Name", @@ -10,6 +12,5 @@ card = StadiumCardDef( subtypes=["Stadium"], collector_number=215, set_code="SWSH11", - rarity=Rarities.RareSecret, - effect=unimplemented + rarity=Rarities.RareSecret ) diff --git a/spirit/game/scripts/cards/SWSH11/ColresssExperiment_190.py b/spirit/game/scripts/cards/SWSH11/ColresssExperiment_190.py index 6e7f0fb..8a3538c 100644 --- a/spirit/game/scripts/cards/SWSH11/ColresssExperiment_190.py +++ b/spirit/game/scripts/cards/SWSH11/ColresssExperiment_190.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import colresss_experiment card = SupporterCardDef( guid="b710e37a-6235-5155-a645-16ee63019f36", @@ -11,5 +12,5 @@ card = SupporterCardDef( collector_number=190, set_code="SWSH11", rarity=Rarities.RareUltra, - effect=unimplemented + effect=colresss_experiment, ) diff --git a/spirit/game/scripts/cards/SWSH11/ColresssExperiment_205.py b/spirit/game/scripts/cards/SWSH11/ColresssExperiment_205.py index c8cb3c1..6c3d7bb 100644 --- a/spirit/game/scripts/cards/SWSH11/ColresssExperiment_205.py +++ b/spirit/game/scripts/cards/SWSH11/ColresssExperiment_205.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import colresss_experiment card = SupporterCardDef( guid="e9933893-f67c-5952-ba97-58c973305aa9", @@ -11,5 +12,5 @@ card = SupporterCardDef( collector_number=205, set_code="SWSH11", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=colresss_experiment, ) diff --git a/spirit/game/scripts/cards/SWSH11/Cresselia_74.py b/spirit/game/scripts/cards/SWSH11/Cresselia_74.py index 3f49a00..42f8ff3 100644 --- a/spirit/game/scripts/cards/SWSH11/Cresselia_74.py +++ b/spirit/game/scripts/cards/SWSH11/Cresselia_74.py @@ -1,6 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def moonglow_reverse(ctx): + """Move 2 damage counters from each of your Pokémon to 1 of your + opponent's Pokémon.""" + targets = ctx.opponent_pokemon_in_play() + if not targets: + return + dest = await ctx.choose_pokemon(targets, "Choose 1 of your opponent's Pokémon") + if dest is None: + return + for source in ctx.my_pokemon_in_play(): + await ctx.move_damage_counters(source, dest, max_count=2) + + card = PokemonCardDef( guid="0609741e-143a-539a-a387-c5c750f1ee6e", key="SWSH11", @@ -23,7 +37,7 @@ card = PokemonCardDef( title="Moonglow Reverse", game_text="Move 2 damage counters from each of your Pok\u00e9mon to 1 of your opponent's Pok\u00e9mon.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=moonglow_reverse, ), Attack( title="Lunar Blast", diff --git a/spirit/game/scripts/cards/SWSH11/DamagePump_156.py b/spirit/game/scripts/cards/SWSH11/DamagePump_156.py index 059d286..7b68073 100644 --- a/spirit/game/scripts/cards/SWSH11/DamagePump_156.py +++ b/spirit/game/scripts/cards/SWSH11/DamagePump_156.py @@ -1,5 +1,31 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import ItemCardDef +from spirit.game.attributes import Rarities, AttrID +from spirit.game.session.passives import effective_max_hp + + +def _damage_pump_condition(board, player_id, pokemon=None): + mine = board.pokemon_in_play(player_id) + if len(mine) < 2: + return False + return any(p.get_attribute(AttrID.HP, 0) < effective_max_hp(board, p) for p in mine) + + +async def damage_pump(ctx): + """Move up to 2 damage counters from 1 of your Pokemon to your others, any way you like.""" + candidates = [p for p in ctx.my_pokemon_in_play() + if p.get_attribute(AttrID.HP, 0) < ctx.max_hp(p)] + if not candidates: + return + source = await ctx.choose_pokemon( + candidates, "Choose 1 of your Pokémon to move damage counters from" + ) + if source is None: + return + targets = [p for p in ctx.my_pokemon_in_play() if p is not source] + if not targets: + return + await ctx.move_damage_counters(source, targets, max_count=2) + card = ItemCardDef( guid="d7949429-a9ba-5fc0-b1e9-6124b68e0c60", @@ -11,5 +37,6 @@ card = ItemCardDef( collector_number=156, set_code="SWSH11", rarity=Rarities.Uncommon, - effect=unimplemented + effect=damage_pump, + condition=_damage_pump_condition ) diff --git a/spirit/game/scripts/cards/SWSH11/DarkPatch_216.py b/spirit/game/scripts/cards/SWSH11/DarkPatch_216.py index 0eebdb0..1e00ce1 100644 --- a/spirit/game/scripts/cards/SWSH11/DarkPatch_216.py +++ b/spirit/game/scripts/cards/SWSH11/DarkPatch_216.py @@ -1,5 +1,30 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import ItemCardDef +from spirit.game.attributes import Rarities, PokemonTypes, AttrID +from spirit.game.card_effects.support_common import attach_from_discard +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.card_effects.passives_common import is_in_active_spot + + +def _is_basic_darkness_energy(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_basic_energy_card(card) and PokemonTypes.DARKNESS.value in types + + +def _is_benched_darkness_pokemon(pokemon): + types = pokemon.get_attribute(AttrID.POKEMON_TYPES) or [] + return not is_in_active_spot(pokemon) and PokemonTypes.DARKNESS.value in types + + +def _dark_patch_condition(board, player_id, pokemon=None): + bench = board.find_player_area(player_id, "bench") + bench_children = bench.children if bench else [] + if not any(PokemonTypes.DARKNESS.value in (p.get_attribute(AttrID.POKEMON_TYPES) or []) + for p in bench_children): + return False + discard = board.find_player_area(player_id, "discard") + cards = discard.children if discard else [] + return any(_is_basic_darkness_energy(c) for c in cards) + card = ItemCardDef( guid="41392d8d-627e-561b-b553-f54d72d1f731", @@ -11,5 +36,10 @@ card = ItemCardDef( collector_number=216, set_code="SWSH11", rarity=Rarities.RareSecret, - effect=unimplemented + effect=attach_from_discard( + predicate=_is_basic_darkness_energy, count=1, minimum=1, + target=_is_benched_darkness_pokemon, + prompt="Choose a basic Darkness Energy card to attach to a Benched Darkness Pokémon", + ), + condition=_dark_patch_condition ) diff --git a/spirit/game/scripts/cards/SWSH11/Darkrai_120.py b/spirit/game/scripts/cards/SWSH11/Darkrai_120.py index 3b7f59d..fec7321 100644 --- a/spirit/game/scripts/cards/SWSH11/Darkrai_120.py +++ b/spirit/game/scripts/cards/SWSH11/Darkrai_120.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="ca0781b0-f1eb-51fe-ab9f-7e77f5ce11c4", @@ -23,14 +24,14 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Asleep.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=condition_attack(SpecialConditions.ASLEEP), ), Attack( title="Pitch-Black Blade", game_text="During your next turn, this Pok\u00e9mon can't attack.", cost={PokemonTypes.DARKNESS: 2, PokemonTypes.COLORLESS: 1}, damage=130, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/DelphoxV_173.py b/spirit/game/scripts/cards/SWSH11/DelphoxV_173.py index db35d28..9bbdb36 100644 --- a/spirit/game/scripts/cards/SWSH11/DelphoxV_173.py +++ b/spirit/game/scripts/cards/SWSH11/DelphoxV_173.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack, snipe_attack + +_magical_fire_base = snipe_attack(120, also_base=True) + + +async def magical_fire(ctx): + """Put 2 Energy attached to this Pokemon in the Lost Zone. Also does 120 damage to 1 of your opponent's Benched Pokemon (no W/R).""" + energies = ctx.attached_energies(ctx.attacker) + if energies: + picks = await ctx.choose_cards( + energies, min(2, len(energies)), + prompt="Put 2 Energy attached to this Pokémon in the Lost Zone", + ) + if picks: + await ctx.move_to_lost_zone(picks) + await _magical_fire_base(ctx) + card = PokemonCardDef( guid="fa35628f-a394-5e28-83d8-6d0bdb8648c1", @@ -20,16 +37,16 @@ card = PokemonCardDef( abilities=[ Attack( title="Eerie Glow", - game_text="Your opponent's Active Pok\u00e9mon is now Burned and Confused.", + game_text="Your opponent's Active Pokémon is now Burned and Confused.", cost={PokemonTypes.FIRE: 1}, - effect=unimplemented, + effect=condition_attack(SpecialConditions.BURNED, SpecialConditions.CONFUSED), ), Attack( title="Magical Fire", - game_text="Put 2 Energy attached to this Pok\u00e9mon in the Lost Zone. This attack also does 120 damage to 1 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", + game_text="Put 2 Energy attached to this Pokémon in the Lost Zone. This attack also does 120 damage to 1 of your opponent's Benched Pokémon. (Don't apply Weakness and Resistance for Benched Pokémon.)", cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + effect=magical_fire, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH11/DelphoxV_27.py b/spirit/game/scripts/cards/SWSH11/DelphoxV_27.py index 7311b93..a21ba8f 100644 --- a/spirit/game/scripts/cards/SWSH11/DelphoxV_27.py +++ b/spirit/game/scripts/cards/SWSH11/DelphoxV_27.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack, snipe_attack + +_magical_fire_base = snipe_attack(120, also_base=True) + + +async def magical_fire(ctx): + """Put 2 Energy attached to this Pokemon in the Lost Zone. Also does 120 damage to 1 of your opponent's Benched Pokemon (no W/R).""" + energies = ctx.attached_energies(ctx.attacker) + if energies: + picks = await ctx.choose_cards( + energies, min(2, len(energies)), + prompt="Put 2 Energy attached to this Pokémon in the Lost Zone", + ) + if picks: + await ctx.move_to_lost_zone(picks) + await _magical_fire_base(ctx) + card = PokemonCardDef( guid="25fa5714-a233-5c79-83d6-3850dc7ae95e", @@ -20,16 +37,16 @@ card = PokemonCardDef( abilities=[ Attack( title="Eerie Glow", - game_text="Your opponent's Active Pok\u00e9mon is now Burned and Confused.", + game_text="Your opponent's Active Pokémon is now Burned and Confused.", cost={PokemonTypes.FIRE: 1}, - effect=unimplemented, + effect=condition_attack(SpecialConditions.BURNED, SpecialConditions.CONFUSED), ), Attack( title="Magical Fire", - game_text="Put 2 Energy attached to this Pok\u00e9mon in the Lost Zone. This attack also does 120 damage to 1 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", + game_text="Put 2 Energy attached to this Pokémon in the Lost Zone. This attack also does 120 damage to 1 of your opponent's Benched Pokémon. (Don't apply Weakness and Resistance for Benched Pokémon.)", cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + effect=magical_fire, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH11/Dewgong_34.py b/spirit/game/scripts/cards/SWSH11/Dewgong_34.py index 69a74d9..afbf3ef 100644 --- a/spirit/game/scripts/cards/SWSH11/Dewgong_34.py +++ b/spirit/game/scripts/cards/SWSH11/Dewgong_34.py @@ -1,5 +1,28 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import flip_protection +from spirit.game.card_effects.pokemon import energy_provides_type + + +async def floe_return(ctx): + """Shuffle any amount of Water Energy from your Pokemon into your deck; + 40 damage for each card shuffled in this way.""" + candidates = [ + e for p in ctx.my_pokemon_in_play() + for e in ctx.attached_energies(p) + if energy_provides_type(e, PokemonTypes.WATER) + ] + picks = [] + if candidates: + picks = await ctx.choose_cards( + candidates, len(candidates), minimum=0, + prompt="Choose any amount of Water Energy from your Pokémon to shuffle into your deck.", + ) + if picks: + await ctx.shuffle_into_deck(picks) + if picks: + await ctx.deal_damage(40 * len(picks)) + card = PokemonCardDef( guid="1a214877-fad3-51c4-b34d-96b3648cd460", @@ -24,7 +47,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, during your opponent's next turn, prevent all damage from and effects of attacks done to this Pok\u00e9mon.", cost={PokemonTypes.WATER: 1}, damage=10, - effect=unimplemented, + effect=flip_protection(prevent=True, effects_too=True), ), Attack( title="Floe Return", @@ -32,7 +55,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=40, damage_operator="x", - effect=unimplemented, + effect=floe_return, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Dottler_19.py b/spirit/game/scripts/cards/SWSH11/Dottler_19.py index 33896e2..1bc3cd1 100644 --- a/spirit/game/scripts/cards/SWSH11/Dottler_19.py +++ b/spirit/game/scripts/cards/SWSH11/Dottler_19.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import protect_next_turn card = PokemonCardDef( guid="c65e6435-45e0-5573-a460-d58399230026", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, this Pok\u00e9mon takes 30 less damage from attacks (after applying Weakness and Resistance).", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=protect_next_turn(reduce=30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/DrapionVSTAR_119.py b/spirit/game/scripts/cards/SWSH11/DrapionVSTAR_119.py index ec2d03e..75f800b 100644 --- a/spirit/game/scripts/cards/SWSH11/DrapionVSTAR_119.py +++ b/spirit/game/scripts/cards/SWSH11/DrapionVSTAR_119.py @@ -1,5 +1,23 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities, SpecialConditions + + +async def hazard_star(ctx): + if await ctx.ask_yes_no( + "Make your opponent's Active Pokémon Paralyzed and Poisoned?" + ): + defender = ctx.opponent_active() + if defender is not None: + await ctx.apply_special_condition(defender, SpecialConditions.PARALYZED) + await ctx.apply_special_condition( + defender, SpecialConditions.POISONED, poison_counters=3 + ) + + +async def big_bang_arm(ctx): + counters = (ctx.max_hp(ctx.attacker) - ctx.attacker.get_attribute(AttrID.HP, 0)) // 10 + await ctx.deal_damage(max(0, 250 - 10 * counters)) + card = PokemonCardDef( guid="ea4871db-def6-5344-9b57-4773b6046920", @@ -22,7 +40,9 @@ card = PokemonCardDef( Ability( title="Hazard Star", game_text="During your turn, you may make your opponent's Active Pok\u00e9mon Paralyzed and Poisoned. During Pok\u00e9mon Checkup, put 3 damage counters on that Pok\u00e9mon instead of 1. (You can't use more than 1 VSTAR Power in a game.)", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + vstar=True, + effect=hazard_star, ), Attack( title="Big Bang Arm", @@ -30,7 +50,7 @@ card = PokemonCardDef( cost={PokemonTypes.DARKNESS: 2, PokemonTypes.COLORLESS: 1}, damage=250, damage_operator="-", - effect=unimplemented, + effect=big_bang_arm, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/DrapionVSTAR_200.py b/spirit/game/scripts/cards/SWSH11/DrapionVSTAR_200.py index 8d3b009..57a6ebf 100644 --- a/spirit/game/scripts/cards/SWSH11/DrapionVSTAR_200.py +++ b/spirit/game/scripts/cards/SWSH11/DrapionVSTAR_200.py @@ -1,5 +1,23 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities, SpecialConditions + + +async def hazard_star(ctx): + if await ctx.ask_yes_no( + "Make your opponent's Active Pokémon Paralyzed and Poisoned?" + ): + defender = ctx.opponent_active() + if defender is not None: + await ctx.apply_special_condition(defender, SpecialConditions.PARALYZED) + await ctx.apply_special_condition( + defender, SpecialConditions.POISONED, poison_counters=3 + ) + + +async def big_bang_arm(ctx): + counters = (ctx.max_hp(ctx.attacker) - ctx.attacker.get_attribute(AttrID.HP, 0)) // 10 + await ctx.deal_damage(max(0, 250 - 10 * counters)) + card = PokemonCardDef( guid="074ceea7-aae2-59b9-ab1c-3de4efe41100", @@ -22,7 +40,9 @@ card = PokemonCardDef( Ability( title="Hazard Star", game_text="During your turn, you may make your opponent's Active Pok\u00e9mon Paralyzed and Poisoned. During Pok\u00e9mon Checkup, put 3 damage counters on that Pok\u00e9mon instead of 1. (You can't use more than 1 VSTAR Power in a game.)", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + vstar=True, + effect=hazard_star, ), Attack( title="Big Bang Arm", @@ -30,7 +50,7 @@ card = PokemonCardDef( cost={PokemonTypes.DARKNESS: 2, PokemonTypes.COLORLESS: 1}, damage=250, damage_operator="-", - effect=unimplemented, + effect=big_bang_arm, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/DrapionV_182.py b/spirit/game/scripts/cards/SWSH11/DrapionV_182.py index b38d6b2..416dfea 100644 --- a/spirit/game/scripts/cards/SWSH11/DrapionV_182.py +++ b/spirit/game/scripts/cards/SWSH11/DrapionV_182.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import WildStylePassive, dynamic_tail card = PokemonCardDef( guid="bd64b9be-7f7a-5a13-a0ae-4b88684f9b29", @@ -21,14 +22,14 @@ card = PokemonCardDef( Ability( title="Wild Style", game_text="This Pok\u00e9mon's attacks cost Colorless less for each of your opponent's Single Strike, Rapid Strike, and Fusion Strike Pok\u00e9mon in play.", - effect=unimplemented, + passive=WildStylePassive(), ), Attack( title="Dynamic Tail", game_text="This attack also does 60 damage to 1 of your Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.COLORLESS: 4}, damage=190, - effect=unimplemented, + effect=dynamic_tail, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Dustox_10.py b/spirit/game/scripts/cards/SWSH11/Dustox_10.py index 4531be3..2d3611e 100644 --- a/spirit/game/scripts/cards/SWSH11/Dustox_10.py +++ b/spirit/game/scripts/cards/SWSH11/Dustox_10.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="509d2a69-ecab-5d21-aba8-ed46f8063d2e", @@ -23,7 +24,9 @@ card = PokemonCardDef( title="Nadir Powder", game_text="Your opponent's Active Pok\u00e9mon is now Confused and Poisoned. During Pok\u00e9mon Checkup, put 8 damage counters on that Pok\u00e9mon instead of 1.", cost={PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + effect=condition_attack( + SpecialConditions.CONFUSED, SpecialConditions.POISONED, counters=8 + ), ), Attack( title="Cutting Wind", diff --git a/spirit/game/scripts/cards/SWSH11/Eelektrik_60.py b/spirit/game/scripts/cards/SWSH11/Eelektrik_60.py index 234e7bc..93d64f1 100644 --- a/spirit/game/scripts/cards/SWSH11/Eelektrik_60.py +++ b/spirit/game/scripts/cards/SWSH11/Eelektrik_60.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions + + +async def ad_hoc_shock(ctx): + """On evolve: you may flip a coin. If heads, the opponent's Active is now Paralyzed.""" + if not await ctx.ask_yes_no("Flip a coin?"): + return + results = await ctx.flip_coins(1, "Ad Hoc Shock") + if not results or not results[0]: + return + defender = ctx.defender + if defender is not None: + await ctx.apply_special_condition(defender, SpecialConditions.PARALYZED) card = PokemonCardDef( guid="9573c3da-3303-55c6-ab85-08b60a0b95dd", @@ -22,7 +34,8 @@ card = PokemonCardDef( Ability( title="Ad Hoc Shock", game_text="When you play this Pok\u00e9mon from your hand to evolve 1 of your Pok\u00e9mon during your turn, you may flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", - effect=unimplemented, + trigger=Triggers.ON_EVOLVE, + effect=ad_hoc_shock, ), Attack( title="Static Shock", diff --git a/spirit/game/scripts/cards/SWSH11/Eelektross_61.py b/spirit/game/scripts/cards/SWSH11/Eelektross_61.py index 7d54dbb..de4932c 100644 --- a/spirit/game/scripts/cards/SWSH11/Eelektross_61.py +++ b/spirit/game/scripts/cards/SWSH11/Eelektross_61.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import self_energy_discard_attack +from spirit.game.card_effects.passives_common import boost_own_next_turn card = PokemonCardDef( guid="0a7757d4-f108-5203-aac9-5687e59831a5", @@ -24,14 +26,14 @@ card = PokemonCardDef( game_text="During your next turn, this Pok\u00e9mon's attacks do 120 more damage to your opponent's Active Pok\u00e9mon (before applying Weakness and Resistance).", cost={PokemonTypes.COLORLESS: 1}, damage=10, - effect=unimplemented, + effect=boost_own_next_turn(120), ), Attack( title="Extreme Current", game_text="Discard 2 Energy from this Pok\u00e9mon.", cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 1}, damage=160, - effect=unimplemented, + effect=self_energy_discard_attack(count=2), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Electrike_54.py b/spirit/game/scripts/cards/SWSH11/Electrike_54.py index 8f010f1..252aaf4 100644 --- a/spirit/game/scripts/cards/SWSH11/Electrike_54.py +++ b/spirit/game/scripts/cards/SWSH11/Electrike_54.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions card = PokemonCardDef( guid="01409f56-e24c-58c6-a69a-cb84ec659642", @@ -28,7 +29,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.COLORLESS: 2}, damage=20, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/EnamorusV_178.py b/spirit/game/scripts/cards/SWSH11/EnamorusV_178.py index 780a7cb..1607898 100644 --- a/spirit/game/scripts/cards/SWSH11/EnamorusV_178.py +++ b/spirit/game/scripts/cards/SWSH11/EnamorusV_178.py @@ -1,5 +1,42 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.session.passives import Passive, carrier_pokemon +from spirit.game.card_effects.pokemon import is_energy_card, energy_provides_type +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.card_effects.support_common import distribute_energy + + +class GuardianOfLovePassive(Passive): + """Shields your Pokemon holding any Psychic Energy (except Enamorus V) + from the opponent's Ability effects.""" + + def blocks_ability_effects(self, target, carrier): + holder = carrier_pokemon(carrier) + if holder is None or target.owning_player_id != holder.owning_player_id: + return False + if target.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) == "EnamorusV": + return False + return any(energy_provides_type(c, PokemonTypes.PSYCHIC.value) + for c in target.children if is_energy_card(c)) + + +async def blossom_tail(ctx): + """Attach up to 2 basic Energy cards from your discard pile to your + Benched Pokemon in any way you like.""" + await ctx.deal_damage() + bench = ctx.my_bench() + if not bench: + return + energies = [c for c in ctx.discard_pile() if is_basic_energy_card(c)] + if not energies: + return + picks = await ctx.choose_cards( + energies, 2, minimum=0, + prompt="Choose up to 2 basic Energy cards to attach to your Benched Pokémon", + ) + if picks: + await distribute_energy(ctx, picks, bench) + card = PokemonCardDef( guid="f0346398-a7f0-56f1-a8f9-f1aa6a22cf88", @@ -21,14 +58,14 @@ card = PokemonCardDef( Ability( title="Guardian of Love", game_text="Prevent all effects of your opponent's Pok\u00e9mon's Abilities done to each of your Pok\u00e9mon that has any Psychic Energy attached, except any Enamorus V.", - effect=unimplemented, + passive=GuardianOfLovePassive(), ), Attack( title="Blossom Tail", game_text="Attach up to 2 basic Energy cards from your discard pile to your Benched Pok\u00e9mon in any way you like.", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 2}, damage=100, - effect=unimplemented, + effect=blossom_tail, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/EnamorusV_82.py b/spirit/game/scripts/cards/SWSH11/EnamorusV_82.py index 69b7100..358761f 100644 --- a/spirit/game/scripts/cards/SWSH11/EnamorusV_82.py +++ b/spirit/game/scripts/cards/SWSH11/EnamorusV_82.py @@ -1,5 +1,42 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.session.passives import Passive, carrier_pokemon +from spirit.game.card_effects.pokemon import is_energy_card, energy_provides_type +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.card_effects.support_common import distribute_energy + + +class GuardianOfLovePassive(Passive): + """Shields your Pokemon holding any Psychic Energy (except Enamorus V) + from the opponent's Ability effects.""" + + def blocks_ability_effects(self, target, carrier): + holder = carrier_pokemon(carrier) + if holder is None or target.owning_player_id != holder.owning_player_id: + return False + if target.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) == "EnamorusV": + return False + return any(energy_provides_type(c, PokemonTypes.PSYCHIC.value) + for c in target.children if is_energy_card(c)) + + +async def blossom_tail(ctx): + """Attach up to 2 basic Energy cards from your discard pile to your + Benched Pokemon in any way you like.""" + await ctx.deal_damage() + bench = ctx.my_bench() + if not bench: + return + energies = [c for c in ctx.discard_pile() if is_basic_energy_card(c)] + if not energies: + return + picks = await ctx.choose_cards( + energies, 2, minimum=0, + prompt="Choose up to 2 basic Energy cards to attach to your Benched Pokémon", + ) + if picks: + await distribute_energy(ctx, picks, bench) + card = PokemonCardDef( guid="934414ce-ab59-5674-a6d4-72374678519c", @@ -21,14 +58,14 @@ card = PokemonCardDef( Ability( title="Guardian of Love", game_text="Prevent all effects of your opponent's Pok\u00e9mon's Abilities done to each of your Pok\u00e9mon that has any Psychic Energy attached, except any Enamorus V.", - effect=unimplemented, + passive=GuardianOfLovePassive(), ), Attack( title="Blossom Tail", game_text="Attach up to 2 basic Energy cards from your discard pile to your Benched Pok\u00e9mon in any way you like.", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 2}, damage=100, - effect=unimplemented, + effect=blossom_tail, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Falinks_110.py b/spirit/game/scripts/cards/SWSH11/Falinks_110.py index 2d789c5..f75da63 100644 --- a/spirit/game/scripts/cards/SWSH11/Falinks_110.py +++ b/spirit/game/scripts/cards/SWSH11/Falinks_110.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import defender_is_v + + +async def fighting_headbutt(ctx): + """10, +50 vs a Pokémon V; not affected by Weakness.""" + bonus = 50 if defender_is_v(ctx) else 0 + await ctx.deal_damage(10 + bonus, ignore_weakness=True) card = PokemonCardDef( guid="d287bd67-a29a-5307-baee-39a1f3f27da7", @@ -24,7 +31,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1}, damage=10, damage_operator="+", - effect=unimplemented, + effect=fighting_headbutt, ), Attack( title="Jet Headbutt", diff --git a/spirit/game/scripts/cards/SWSH11/Fantina_157.py b/spirit/game/scripts/cards/SWSH11/Fantina_157.py index 3aaf71b..8ae3056 100644 --- a/spirit/game/scripts/cards/SWSH11/Fantina_157.py +++ b/spirit/game/scripts/cards/SWSH11/Fantina_157.py @@ -1,5 +1,23 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef, is_pokemon_v from spirit.game.attributes import Rarities +from spirit.game.card_effects.passives_common import takes_less_passive + + +def _fantina_condition(board, player_id): + lost = board.find_player_area(player_id, "lostZone") + return bool(lost) and len(lost.children) >= 10 + + +async def fantina(ctx): + """During the opponent's next turn, all of your Pokemon take 120 less + damage from attacks from their Pokemon V (after W/R).""" + shield = takes_less_passive( + 120, protects="team", + attacker_pred=lambda a: is_pokemon_v(a.archetype_id), + ) + for pokemon in ctx.my_pokemon_in_play(): + ctx.add_passive_through_opponents_turn(pokemon, shield) + card = SupporterCardDef( guid="b53c40cc-5bd0-5b2c-9d60-8f6e9a0988fe", @@ -11,5 +29,6 @@ card = SupporterCardDef( collector_number=157, set_code="SWSH11", rarity=Rarities.Uncommon, - effect=unimplemented + effect=fantina, + condition=_fantina_condition, ) diff --git a/spirit/game/scripts/cards/SWSH11/Fantina_191.py b/spirit/game/scripts/cards/SWSH11/Fantina_191.py index 02c37ce..e7bad4a 100644 --- a/spirit/game/scripts/cards/SWSH11/Fantina_191.py +++ b/spirit/game/scripts/cards/SWSH11/Fantina_191.py @@ -1,5 +1,23 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef, is_pokemon_v from spirit.game.attributes import Rarities +from spirit.game.card_effects.passives_common import takes_less_passive + + +def _fantina_condition(board, player_id): + lost = board.find_player_area(player_id, "lostZone") + return bool(lost) and len(lost.children) >= 10 + + +async def fantina(ctx): + """During the opponent's next turn, all of your Pokemon take 120 less + damage from attacks from their Pokemon V (after W/R).""" + shield = takes_less_passive( + 120, protects="team", + attacker_pred=lambda a: is_pokemon_v(a.archetype_id), + ) + for pokemon in ctx.my_pokemon_in_play(): + ctx.add_passive_through_opponents_turn(pokemon, shield) + card = SupporterCardDef( guid="7d04358d-41f0-54ab-9f45-5acce626c763", @@ -11,5 +29,6 @@ card = SupporterCardDef( collector_number=191, set_code="SWSH11", rarity=Rarities.RareUltra, - effect=unimplemented + effect=fantina, + condition=_fantina_condition, ) diff --git a/spirit/game/scripts/cards/SWSH11/Fantina_206.py b/spirit/game/scripts/cards/SWSH11/Fantina_206.py index c95258d..5b3bd4d 100644 --- a/spirit/game/scripts/cards/SWSH11/Fantina_206.py +++ b/spirit/game/scripts/cards/SWSH11/Fantina_206.py @@ -1,5 +1,23 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef, is_pokemon_v from spirit.game.attributes import Rarities +from spirit.game.card_effects.passives_common import takes_less_passive + + +def _fantina_condition(board, player_id): + lost = board.find_player_area(player_id, "lostZone") + return bool(lost) and len(lost.children) >= 10 + + +async def fantina(ctx): + """During the opponent's next turn, all of your Pokemon take 120 less + damage from attacks from their Pokemon V (after W/R).""" + shield = takes_less_passive( + 120, protects="team", + attacker_pred=lambda a: is_pokemon_v(a.archetype_id), + ) + for pokemon in ctx.my_pokemon_in_play(): + ctx.add_passive_through_opponents_turn(pokemon, shield) + card = SupporterCardDef( guid="edfc2e5f-57e2-5db4-8a55-722ee190c1ad", @@ -11,5 +29,6 @@ card = SupporterCardDef( collector_number=206, set_code="SWSH11", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=fantina, + condition=_fantina_condition, ) diff --git a/spirit/game/scripts/cards/SWSH11/Finneon_40.py b/spirit/game/scripts/cards/SWSH11/Finneon_40.py index 3f7f5d3..84bce0a 100644 --- a/spirit/game/scripts/cards/SWSH11/Finneon_40.py +++ b/spirit/game/scripts/cards/SWSH11/Finneon_40.py @@ -1,5 +1,43 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import AttrID, CardType, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import has_attack_titled + +_has_swim_freely = has_attack_titled("Swim Freely") + + +def _is_water_energy_card(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return ( + card.get_attribute(AttrID.CARD_TYPE) == CardType.ENERGY.value + and PokemonTypes.WATER.value in types + ) + + +def _oceanic_condition(board, player_id, pokemon): + hand = board.find_player_area(player_id, "hand") + if not hand or not any(_is_water_energy_card(c) for c in hand.children): + return False + return any(_has_swim_freely(p) for p in board.pokemon_in_play(player_id)) + + +async def oceanic_accompaniment(ctx): + hand_energies = [c for c in ctx.hand() if _is_water_energy_card(c)] + if not hand_energies: + return + targets = [p for p in ctx.my_pokemon_in_play() if _has_swim_freely(p)] + if not targets: + return + picked = await ctx.choose_cards( + hand_energies, 1, prompt="Choose a Water Energy card to attach" + ) + if not picked: + return + target = await ctx.choose_pokemon( + targets, "Choose a Pokémon with Swim Freely to attach the Energy to" + ) + if target is not None: + await ctx.attach_energy(picked[0], target) + card = PokemonCardDef( guid="fc1d1d60-63aa-54de-af3a-fe9775c80aa4", @@ -21,7 +59,9 @@ card = PokemonCardDef( Ability( title="Oceanic Accompaniment", game_text="As often as you like during your turn, you may attach a Water Energy card from your hand to 1 of your Pok\u00e9mon that has the Swim Freely attack.", - effect=unimplemented, + activation=Activations.UNLIMITED, + condition=_oceanic_condition, + effect=oceanic_accompaniment, ), Attack( title="Water Gun", diff --git a/spirit/game/scripts/cards/SWSH11/GalarianPerrserkerV_129.py b/spirit/game/scripts/cards/SWSH11/GalarianPerrserkerV_129.py index a20e83d..3a5eefc 100644 --- a/spirit/game/scripts/cards/SWSH11/GalarianPerrserkerV_129.py +++ b/spirit/game/scripts/cards/SWSH11/GalarianPerrserkerV_129.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import draw_attack +from spirit.game.card_effects.attacks_common import damage_per, count_hand card = PokemonCardDef( guid="9e12d520-10c7-5a2e-b1be-ae6e0e5882ba", @@ -23,7 +25,7 @@ card = PokemonCardDef( title="Feelin' Fine", game_text="Draw 3 cards.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=draw_attack(3), ), Attack( title="Treasure Rush", @@ -31,7 +33,7 @@ card = PokemonCardDef( cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 1}, damage=20, damage_operator="x", - effect=unimplemented, + effect=damage_per(count_hand("mine"), 20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/GalarianPerrserkerV_183.py b/spirit/game/scripts/cards/SWSH11/GalarianPerrserkerV_183.py index 2749dac..4c9f01f 100644 --- a/spirit/game/scripts/cards/SWSH11/GalarianPerrserkerV_183.py +++ b/spirit/game/scripts/cards/SWSH11/GalarianPerrserkerV_183.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import draw_attack +from spirit.game.card_effects.attacks_common import damage_per, count_hand card = PokemonCardDef( guid="7e19ba01-e7b5-5404-a007-27609ce70773", @@ -23,7 +25,7 @@ card = PokemonCardDef( title="Feelin' Fine", game_text="Draw 3 cards.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=draw_attack(3), ), Attack( title="Treasure Rush", @@ -31,7 +33,7 @@ card = PokemonCardDef( cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 1}, damage=20, damage_operator="x", - effect=unimplemented, + effect=damage_per(count_hand("mine"), 20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/GalarianPerrserkerV_184.py b/spirit/game/scripts/cards/SWSH11/GalarianPerrserkerV_184.py index 228114d..5ee7f57 100644 --- a/spirit/game/scripts/cards/SWSH11/GalarianPerrserkerV_184.py +++ b/spirit/game/scripts/cards/SWSH11/GalarianPerrserkerV_184.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import draw_attack +from spirit.game.card_effects.attacks_common import damage_per, count_hand card = PokemonCardDef( guid="4ac7c095-ccea-589b-922b-3c1bd348d21d", @@ -23,7 +25,7 @@ card = PokemonCardDef( title="Feelin' Fine", game_text="Draw 3 cards.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=draw_attack(3), ), Attack( title="Treasure Rush", @@ -31,7 +33,7 @@ card = PokemonCardDef( cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 1}, damage=20, damage_operator="x", - effect=unimplemented, + effect=damage_per(count_hand("mine"), 20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/GalarianStunfisk_127.py b/spirit/game/scripts/cards/SWSH11/GalarianStunfisk_127.py index e22b0d4..ce2d2be 100644 --- a/spirit/game/scripts/cards/SWSH11/GalarianStunfisk_127.py +++ b/spirit/game/scripts/cards/SWSH11/GalarianStunfisk_127.py @@ -1,6 +1,23 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def field_trap(ctx): + await ctx.deal_damage() + stadium = ctx.stadium_in_play() + if stadium is None or stadium.owning_player_id != ctx.opponent_id: + return + discarded = await ctx.discard_stadium() + if discarded is None: + return + target = ctx.opponent_active() + if target is None or ctx.effects_blocked(target): + return + await ctx.discard_energy_from( + target, 2, prompt="Choose 2 Energy to discard from the Defending Pokémon" + ) + + card = PokemonCardDef( guid="bafdf6dd-960c-5255-b2a8-8fdf3249a08a", key="SWSH11", @@ -24,7 +41,7 @@ card = PokemonCardDef( game_text="If your opponent has a Stadium in play, discard it. If you discarded a Stadium in this way, discard 2 Energy from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.METAL: 1}, damage=20, - effect=unimplemented, + effect=field_trap, ), Attack( title="Tackle", diff --git a/spirit/game/scripts/cards/SWSH11/GalladeV_181.py b/spirit/game/scripts/cards/SWSH11/GalladeV_181.py index 97bbfbe..29ae82e 100644 --- a/spirit/game/scripts/cards/SWSH11/GalladeV_181.py +++ b/spirit/game/scripts/cards/SWSH11/GalladeV_181.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_prizes_taken + + +async def buster_swing(ctx): + await ctx.deal_damage(ignore_resistance=True) + card = PokemonCardDef( guid="e799644b-fbf3-55fe-8603-507b25240f04", @@ -24,14 +30,14 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_prizes_taken("mine"), 50, base=20), ), Attack( title="Buster Swing", game_text="This attack's damage isn't affected by Resistance.", cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=130, - effect=unimplemented, + effect=buster_swing, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Gastly_64.py b/spirit/game/scripts/cards/SWSH11/Gastly_64.py index 130d0d5..d45baa2 100644 --- a/spirit/game/scripts/cards/SWSH11/Gastly_64.py +++ b/spirit/game/scripts/cards/SWSH11/Gastly_64.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import place_counters card = PokemonCardDef( guid="396ab052-932d-550c-a13c-dcf04c204e45", @@ -23,7 +24,7 @@ card = PokemonCardDef( title="Furtive Drop", game_text="Put 1 damage counter on your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=place_counters(1, "opponent_active"), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Gastrodon_102.py b/spirit/game/scripts/cards/SWSH11/Gastrodon_102.py index 43d8ca5..0003c4f 100644 --- a/spirit/game/scripts/cards/SWSH11/Gastrodon_102.py +++ b/spirit/game/scripts/cards/SWSH11/Gastrodon_102.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import heal_attack +from spirit.game.card_effects.attacks_common import spread_damage card = PokemonCardDef( guid="9b7a22e4-8e10-5f56-bd9a-5fb43469d2a1", @@ -23,14 +25,14 @@ card = PokemonCardDef( title="Recover", game_text="Discard an Energy from this Pok\u00e9mon and heal all damage from it.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=heal_attack(all_damage=True, discard_energy=1, target="self"), ), Attack( title="Earthquake", game_text="This attack also does 20 damage to each of your Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=170, - effect=unimplemented, + effect=spread_damage(20, side="mine", also_base=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Gengar_66.py b/spirit/game/scripts/cards/SWSH11/Gengar_66.py index c394cd0..d224df1 100644 --- a/spirit/game/scripts/cards/SWSH11/Gengar_66.py +++ b/spirit/game/scripts/cards/SWSH11/Gengar_66.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import place_counters, count_bench +from spirit.game.card_effects.support_common import requires_bench_space + + +async def netherworld_gate(ctx): + """Once per turn, from the discard pile: you may put this Pokemon onto + your Bench. If you do, put 3 damage counters on it.""" + if not await ctx.ask_yes_no("Put this Pokémon onto your Bench?"): + return + if await ctx.bench_pokemon(ctx.source): + await ctx.deal_damage(30, target=ctx.source, apply_modifiers=False, + as_counters=True) + card = PokemonCardDef( guid="a1a37e5d-6891-558e-8c21-b56e8acf183a", @@ -23,13 +36,18 @@ card = PokemonCardDef( Ability( title="Netherworld Gate", game_text="Once during your turn, if this Pok\u00e9mon is in your discard pile, you may put it onto your Bench. If you do, put 3 damage counters on this Pok\u00e9mon.", - effect=unimplemented, + usable_from="discard", + activation=Activations.ONCE_PER_TURN, + condition=requires_bench_space(1), + effect=netherworld_gate, ), Attack( title="Screaming Circle", game_text="Put 2 damage counters on your opponent's Active Pok\u00e9mon for each of your opponent's Benched Pok\u00e9mon.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=place_counters( + lambda ctx: 2 * count_bench("opponent")(ctx), "opponent_active" + ), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/GiratinaVSTAR_131.py b/spirit/game/scripts/cards/SWSH11/GiratinaVSTAR_131.py index 23d0279..70e278d 100644 --- a/spirit/game/scripts/cards/SWSH11/GiratinaVSTAR_131.py +++ b/spirit/game/scripts/cards/SWSH11/GiratinaVSTAR_131.py @@ -1,5 +1,30 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import lost_mine_condition + + +async def lost_impact(ctx): + """280. Put 2 Energy attached to your Pokemon in the Lost Zone.""" + await ctx.deal_damage() + energies = [] + for pokemon in ctx.my_pokemon_in_play(): + energies.extend(ctx.attached_energies(pokemon)) + if not energies: + return + picks = await ctx.choose_cards( + energies, 2, + prompt="Choose 2 Energy to put in the Lost Zone", + ) + if picks: + await ctx.move_to_lost_zone(picks) + + +async def star_requiem(ctx): + """VSTAR Power: your opponent's Active Pokemon is Knocked Out.""" + target = ctx.defender + if target is not None: + await ctx.knock_out(target) + card = PokemonCardDef( guid="2448f7fd-1959-5e6d-af1c-714542a5835c", @@ -20,16 +45,18 @@ card = PokemonCardDef( abilities=[ Attack( title="Lost Impact", - game_text="Put 2 Energy attached to your Pok\u00e9mon in the Lost Zone.", + game_text="Put 2 Energy attached to your Pokémon in the Lost Zone.", cost={PokemonTypes.GRASS: 1, PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=280, - effect=unimplemented, + effect=lost_impact, ), Attack( title="Star Requiem", - game_text="You can use this attack only if you have 10 or more cards in the Lost Zone. Your opponent's Active Pok\u00e9mon is Knocked Out. (You can't use more than 1 VSTAR Power in a game.)", + game_text="You can use this attack only if you have 10 or more cards in the Lost Zone. Your opponent's Active Pokémon is Knocked Out. (You can't use more than 1 VSTAR Power in a game.)", cost={PokemonTypes.GRASS: 1, PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + vstar=True, + condition=lost_mine_condition, + effect=star_requiem, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH11/GiratinaVSTAR_201.py b/spirit/game/scripts/cards/SWSH11/GiratinaVSTAR_201.py index edc3bd8..e5788c9 100644 --- a/spirit/game/scripts/cards/SWSH11/GiratinaVSTAR_201.py +++ b/spirit/game/scripts/cards/SWSH11/GiratinaVSTAR_201.py @@ -1,5 +1,30 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import lost_mine_condition + + +async def lost_impact(ctx): + """280. Put 2 Energy attached to your Pokemon in the Lost Zone.""" + await ctx.deal_damage() + energies = [] + for pokemon in ctx.my_pokemon_in_play(): + energies.extend(ctx.attached_energies(pokemon)) + if not energies: + return + picks = await ctx.choose_cards( + energies, 2, + prompt="Choose 2 Energy to put in the Lost Zone", + ) + if picks: + await ctx.move_to_lost_zone(picks) + + +async def star_requiem(ctx): + """VSTAR Power: your opponent's Active Pokemon is Knocked Out.""" + target = ctx.defender + if target is not None: + await ctx.knock_out(target) + card = PokemonCardDef( guid="c2d39670-1449-5aef-8da8-b55dd67c7e2a", @@ -20,16 +45,18 @@ card = PokemonCardDef( abilities=[ Attack( title="Lost Impact", - game_text="Put 2 Energy attached to your Pok\u00e9mon in the Lost Zone.", + game_text="Put 2 Energy attached to your Pokémon in the Lost Zone.", cost={PokemonTypes.GRASS: 1, PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=280, - effect=unimplemented, + effect=lost_impact, ), Attack( title="Star Requiem", - game_text="You can use this attack only if you have 10 or more cards in the Lost Zone. Your opponent's Active Pok\u00e9mon is Knocked Out. (You can't use more than 1 VSTAR Power in a game.)", + game_text="You can use this attack only if you have 10 or more cards in the Lost Zone. Your opponent's Active Pokémon is Knocked Out. (You can't use more than 1 VSTAR Power in a game.)", cost={PokemonTypes.GRASS: 1, PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + vstar=True, + condition=lost_mine_condition, + effect=star_requiem, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH11/GiratinaVSTAR_212.py b/spirit/game/scripts/cards/SWSH11/GiratinaVSTAR_212.py index 6c1d2a7..1fca1a8 100644 --- a/spirit/game/scripts/cards/SWSH11/GiratinaVSTAR_212.py +++ b/spirit/game/scripts/cards/SWSH11/GiratinaVSTAR_212.py @@ -1,5 +1,30 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import lost_mine_condition + + +async def lost_impact(ctx): + """280. Put 2 Energy attached to your Pokemon in the Lost Zone.""" + await ctx.deal_damage() + energies = [] + for pokemon in ctx.my_pokemon_in_play(): + energies.extend(ctx.attached_energies(pokemon)) + if not energies: + return + picks = await ctx.choose_cards( + energies, 2, + prompt="Choose 2 Energy to put in the Lost Zone", + ) + if picks: + await ctx.move_to_lost_zone(picks) + + +async def star_requiem(ctx): + """VSTAR Power: your opponent's Active Pokemon is Knocked Out.""" + target = ctx.defender + if target is not None: + await ctx.knock_out(target) + card = PokemonCardDef( guid="1c7f1469-e68a-524d-9c09-4c39ed86a34b", @@ -20,16 +45,18 @@ card = PokemonCardDef( abilities=[ Attack( title="Lost Impact", - game_text="Put 2 Energy attached to your Pok\u00e9mon in the Lost Zone.", + game_text="Put 2 Energy attached to your Pokémon in the Lost Zone.", cost={PokemonTypes.GRASS: 1, PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=280, - effect=unimplemented, + effect=lost_impact, ), Attack( title="Star Requiem", - game_text="You can use this attack only if you have 10 or more cards in the Lost Zone. Your opponent's Active Pok\u00e9mon is Knocked Out. (You can't use more than 1 VSTAR Power in a game.)", + game_text="You can use this attack only if you have 10 or more cards in the Lost Zone. Your opponent's Active Pokémon is Knocked Out. (You can't use more than 1 VSTAR Power in a game.)", cost={PokemonTypes.GRASS: 1, PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + vstar=True, + condition=lost_mine_condition, + effect=star_requiem, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH11/GiratinaV_130.py b/spirit/game/scripts/cards/SWSH11/GiratinaV_130.py index 5b3e767..ac0857d 100644 --- a/spirit/game/scripts/cards/SWSH11/GiratinaV_130.py +++ b/spirit/game/scripts/cards/SWSH11/GiratinaV_130.py @@ -1,6 +1,27 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def abyss_seeking(ctx): + """Look at the top 4 cards of your deck. Put 2 into your hand, the rest in the Lost Zone.""" + top = ctx.deck_top(4) + if not top: + return + picks = await ctx.choose_cards( + top, 2, minimum=2, + prompt="Choose 2 cards to put into your hand; the rest go to the Lost Zone.", + ) + await ctx.put_in_hand(picks, reveal=False) + rest = [c for c in top if c not in picks] + if rest: + await ctx.move_to_lost_zone(rest) + + +async def shred(ctx): + """160. This attack's damage isn't affected by any effects on the opponent's Active Pokemon.""" + await ctx.deal_damage(160, ignore_target_effects=True) + + card = PokemonCardDef( guid="397ea489-34cc-5382-bad4-2122797e32bf", key="SWSH11", @@ -21,14 +42,14 @@ card = PokemonCardDef( title="Abyss Seeking", game_text="Look at the top 4 cards of your deck and put 2 of them into your hand. Put the other cards in the Lost Zone.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=abyss_seeking, ), Attack( title="Shred", - game_text="This attack's damage isn't affected by any effects on your opponent's Active Pok\u00e9mon.", + game_text="This attack's damage isn't affected by any effects on your opponent's Active Pokémon.", cost={PokemonTypes.GRASS: 1, PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=160, - effect=unimplemented, + effect=shred, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH11/GiratinaV_185.py b/spirit/game/scripts/cards/SWSH11/GiratinaV_185.py index 2cf21d6..9dad446 100644 --- a/spirit/game/scripts/cards/SWSH11/GiratinaV_185.py +++ b/spirit/game/scripts/cards/SWSH11/GiratinaV_185.py @@ -1,6 +1,27 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def abyss_seeking(ctx): + """Look at the top 4 cards of your deck. Put 2 into your hand, the rest in the Lost Zone.""" + top = ctx.deck_top(4) + if not top: + return + picks = await ctx.choose_cards( + top, 2, minimum=2, + prompt="Choose 2 cards to put into your hand; the rest go to the Lost Zone.", + ) + await ctx.put_in_hand(picks, reveal=False) + rest = [c for c in top if c not in picks] + if rest: + await ctx.move_to_lost_zone(rest) + + +async def shred(ctx): + """160. This attack's damage isn't affected by any effects on the opponent's Active Pokemon.""" + await ctx.deal_damage(160, ignore_target_effects=True) + + card = PokemonCardDef( guid="c31fabb9-f820-59da-b811-59650a825893", key="SWSH11", @@ -21,14 +42,14 @@ card = PokemonCardDef( title="Abyss Seeking", game_text="Look at the top 4 cards of your deck and put 2 of them into your hand. Put the other cards in the Lost Zone.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=abyss_seeking, ), Attack( title="Shred", - game_text="This attack's damage isn't affected by any effects on your opponent's Active Pok\u00e9mon.", + game_text="This attack's damage isn't affected by any effects on your opponent's Active Pokémon.", cost={PokemonTypes.GRASS: 1, PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=160, - effect=unimplemented, + effect=shred, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH11/GiratinaV_186.py b/spirit/game/scripts/cards/SWSH11/GiratinaV_186.py index 16536e1..3ca8ceb 100644 --- a/spirit/game/scripts/cards/SWSH11/GiratinaV_186.py +++ b/spirit/game/scripts/cards/SWSH11/GiratinaV_186.py @@ -1,6 +1,27 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def abyss_seeking(ctx): + """Look at the top 4 cards of your deck. Put 2 into your hand, the rest in the Lost Zone.""" + top = ctx.deck_top(4) + if not top: + return + picks = await ctx.choose_cards( + top, 2, minimum=2, + prompt="Choose 2 cards to put into your hand; the rest go to the Lost Zone.", + ) + await ctx.put_in_hand(picks, reveal=False) + rest = [c for c in top if c not in picks] + if rest: + await ctx.move_to_lost_zone(rest) + + +async def shred(ctx): + """160. This attack's damage isn't affected by any effects on the opponent's Active Pokemon.""" + await ctx.deal_damage(160, ignore_target_effects=True) + + card = PokemonCardDef( guid="fa0abf4f-e929-5212-a723-9ec6f00f7f65", key="SWSH11", @@ -21,14 +42,14 @@ card = PokemonCardDef( title="Abyss Seeking", game_text="Look at the top 4 cards of your deck and put 2 of them into your hand. Put the other cards in the Lost Zone.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=abyss_seeking, ), Attack( title="Shred", - game_text="This attack's damage isn't affected by any effects on your opponent's Active Pok\u00e9mon.", + game_text="This attack's damage isn't affected by any effects on your opponent's Active Pokémon.", cost={PokemonTypes.GRASS: 1, PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=160, - effect=unimplemented, + effect=shred, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH11/Glastrier_51.py b/spirit/game/scripts/cards/SWSH11/Glastrier_51.py index 1f8bd6c..c51ac77 100644 --- a/spirit/game/scripts/cards/SWSH11/Glastrier_51.py +++ b/spirit/game/scripts/cards/SWSH11/Glastrier_51.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import lock_defender_attacks, recoil_attack +from spirit.game.session.effects import is_basic_pokemon + + +async def freeze_down(ctx): + """40. If the Defending Pokemon is a Basic Pokemon, it can't attack + during your opponent's next turn.""" + await ctx.deal_damage() + defender = ctx.defender + if defender is not None and is_basic_pokemon(defender): + lock_defender_attacks(ctx) + card = PokemonCardDef( guid="ae268100-fbfc-59a1-b494-8cec412af018", @@ -23,14 +35,14 @@ card = PokemonCardDef( game_text="If the Defending Pok\u00e9mon is a Basic Pok\u00e9mon, it can't attack during your opponent's next turn.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 1}, damage=40, - effect=unimplemented, + effect=freeze_down, ), Attack( title="Wild Tackle", game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, damage=130, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Gligar_95.py b/spirit/game/scripts/cards/SWSH11/Gligar_95.py index d65a6ac..900d874 100644 --- a/spirit/game/scripts/cards/SWSH11/Gligar_95.py +++ b/spirit/game/scripts/cards/SWSH11/Gligar_95.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="885b6a8a-8352-5dda-adc4-a9cc2b71e44e", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1}, damage=10, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=4, per_heads=10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Gliscor_96.py b/spirit/game/scripts/cards/SWSH11/Gliscor_96.py index 26b343c..6c87af9 100644 --- a/spirit/game/scripts/cards/SWSH11/Gliscor_96.py +++ b/spirit/game/scripts/cards/SWSH11/Gliscor_96.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions + + +async def hurricane_shock(ctx): + """Flip 4 coins. 50 damage for each heads; if at least 2 are heads, the + opponent's Active Pokémon is now Paralyzed.""" + results = await ctx.flip_coins(4, "Hurricane Shock") + heads = sum(1 for r in results if r) + if heads > 0: + await ctx.deal_damage(heads * 50) + if heads >= 2: + await ctx.apply_special_condition(ctx.defender, SpecialConditions.PARALYZED) + card = PokemonCardDef( guid="6299830c-db10-5d94-b580-5c13937f4793", @@ -25,7 +37,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 2}, damage=50, damage_operator="x", - effect=unimplemented, + effect=hurricane_shock, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Gloom_2.py b/spirit/game/scripts/cards/SWSH11/Gloom_2.py index 049b94a..a406c60 100644 --- a/spirit/game/scripts/cards/SWSH11/Gloom_2.py +++ b/spirit/game/scripts/cards/SWSH11/Gloom_2.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import heal_attack card = PokemonCardDef( guid="218d32f8-fb2c-547a-8909-f582cf6f5c9d", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Heal 30 damage from this Pok\u00e9mon.", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=heal_attack(30, target="self"), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Greedent_151.py b/spirit/game/scripts/cards/SWSH11/Greedent_151.py index 3cef0db..3565b52 100644 --- a/spirit/game/scripts/cards/SWSH11/Greedent_151.py +++ b/spirit/game/scripts/cards/SWSH11/Greedent_151.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import draw_attack + + +async def spill_out(ctx): + """60+150. Discard your hand. If you discarded 5 or more cards this way, +150 damage.""" + hand = ctx.hand() + count = len(hand) + await ctx.discard_cards(hand) + await ctx.deal_damage(60 + (150 if count >= 5 else 0)) + card = PokemonCardDef( guid="15fe97d0-39ab-543a-8dd0-71f704425543", @@ -23,7 +33,7 @@ card = PokemonCardDef( title="Collect", game_text="Draw 2 cards.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=draw_attack(2), ), Attack( title="Spill Out", @@ -31,7 +41,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=60, damage_operator="+", - effect=unimplemented, + effect=spill_out, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Hariyama_98.py b/spirit/game/scripts/cards/SWSH11/Hariyama_98.py index be886dc..d59c818 100644 --- a/spirit/game/scripts/cards/SWSH11/Hariyama_98.py +++ b/spirit/game/scripts/cards/SWSH11/Hariyama_98.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack + + +async def muscular_slap(ctx): + """100. This attack's damage isn't affected by Resistance.""" + await ctx.deal_damage(ignore_resistance=True) card = PokemonCardDef( guid="26faf680-ebdf-59a1-b575-0809607a4114", @@ -24,14 +30,14 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 1}, damage=40, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), Attack( title="Muscular Slap", game_text="This attack's damage isn't affected by Resistance.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 2}, damage=100, - effect=unimplemented, + effect=muscular_slap, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Haunter_65.py b/spirit/game/scripts/cards/SWSH11/Haunter_65.py index d89bccf..bf4dab4 100644 --- a/spirit/game/scripts/cards/SWSH11/Haunter_65.py +++ b/spirit/game/scripts/cards/SWSH11/Haunter_65.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import place_counters +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -24,7 +25,7 @@ card = PokemonCardDef( title="Cursed Drop", game_text="Put 3 damage counters on your opponent's Pok\u00e9mon in any way you like.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=place_counters(3, "choose_any_opponent"), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/HisuianArcanine_84.py b/spirit/game/scripts/cards/SWSH11/HisuianArcanine_84.py index 66dd773..7cbace4 100644 --- a/spirit/game/scripts/cards/SWSH11/HisuianArcanine_84.py +++ b/spirit/game/scripts/cards/SWSH11/HisuianArcanine_84.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if card = PokemonCardDef( guid="4ff36885-2d95-57cd-a596-e9890ab209d3", @@ -25,7 +26,7 @@ card = PokemonCardDef( cost={}, damage=10, damage_operator="+", - effect=unimplemented, + effect=bonus_if(lambda ctx: ctx.hand_size() == 0, 150, base=10), ), Attack( title="Sharp Fang", diff --git a/spirit/game/scripts/cards/SWSH11/HisuianBasculegion_45.py b/spirit/game/scripts/cards/SWSH11/HisuianBasculegion_45.py index 581ec37..8879d5f 100644 --- a/spirit/game/scripts/cards/SWSH11/HisuianBasculegion_45.py +++ b/spirit/game/scripts/cards/SWSH11/HisuianBasculegion_45.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.card_effects.attacks_common import self_energy_discard_attack + + +async def upstream_spirits(ctx): + """This attack does 20 damage for each basic Energy card in your discard + pile. Then, shuffle those cards into your deck.""" + basics = [c for c in ctx.discard_pile() if is_basic_energy_card(c)] + await ctx.deal_damage(20 * len(basics)) + if basics: + await ctx.shuffle_into_deck(basics) + card = PokemonCardDef( guid="b99dc7d4-f6ac-53b0-be4b-7ae2d8fd8b99", @@ -25,14 +37,14 @@ card = PokemonCardDef( cost={}, damage=20, damage_operator="x", - effect=unimplemented, + effect=upstream_spirits, ), Attack( title="Water Shot", game_text="Discard an Energy from this Pok\u00e9mon.", cost={PokemonTypes.WATER: 1}, damage=70, - effect=unimplemented, + effect=self_energy_discard_attack(count=1, before_damage=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/HisuianBasculin_44.py b/spirit/game/scripts/cards/SWSH11/HisuianBasculin_44.py index 2cf77fc..d280c3f 100644 --- a/spirit/game/scripts/cards/SWSH11/HisuianBasculin_44.py +++ b/spirit/game/scripts/cards/SWSH11/HisuianBasculin_44.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_or_nothing card = PokemonCardDef( guid="9e6c47f3-403b-5067-8232-3cb5a58802a7", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Flip a coin. If tails, this attack does nothing.", cost={}, damage=40, - effect=unimplemented, + effect=flip_or_nothing(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/HisuianElectrodeV_172.py b/spirit/game/scripts/cards/SWSH11/HisuianElectrodeV_172.py index a36b64a..fc763c2 100644 --- a/spirit/game/scripts/cards/SWSH11/HisuianElectrodeV_172.py +++ b/spirit/game/scripts/cards/SWSH11/HisuianElectrodeV_172.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, self_energy_discard_attack + + +def _condition_count(ctx): + conditions = ctx.attacker.get_attribute(AttrID.SPECIAL_CONDITIONS) or [] + return len(conditions) + card = PokemonCardDef( guid="3cd2388b-cff7-5c31-82f0-3518bced8c4f", @@ -24,14 +31,14 @@ card = PokemonCardDef( cost={}, damage=100, damage_operator="x", - effect=unimplemented, + effect=damage_per(_condition_count, 100), ), Attack( title="Solar Shot", game_text="Discard all Energy from this Pok\u00e9mon.", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + effect=self_energy_discard_attack(all_energy=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/HisuianGoodraVSTAR_136.py b/spirit/game/scripts/cards/SWSH11/HisuianGoodraVSTAR_136.py index e789b00..a2d7688 100644 --- a/spirit/game/scripts/cards/SWSH11/HisuianGoodraVSTAR_136.py +++ b/spirit/game/scripts/cards/SWSH11/HisuianGoodraVSTAR_136.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.passives_common import protect_next_turn +from spirit.game.session.passives import effective_max_hp + + +def _moisture_star_condition(board, player_id, pokemon): + return pokemon.get_attribute(AttrID.HP, 0) < effective_max_hp(board, pokemon) + + +async def moisture_star(ctx): + if await ctx.ask_yes_no("Heal all damage from this Pokémon?"): + await ctx.heal(9999, target=ctx.source) + card = PokemonCardDef( guid="51288336-0dab-5ef6-86b5-d8d77f56ec12", @@ -21,14 +33,17 @@ card = PokemonCardDef( Ability( title="Moisture Star", game_text="During your turn, you may heal all damage from this Pok\u00e9mon. (You can't use more than 1 VSTAR Power in a game.)", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + vstar=True, + condition=_moisture_star_condition, + effect=moisture_star, ), Attack( title="Rolling Iron", game_text="During your opponent's next turn, this Pok\u00e9mon takes 80 less damage from attacks (after applying Weakness and Resistance).", cost={PokemonTypes.WATER: 1, PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 1}, damage=200, - effect=unimplemented, + effect=protect_next_turn(reduce=80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/HisuianGoodraVSTAR_202.py b/spirit/game/scripts/cards/SWSH11/HisuianGoodraVSTAR_202.py index c2ad2cf..b3dcafe 100644 --- a/spirit/game/scripts/cards/SWSH11/HisuianGoodraVSTAR_202.py +++ b/spirit/game/scripts/cards/SWSH11/HisuianGoodraVSTAR_202.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.passives_common import protect_next_turn +from spirit.game.session.passives import effective_max_hp + + +def _moisture_star_condition(board, player_id, pokemon): + return pokemon.get_attribute(AttrID.HP, 0) < effective_max_hp(board, pokemon) + + +async def moisture_star(ctx): + if await ctx.ask_yes_no("Heal all damage from this Pokémon?"): + await ctx.heal(9999, target=ctx.source) + card = PokemonCardDef( guid="45ec4250-3b7d-5915-b6c3-db05452d2cce", @@ -21,14 +33,17 @@ card = PokemonCardDef( Ability( title="Moisture Star", game_text="During your turn, you may heal all damage from this Pok\u00e9mon. (You can't use more than 1 VSTAR Power in a game.)", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + vstar=True, + condition=_moisture_star_condition, + effect=moisture_star, ), Attack( title="Rolling Iron", game_text="During your opponent's next turn, this Pok\u00e9mon takes 80 less damage from attacks (after applying Weakness and Resistance).", cost={PokemonTypes.WATER: 1, PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 1}, damage=200, - effect=unimplemented, + effect=protect_next_turn(reduce=80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/HisuianGoodraV_135.py b/spirit/game/scripts/cards/SWSH11/HisuianGoodraV_135.py index af60748..d92084c 100644 --- a/spirit/game/scripts/cards/SWSH11/HisuianGoodraV_135.py +++ b/spirit/game/scripts/cards/SWSH11/HisuianGoodraV_135.py @@ -1,4 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.support_common import gust_attack +from spirit.game.card_effects.passives_common import protect_next_turn +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -22,14 +24,14 @@ card = PokemonCardDef( game_text="Your opponent switches their Active Pok\u00e9mon with 1 of their Benched Pok\u00e9mon.", cost={PokemonTypes.WATER: 1, PokemonTypes.METAL: 1}, damage=60, - effect=unimplemented, + effect=gust_attack(opponent_chooses=True), ), Attack( title="Rolling Shell", game_text="During your opponent's next turn, this Pok\u00e9mon takes 30 less damage from attacks (after applying Weakness and Resistance).", cost={PokemonTypes.WATER: 1, PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 1}, damage=140, - effect=unimplemented, + effect=protect_next_turn(reduce=30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/HisuianGoodraV_187.py b/spirit/game/scripts/cards/SWSH11/HisuianGoodraV_187.py index fb3424d..4447d18 100644 --- a/spirit/game/scripts/cards/SWSH11/HisuianGoodraV_187.py +++ b/spirit/game/scripts/cards/SWSH11/HisuianGoodraV_187.py @@ -1,4 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.support_common import gust_attack +from spirit.game.card_effects.passives_common import protect_next_turn +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -22,14 +24,14 @@ card = PokemonCardDef( game_text="Your opponent switches their Active Pok\u00e9mon with 1 of their Benched Pok\u00e9mon.", cost={PokemonTypes.WATER: 1, PokemonTypes.METAL: 1}, damage=60, - effect=unimplemented, + effect=gust_attack(opponent_chooses=True), ), Attack( title="Rolling Shell", game_text="During your opponent's next turn, this Pok\u00e9mon takes 30 less damage from attacks (after applying Weakness and Resistance).", cost={PokemonTypes.WATER: 1, PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 1}, damage=140, - effect=unimplemented, + effect=protect_next_turn(reduce=30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/HisuianGoodra_134.py b/spirit/game/scripts/cards/SWSH11/HisuianGoodra_134.py index afc67f6..ba974f5 100644 --- a/spirit/game/scripts/cards/SWSH11/HisuianGoodra_134.py +++ b/spirit/game/scripts/cards/SWSH11/HisuianGoodra_134.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, is_pokemon_v from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_basic_pokemon +from spirit.game.card_effects.pokemon import energy_provides_type +from spirit.game.card_effects.passives_common import prevent_damage_when + + +def _metal_lodging_pred(calc, carrier): + target = calc.target + if target is None or target.owning_player_id != carrier.owning_player_id: + return False + if not is_basic_pokemon(target): + return False + if not any(energy_provides_type(e, PokemonTypes.METAL.value) for e in target.children): + return False + attacker = calc.attacker + return attacker is not None and is_pokemon_v(attacker.archetype_id) + card = PokemonCardDef( guid="a04130db-d9da-503c-af2c-e4ea5789030f", @@ -21,7 +37,7 @@ card = PokemonCardDef( Ability( title="Metal Lodging", game_text="Prevent all damage done to each of your Basic Pok\u00e9mon that has any Metal Energy attached by attacks from your opponent's Pok\u00e9mon V.", - effect=unimplemented, + passive=prevent_damage_when(_metal_lodging_pred), ), Attack( title="Heavy Impact", diff --git a/spirit/game/scripts/cards/SWSH11/HisuianGrowlithe_83.py b/spirit/game/scripts/cards/SWSH11/HisuianGrowlithe_83.py index 1ef2858..01ac244 100644 --- a/spirit/game/scripts/cards/SWSH11/HisuianGrowlithe_83.py +++ b/spirit/game/scripts/cards/SWSH11/HisuianGrowlithe_83.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="9a73fb15-407a-5557-8793-c91664c8cac6", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Singe", game_text="Your opponent's Active Pok\u00e9mon is now Burned.", cost={}, - effect=unimplemented, + effect=condition_attack(SpecialConditions.BURNED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/HisuianSliggoo_133.py b/spirit/game/scripts/cards/SWSH11/HisuianSliggoo_133.py index 8c38f1d..b9e887c 100644 --- a/spirit/game/scripts/cards/SWSH11/HisuianSliggoo_133.py +++ b/spirit/game/scripts/cards/SWSH11/HisuianSliggoo_133.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import protect_next_turn card = PokemonCardDef( guid="7cdea712-6b10-54e3-a88a-c9cf360e8c0a", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Rigidify", game_text="During your opponent's next turn, this Pok\u00e9mon takes 50 less damage from attacks (after applying Weakness and Resistance).", cost={}, - effect=unimplemented, + effect=protect_next_turn(reduce=50), ), Attack( title="Gentle Slap", diff --git a/spirit/game/scripts/cards/SWSH11/HisuianZoroarkVSTAR_147.py b/spirit/game/scripts/cards/SWSH11/HisuianZoroarkVSTAR_147.py index e8d7779..3566be2 100644 --- a/spirit/game/scripts/cards/SWSH11/HisuianZoroarkVSTAR_147.py +++ b/spirit/game/scripts/cards/SWSH11/HisuianZoroarkVSTAR_147.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.attacks_common import damage_per + + +async def phantom_star(ctx): + """VSTAR Power: you may discard your hand and draw 7 cards.""" + if not await ctx.ask_yes_no("Discard your hand and draw 7 cards?"): + return + await ctx.discard_cards(ctx.hand()) + await ctx.draw_cards(7) + + +def _damaged_own_pokemon(ctx): + return sum( + 1 for p in ctx.my_pokemon_in_play() + if p.get_attribute(AttrID.HP, ctx.max_hp(p)) < ctx.max_hp(p) + ) + card = PokemonCardDef( guid="02162caa-84a9-5c4e-b5e7-646c8d4b3deb", @@ -22,7 +39,9 @@ card = PokemonCardDef( Ability( title="Phantom Star", game_text="During your turn, you may discard your hand and draw 7 cards. (You can't use more than 1 VSTAR Power in a game.)", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + vstar=True, + effect=phantom_star, ), Attack( title="Ticking Curse", @@ -30,7 +49,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=50, damage_operator="x", - effect=unimplemented, + effect=damage_per(_damaged_own_pokemon, 50), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/HisuianZoroarkVSTAR_203.py b/spirit/game/scripts/cards/SWSH11/HisuianZoroarkVSTAR_203.py index 64a70ae..a33df5a 100644 --- a/spirit/game/scripts/cards/SWSH11/HisuianZoroarkVSTAR_203.py +++ b/spirit/game/scripts/cards/SWSH11/HisuianZoroarkVSTAR_203.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.attacks_common import damage_per + + +async def phantom_star(ctx): + """VSTAR Power: you may discard your hand and draw 7 cards.""" + if not await ctx.ask_yes_no("Discard your hand and draw 7 cards?"): + return + await ctx.discard_cards(ctx.hand()) + await ctx.draw_cards(7) + + +def _damaged_own_pokemon(ctx): + return sum( + 1 for p in ctx.my_pokemon_in_play() + if p.get_attribute(AttrID.HP, ctx.max_hp(p)) < ctx.max_hp(p) + ) + card = PokemonCardDef( guid="0c8dc18c-cca2-5814-accf-05426543dc61", @@ -22,7 +39,9 @@ card = PokemonCardDef( Ability( title="Phantom Star", game_text="During your turn, you may discard your hand and draw 7 cards. (You can't use more than 1 VSTAR Power in a game.)", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + vstar=True, + effect=phantom_star, ), Attack( title="Ticking Curse", @@ -30,7 +49,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=50, damage_operator="x", - effect=unimplemented, + effect=damage_per(_damaged_own_pokemon, 50), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/HisuianZoroarkVSTAR_213.py b/spirit/game/scripts/cards/SWSH11/HisuianZoroarkVSTAR_213.py index 0ab18fd..4d10798 100644 --- a/spirit/game/scripts/cards/SWSH11/HisuianZoroarkVSTAR_213.py +++ b/spirit/game/scripts/cards/SWSH11/HisuianZoroarkVSTAR_213.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.attacks_common import damage_per + + +async def phantom_star(ctx): + """VSTAR Power: you may discard your hand and draw 7 cards.""" + if not await ctx.ask_yes_no("Discard your hand and draw 7 cards?"): + return + await ctx.discard_cards(ctx.hand()) + await ctx.draw_cards(7) + + +def _damaged_own_pokemon(ctx): + return sum( + 1 for p in ctx.my_pokemon_in_play() + if p.get_attribute(AttrID.HP, ctx.max_hp(p)) < ctx.max_hp(p) + ) + card = PokemonCardDef( guid="bcbfa39a-9816-5891-b77e-24f3eab8e2a4", @@ -22,7 +39,9 @@ card = PokemonCardDef( Ability( title="Phantom Star", game_text="During your turn, you may discard your hand and draw 7 cards. (You can't use more than 1 VSTAR Power in a game.)", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + vstar=True, + effect=phantom_star, ), Attack( title="Ticking Curse", @@ -30,7 +49,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=50, damage_operator="x", - effect=unimplemented, + effect=damage_per(_damaged_own_pokemon, 50), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/HisuianZoroarkV_146.py b/spirit/game/scripts/cards/SWSH11/HisuianZoroarkV_146.py index b1c6002..2583cb8 100644 --- a/spirit/game/scripts/cards/SWSH11/HisuianZoroarkV_146.py +++ b/spirit/game/scripts/cards/SWSH11/HisuianZoroarkV_146.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import switch_self_attack + + +async def shadow_cyclone(ctx): + await ctx.deal_damage() + await ctx.move_energy_freely([ctx.attacker], ctx.my_bench(), max_count=1) card = PokemonCardDef( guid="e68113fe-632e-5312-90d9-5ef9bd1be86b", @@ -23,14 +29,14 @@ card = PokemonCardDef( game_text="You may switch this Pok\u00e9mon with 1 of your Benched Pok\u00e9mon.", cost={}, damage=30, - effect=unimplemented, + effect=switch_self_attack(optional=True), ), Attack( title="Shadow Cyclone", game_text="Move an Energy from this Pok\u00e9mon to 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 3}, damage=130, - effect=unimplemented, + effect=shadow_cyclone, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/HisuianZoroark_76.py b/spirit/game/scripts/cards/SWSH11/HisuianZoroark_76.py index 0b287d2..8ea863b 100644 --- a/spirit/game/scripts/cards/SWSH11/HisuianZoroark_76.py +++ b/spirit/game/scripts/cards/SWSH11/HisuianZoroark_76.py @@ -1,5 +1,7 @@ from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import recover_from_discard +from spirit.game.card_effects.pokemon import delayed_knockout card = PokemonCardDef( guid="f9ad5b93-efa4-5246-9242-6ed44c647c07", @@ -24,13 +26,13 @@ card = PokemonCardDef( title="Doom Curse", game_text="At the end of your opponent's next turn, the Defending Pok\u00e9mon will be Knocked Out.", cost={}, - effect=unimplemented, + effect=delayed_knockout, ), Attack( title="Call Back", game_text="Put a card from your discard pile into your hand.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=recover_from_discard(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/HisuianZorua_75.py b/spirit/game/scripts/cards/SWSH11/HisuianZorua_75.py index 3be3d62..ecdd34d 100644 --- a/spirit/game/scripts/cards/SWSH11/HisuianZorua_75.py +++ b/spirit/game/scripts/cards/SWSH11/HisuianZorua_75.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import draw_attack card = PokemonCardDef( guid="80e5c7e1-2c3f-545b-9584-e7517486df62", @@ -23,7 +24,7 @@ card = PokemonCardDef( title="Collect", game_text="Draw a card.", cost={}, - effect=unimplemented, + effect=draw_attack(1), ), Attack( title="Mumble", diff --git a/spirit/game/scripts/cards/SWSH11/Honchkrow_115.py b/spirit/game/scripts/cards/SWSH11/Honchkrow_115.py index 213d656..1e6cee5 100644 --- a/spirit/game/scripts/cards/SWSH11/Honchkrow_115.py +++ b/spirit/game/scripts/cards/SWSH11/Honchkrow_115.py @@ -1,6 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def night_cyclone(ctx): + """160 damage, then move all Energy from this Pokemon to your Benched Pokemon in any way you like.""" + await ctx.deal_damage() + bench = ctx.my_bench() + energies = list(ctx.attached_energies(ctx.attacker)) + if not bench or not energies: + return + for energy in energies: + target = await ctx.choose_pokemon(bench, "Choose a Benched Pokémon to move this Energy to") + if target is None: + break + await ctx.move_energy(energy, target) + + card = PokemonCardDef( guid="8ef6af8c-bb7a-5f30-ab9c-2785269742c5", key="SWSH11", @@ -30,7 +45,7 @@ card = PokemonCardDef( game_text="Move all Energy from this Pok\u00e9mon to your Benched Pok\u00e9mon in any way you like.", cost={PokemonTypes.DARKNESS: 2, PokemonTypes.COLORLESS: 1}, damage=160, - effect=unimplemented, + effect=night_cyclone, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Hoopa_122.py b/spirit/game/scripts/cards/SWSH11/Hoopa_122.py index 707b258..10ea8f2 100644 --- a/spirit/game/scripts/cards/SWSH11/Hoopa_122.py +++ b/spirit/game/scripts/cards/SWSH11/Hoopa_122.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_attach_energy +from spirit.game.card_effects.pokemon import is_energy_card, energy_provides_type + + +def _is_darkness_energy(card): + return is_energy_card(card) and energy_provides_type(card, PokemonTypes.DARKNESS) + card = PokemonCardDef( guid="c7318a02-a813-5c6c-8a1c-12eb05a05d07", @@ -22,7 +29,10 @@ card = PokemonCardDef( title="Hand of Djinn", game_text="Search your deck for a Darkness Energy card and attach it to 1 of your Pok\u00e9mon. Then, shuffle your deck.", cost={PokemonTypes.DARKNESS: 1}, - effect=unimplemented, + effect=search_attach_energy( + _is_darkness_energy, count=1, + prompt="Choose a Darkness Energy card to attach.", + ), ), Attack( title="Tyrannical Hole", diff --git a/spirit/game/scripts/cards/SWSH11/Horsea_35.py b/spirit/game/scripts/cards/SWSH11/Horsea_35.py index a64c3cb..4bc3689 100644 --- a/spirit/game/scripts/cards/SWSH11/Horsea_35.py +++ b/spirit/game/scripts/cards/SWSH11/Horsea_35.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import switch_self_attack card = PokemonCardDef( guid="31183860-80ad-5336-ac6a-2b302c2a4205", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Switch this Pok\u00e9mon with 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.WATER: 1}, damage=10, - effect=unimplemented, + effect=switch_self_attack(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Inkay_121.py b/spirit/game/scripts/cards/SWSH11/Inkay_121.py index ea894fa..eb48a51 100644 --- a/spirit/game/scripts/cards/SWSH11/Inkay_121.py +++ b/spirit/game/scripts/cards/SWSH11/Inkay_121.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_bench card = PokemonCardDef( guid="73c23f96-8dae-503d-bfe2-31b896151ad3", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Call for Family", game_text="Search your deck for up to 2 Basic Pok\u00e9mon and put them onto your Bench. Then, shuffle your deck.", cost={PokemonTypes.DARKNESS: 1}, - effect=unimplemented, + effect=search_to_bench(count=2), ), Attack( title="Tackle", diff --git a/spirit/game/scripts/cards/SWSH11/Inkay_77.py b/spirit/game/scripts/cards/SWSH11/Inkay_77.py index f6370bc..a3585d1 100644 --- a/spirit/game/scripts/cards/SWSH11/Inkay_77.py +++ b/spirit/game/scripts/cards/SWSH11/Inkay_77.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_or_nothing card = PokemonCardDef( guid="a4d45c57-0570-5860-a2f2-ea9597866a5c", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Flip a coin. If tails, this attack does nothing.", cost={PokemonTypes.PSYCHIC: 1}, damage=30, - effect=unimplemented, + effect=flip_or_nothing(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Iscan_158.py b/spirit/game/scripts/cards/SWSH11/Iscan_158.py index ddc0ab7..cd39e82 100644 --- a/spirit/game/scripts/cards/SWSH11/Iscan_158.py +++ b/spirit/game/scripts/cards/SWSH11/Iscan_158.py @@ -1,6 +1,14 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.card_effects.support_common import conditional_draw +from spirit.game.data_utils import SupporterCardDef, def_for from spirit.game.attributes import Rarities + +def _active_is_hisuian(ctx): + active = ctx.my_active() + d = def_for(active.archetype_id) if active is not None else None + return bool(d and d.display_name and "Hisuian" in d.display_name) + + card = SupporterCardDef( guid="808c0b3f-9d82-5696-831d-66e093f06b5d", key="SWSH11", @@ -11,5 +19,5 @@ card = SupporterCardDef( collector_number=158, set_code="SWSH11", rarity=Rarities.Uncommon, - effect=unimplemented + effect=conditional_draw(2, 2, _active_is_hisuian) ) diff --git a/spirit/game/scripts/cards/SWSH11/Iscan_192.py b/spirit/game/scripts/cards/SWSH11/Iscan_192.py index 47a282d..33f6075 100644 --- a/spirit/game/scripts/cards/SWSH11/Iscan_192.py +++ b/spirit/game/scripts/cards/SWSH11/Iscan_192.py @@ -1,6 +1,14 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.card_effects.support_common import conditional_draw +from spirit.game.data_utils import SupporterCardDef, def_for from spirit.game.attributes import Rarities + +def _active_is_hisuian(ctx): + active = ctx.my_active() + d = def_for(active.archetype_id) if active is not None else None + return bool(d and d.display_name and "Hisuian" in d.display_name) + + card = SupporterCardDef( guid="267d805f-dbfd-52a1-b8d3-2043d1523851", key="SWSH11", @@ -11,5 +19,5 @@ card = SupporterCardDef( collector_number=192, set_code="SWSH11", rarity=Rarities.RareUltra, - effect=unimplemented + effect=conditional_draw(2, 2, _active_is_hisuian) ) diff --git a/spirit/game/scripts/cards/SWSH11/Iscan_207.py b/spirit/game/scripts/cards/SWSH11/Iscan_207.py index 503f7cf..afdca0b 100644 --- a/spirit/game/scripts/cards/SWSH11/Iscan_207.py +++ b/spirit/game/scripts/cards/SWSH11/Iscan_207.py @@ -1,6 +1,14 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.card_effects.support_common import conditional_draw +from spirit.game.data_utils import SupporterCardDef, def_for from spirit.game.attributes import Rarities + +def _active_is_hisuian(ctx): + active = ctx.my_active() + d = def_for(active.archetype_id) if active is not None else None + return bool(d and d.display_name and "Hisuian" in d.display_name) + + card = SupporterCardDef( guid="417f9be9-5c8b-5397-95b4-f9a40bf91604", key="SWSH11", @@ -11,5 +19,5 @@ card = SupporterCardDef( collector_number=207, set_code="SWSH11", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=conditional_draw(2, 2, _active_is_hisuian) ) diff --git a/spirit/game/scripts/cards/SWSH11/Jynx_68.py b/spirit/game/scripts/cards/SWSH11/Jynx_68.py index 50b1743..1714a60 100644 --- a/spirit/game/scripts/cards/SWSH11/Jynx_68.py +++ b/spirit/game/scripts/cards/SWSH11/Jynx_68.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.support_common import gust_then + + +async def _alluring_dance_confuse(ctx, new_active): + await ctx.apply_special_condition(new_active, SpecialConditions.CONFUSED) + card = PokemonCardDef( guid="3747552a-4458-5823-a691-604aecc92702", @@ -21,9 +27,9 @@ card = PokemonCardDef( abilities=[ Attack( title="Alluring Dance", - game_text="Switch 1 of your opponent's Benched Pok\u00e9mon with their Active Pok\u00e9mon. The new Active Pok\u00e9mon is now Confused.", + game_text="Switch 1 of your opponent's Benched Pokémon with their Active Pokémon. The new Active Pokémon is now Confused.", cost={PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + effect=gust_then(_alluring_dance_confuse), ), Attack( title="Super Psy Bolt", @@ -31,4 +37,4 @@ card = PokemonCardDef( damage=80, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH11/Kingdra_37.py b/spirit/game/scripts/cards/SWSH11/Kingdra_37.py index 81bda4e..e071a3c 100644 --- a/spirit/game/scripts/cards/SWSH11/Kingdra_37.py +++ b/spirit/game/scripts/cards/SWSH11/Kingdra_37.py @@ -1,6 +1,23 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def _seething_currents(ctx): + """You may have either player shuffle their hand into their deck; if + they did, they draw 4 cards.""" + if not await ctx.ask_yes_no( + "Have either player shuffle their hand and put it on the bottom of their deck?" + ): + return + choice = await ctx.choose( + "Choose a player to shuffle their hand into their deck.", + ["You", "Your Opponent"], use_panel=False, + ) + target_id = ctx.player_id if choice == 0 else ctx.opponent_id + moved = await ctx.hand_to_bottom_of_deck(target_id) + if moved: + await ctx.draw_cards(4, player_id=target_id) + card = PokemonCardDef( guid="8552ac38-d4fa-581b-8e31-2d582efdf008", key="SWSH11", @@ -22,7 +39,8 @@ card = PokemonCardDef( Ability( title="Seething Currents", game_text="Once during your turn, you may have either player shuffle their hand and put it on the bottom of their deck. If that player put any cards on the bottom of their deck in this way, they draw 4 cards.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + effect=_seething_currents, ), Attack( title="Hydro Splash", diff --git a/spirit/game/scripts/cards/SWSH11/Komala_149.py b/spirit/game/scripts/cards/SWSH11/Komala_149.py index 414aa28..1dbde4d 100644 --- a/spirit/game/scripts/cards/SWSH11/Komala_149.py +++ b/spirit/game/scripts/cards/SWSH11/Komala_149.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, CLIENT_SPECIAL_CONDITION_NAMES, PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.session.passives import Passive + + +class AllJustADreamPassive(Passive): + def modify_prizes_for_knockout(self, pokemon, ctx, count, carrier): + if pokemon is not carrier or not ctx.is_attack_effect(): + return count + attacker = getattr(ctx, "attacker", None) + if attacker is None or attacker.owning_player_id == pokemon.owning_player_id: + return count + conditions = pokemon.get_attribute(AttrID.SPECIAL_CONDITIONS) or [] + if CLIENT_SPECIAL_CONDITION_NAMES[SpecialConditions.ASLEEP] not in conditions: + return count + return 0 + card = PokemonCardDef( guid="aaac1bf0-77c7-5481-b9e9-356d2a7f0ea5", @@ -20,15 +36,15 @@ card = PokemonCardDef( abilities=[ Ability( title="All Just a Dream", - game_text="If this Pok\u00e9mon is Asleep and is Knocked Out by damage from an attack from your opponent's Pok\u00e9mon, your opponent can't take any Prize cards for it.", - effect=unimplemented, + game_text="If this Pokémon is Asleep and is Knocked Out by damage from an attack from your opponent's Pokémon, your opponent can't take any Prize cards for it.", + passive=AllJustADreamPassive(), ), Attack( title="Collapse", - game_text="This Pok\u00e9mon is now Asleep.", + game_text="This Pokémon is now Asleep.", cost={PokemonTypes.COLORLESS: 2}, damage=60, - effect=unimplemented, + effect=condition_attack(self_conditions=(SpecialConditions.ASLEEP,)), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH11/KyuremVMAX_197.py b/spirit/game/scripts/cards/SWSH11/KyuremVMAX_197.py index f438d1b..83f4907 100644 --- a/spirit/game/scripts/cards/SWSH11/KyuremVMAX_197.py +++ b/spirit/game/scripts/cards/SWSH11/KyuremVMAX_197.py @@ -1,5 +1,41 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import energy_provides_type + + +async def glaciated_world(ctx): + """Once per turn, discard the top deck card; if Water Energy, attach it.""" + if not await ctx.ask_yes_no("Discard the top card of your deck?"): + return + top = ctx.deck_top(1) + if not top: + return + card = top[0] + await ctx.discard_cards([card]) + if energy_provides_type(card, PokemonTypes.WATER): + target = await ctx.choose_pokemon( + ctx.my_pokemon_in_play(), "Attach the Water Energy to 1 of your Pokémon" + ) + if target is not None: + await ctx.attach_energy(card, target) + + +async def max_frost(ctx): + """You may discard any amount of Water Energy from this Pokemon; +50 each.""" + attached = [ + e for e in ctx.attached_energies(ctx.attacker) + if energy_provides_type(e, PokemonTypes.WATER) + ] + picks = [] + if attached: + picks = await ctx.choose_cards( + attached, len(attached), minimum=0, + prompt="Discard any amount of Water Energy from this Pokémon.", + ) + if picks: + await ctx.discard_cards(picks) + await ctx.deal_damage(120 + 50 * len(picks)) + card = PokemonCardDef( guid="77fc6d36-e8c2-52f9-81eb-ac0ebe6ad0ae", @@ -22,7 +58,8 @@ card = PokemonCardDef( Ability( title="Glaciated World", game_text="Once during your turn, you may discard the top card of your deck. If that card is a Water Energy card, attach it to 1 of your Pok\u00e9mon.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + effect=glaciated_world, ), Attack( title="Max Frost", @@ -30,7 +67,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 3}, damage=120, damage_operator="+", - effect=unimplemented, + effect=max_frost, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/KyuremVMAX_49.py b/spirit/game/scripts/cards/SWSH11/KyuremVMAX_49.py index 6d76db3..b8345c7 100644 --- a/spirit/game/scripts/cards/SWSH11/KyuremVMAX_49.py +++ b/spirit/game/scripts/cards/SWSH11/KyuremVMAX_49.py @@ -1,5 +1,41 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import energy_provides_type + + +async def glaciated_world(ctx): + """Once per turn, discard the top deck card; if Water Energy, attach it.""" + if not await ctx.ask_yes_no("Discard the top card of your deck?"): + return + top = ctx.deck_top(1) + if not top: + return + card = top[0] + await ctx.discard_cards([card]) + if energy_provides_type(card, PokemonTypes.WATER): + target = await ctx.choose_pokemon( + ctx.my_pokemon_in_play(), "Attach the Water Energy to 1 of your Pokémon" + ) + if target is not None: + await ctx.attach_energy(card, target) + + +async def max_frost(ctx): + """You may discard any amount of Water Energy from this Pokemon; +50 each.""" + attached = [ + e for e in ctx.attached_energies(ctx.attacker) + if energy_provides_type(e, PokemonTypes.WATER) + ] + picks = [] + if attached: + picks = await ctx.choose_cards( + attached, len(attached), minimum=0, + prompt="Discard any amount of Water Energy from this Pokémon.", + ) + if picks: + await ctx.discard_cards(picks) + await ctx.deal_damage(120 + 50 * len(picks)) + card = PokemonCardDef( guid="e1735ddf-8a09-5382-b45f-226e0501d1ee", @@ -22,7 +58,8 @@ card = PokemonCardDef( Ability( title="Glaciated World", game_text="Once during your turn, you may discard the top card of your deck. If that card is a Water Energy card, attach it to 1 of your Pok\u00e9mon.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + effect=glaciated_world, ), Attack( title="Max Frost", @@ -30,7 +67,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 3}, damage=120, damage_operator="+", - effect=unimplemented, + effect=max_frost, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/KyuremV_174.py b/spirit/game/scripts/cards/SWSH11/KyuremV_174.py index 86a165c..3dfa7ee 100644 --- a/spirit/game/scripts/cards/SWSH11/KyuremV_174.py +++ b/spirit/game/scripts/cards/SWSH11/KyuremV_174.py @@ -1,5 +1,26 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.session.effects import is_special_energy +from spirit.game.card_effects.pokemon import is_energy_card +from spirit.game.card_effects.support_common import distribute_energy + + +def _is_water_energy_card(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_energy_card(card) and not is_special_energy(card) and PokemonTypes.WATER.value in types + + +async def rapid_freeze(ctx): + """Attach any number of Water Energy cards from your hand to your Pokemon in any way you like.""" + pool = [c for c in ctx.hand() if _is_water_energy_card(c)] + if not pool: + return + picks = await ctx.choose_cards( + pool, len(pool), minimum=0, prompt="Choose Water Energy cards to attach") + if not picks: + return + await distribute_energy(ctx, picks, ctx.my_pokemon_in_play()) + card = PokemonCardDef( guid="4b410b0d-266f-5392-9b8c-c5e1a3a7d92b", @@ -22,7 +43,7 @@ card = PokemonCardDef( title="Rapid Freeze", game_text="Attach any number of Water Energy cards from your hand to your Pok\u00e9mon in any way you like.", cost={PokemonTypes.WATER: 1}, - effect=unimplemented, + effect=rapid_freeze, ), Attack( title="Frost Smash", diff --git a/spirit/game/scripts/cards/SWSH11/KyuremV_48.py b/spirit/game/scripts/cards/SWSH11/KyuremV_48.py index 9fffadc..d9f6b48 100644 --- a/spirit/game/scripts/cards/SWSH11/KyuremV_48.py +++ b/spirit/game/scripts/cards/SWSH11/KyuremV_48.py @@ -1,5 +1,26 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.session.effects import is_special_energy +from spirit.game.card_effects.pokemon import is_energy_card +from spirit.game.card_effects.support_common import distribute_energy + + +def _is_water_energy_card(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_energy_card(card) and not is_special_energy(card) and PokemonTypes.WATER.value in types + + +async def rapid_freeze(ctx): + """Attach any number of Water Energy cards from your hand to your Pokemon in any way you like.""" + pool = [c for c in ctx.hand() if _is_water_energy_card(c)] + if not pool: + return + picks = await ctx.choose_cards( + pool, len(pool), minimum=0, prompt="Choose Water Energy cards to attach") + if not picks: + return + await distribute_energy(ctx, picks, ctx.my_pokemon_in_play()) + card = PokemonCardDef( guid="31832006-11a2-53d0-a709-a3bf0bcefa20", @@ -22,7 +43,7 @@ card = PokemonCardDef( title="Rapid Freeze", game_text="Attach any number of Water Energy cards from your hand to your Pok\u00e9mon in any way you like.", cost={PokemonTypes.WATER: 1}, - effect=unimplemented, + effect=rapid_freeze, ), Attack( title="Frost Smash", diff --git a/spirit/game/scripts/cards/SWSH11/Lady_159.py b/spirit/game/scripts/cards/SWSH11/Lady_159.py index e757b36..85d1f2a 100644 --- a/spirit/game/scripts/cards/SWSH11/Lady_159.py +++ b/spirit/game/scripts/cards/SWSH11/Lady_159.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.card_effects.support_common import search_to_hand card = SupporterCardDef( guid="09a01c47-4387-50d4-8af5-3030a17fafd6", @@ -11,5 +13,8 @@ card = SupporterCardDef( collector_number=159, set_code="SWSH11", rarity=Rarities.Uncommon, - effect=unimplemented + effect=search_to_hand( + is_basic_energy_card, count=4, reveal=True, + prompt="Choose up to 4 basic Energy cards.", + ) ) diff --git a/spirit/game/scripts/cards/SWSH11/Lady_193.py b/spirit/game/scripts/cards/SWSH11/Lady_193.py index 78187a2..2a7e54e 100644 --- a/spirit/game/scripts/cards/SWSH11/Lady_193.py +++ b/spirit/game/scripts/cards/SWSH11/Lady_193.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.card_effects.support_common import search_to_hand card = SupporterCardDef( guid="f9a504c9-030d-5c62-ad05-1a6e1f3a6052", @@ -11,5 +13,8 @@ card = SupporterCardDef( collector_number=193, set_code="SWSH11", rarity=Rarities.RareUltra, - effect=unimplemented + effect=search_to_hand( + is_basic_energy_card, count=4, reveal=True, + prompt="Choose up to 4 basic Energy cards.", + ) ) diff --git a/spirit/game/scripts/cards/SWSH11/Lady_208.py b/spirit/game/scripts/cards/SWSH11/Lady_208.py index fa9eae9..db06fea 100644 --- a/spirit/game/scripts/cards/SWSH11/Lady_208.py +++ b/spirit/game/scripts/cards/SWSH11/Lady_208.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.card_effects.support_common import search_to_hand card = SupporterCardDef( guid="35f9b28a-9133-5326-8256-96a1ff31a27a", @@ -11,5 +13,8 @@ card = SupporterCardDef( collector_number=208, set_code="SWSH11", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=search_to_hand( + is_basic_energy_card, count=4, reveal=True, + prompt="Choose up to 4 basic Energy cards.", + ) ) diff --git a/spirit/game/scripts/cards/SWSH11/LakeAcuity_160.py b/spirit/game/scripts/cards/SWSH11/LakeAcuity_160.py index 588094b..89ad961 100644 --- a/spirit/game/scripts/cards/SWSH11/LakeAcuity_160.py +++ b/spirit/game/scripts/cards/SWSH11/LakeAcuity_160.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import StadiumCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import StadiumCardDef +from spirit.game.attributes import PokemonTypes, Rarities +from spirit.game.card_effects.pokemon import is_energy_card, energy_provides_type +from spirit.game.card_effects.passives_common import takes_less_passive + + +def _has_water_or_fighting_energy(pokemon) -> bool: + return any( + is_energy_card(c) and ( + energy_provides_type(c, PokemonTypes.WATER.value) + or energy_provides_type(c, PokemonTypes.FIGHTING.value) + ) + for c in pokemon.children + ) + card = StadiumCardDef( guid="a133e101-31d6-5ae1-96c2-85a0b1cfe759", @@ -11,5 +24,7 @@ card = StadiumCardDef( collector_number=160, set_code="SWSH11", rarity=Rarities.Uncommon, - effect=unimplemented + passive=takes_less_passive( + 20, protects=lambda target, carrier: _has_water_or_fighting_energy(target) + ), ) diff --git a/spirit/game/scripts/cards/SWSH11/Lampent_25.py b/spirit/game/scripts/cards/SWSH11/Lampent_25.py index f8249d9..fed942d 100644 --- a/spirit/game/scripts/cards/SWSH11/Lampent_25.py +++ b/spirit/game/scripts/cards/SWSH11/Lampent_25.py @@ -1,6 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def flickering_glow(ctx): + """Flip a coin. If heads, discard an Energy from the opponent's Active.""" + await ctx.deal_damage() + heads = (await ctx.flip_coins(1, ctx.ability.title))[0] + if heads: + target = ctx.opponent_active() + if target is not None and not ctx.effects_blocked(target): + await ctx.discard_energy_from( + target, 1, prompt="Choose Energy to discard from the Defending Pokémon") + + card = PokemonCardDef( guid="1aaae65c-7532-5af7-b35d-75a50561a62d", key="SWSH11", @@ -24,7 +36,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, discard an Energy from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.FIRE: 1}, damage=20, - effect=unimplemented, + effect=flickering_glow, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Landorus_105.py b/spirit/game/scripts/cards/SWSH11/Landorus_105.py index 23d7d57..0665ca7 100644 --- a/spirit/game/scripts/cards/SWSH11/Landorus_105.py +++ b/spirit/game/scripts/cards/SWSH11/Landorus_105.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import self_energy_discard_attack card = PokemonCardDef( guid="ff3122f0-978d-5bf7-ba28-8c81a8773b05", @@ -28,7 +29,7 @@ card = PokemonCardDef( game_text="Discard 2 Energy from this Pok\u00e9mon.", cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=130, - effect=unimplemented, + effect=self_energy_discard_attack(count=2), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Litleo_28.py b/spirit/game/scripts/cards/SWSH11/Litleo_28.py index ed9d2a6..5fa33b0 100644 --- a/spirit/game/scripts/cards/SWSH11/Litleo_28.py +++ b/spirit/game/scripts/cards/SWSH11/Litleo_28.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="ca074aa8-fa1a-5dc6-a908-7b28cf245314", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Singe", game_text="Your opponent's Active Pok\u00e9mon is now Burned.", cost={PokemonTypes.FIRE: 1}, - effect=unimplemented, + effect=condition_attack(SpecialConditions.BURNED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Litwick_24.py b/spirit/game/scripts/cards/SWSH11/Litwick_24.py index 3eff703..ca39b65 100644 --- a/spirit/game/scripts/cards/SWSH11/Litwick_24.py +++ b/spirit/game/scripts/cards/SWSH11/Litwick_24.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import mill_attack card = PokemonCardDef( guid="4df301b4-84e2-5018-92cc-50f893097832", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Kindling Panic", game_text="Discard the top card of your opponent's deck.", cost={PokemonTypes.FIRE: 1}, - effect=unimplemented, + effect=mill_attack(1), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Lumineon_41.py b/spirit/game/scripts/cards/SWSH11/Lumineon_41.py index 4c344b3..024d2ad 100644 --- a/spirit/game/scripts/cards/SWSH11/Lumineon_41.py +++ b/spirit/game/scripts/cards/SWSH11/Lumineon_41.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import flip_protection card = PokemonCardDef( guid="b5b6d4a4-c116-50c7-92d8-539f61853163", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, during your opponent's next turn, prevent all damage from and effects of attacks done to this Pok\u00e9mon.", cost={PokemonTypes.WATER: 1}, damage=10, - effect=unimplemented, + effect=flip_protection(prevent=True, effects_too=True), ), Attack( title="Waterfall", diff --git a/spirit/game/scripts/cards/SWSH11/Luvdisc_38.py b/spirit/game/scripts/cards/SWSH11/Luvdisc_38.py index 0f9caf7..4bbce2d 100644 --- a/spirit/game/scripts/cards/SWSH11/Luvdisc_38.py +++ b/spirit/game/scripts/cards/SWSH11/Luvdisc_38.py @@ -1,6 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def matching_look(ctx): + """Each player reveals the top 2 cards of their deck, then draws those cards.""" + await ctx.draw_cards(2, player_id=ctx.player_id) + await ctx.draw_cards(2, player_id=ctx.opponent_id) + + card = PokemonCardDef( guid="d4fd59cd-de2b-5e64-83f7-46e2ba727d47", key="SWSH11", @@ -22,7 +29,7 @@ card = PokemonCardDef( title="Matching Look", game_text="Each player reveals the top 2 cards of their deck, then draws those cards.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=matching_look, ), Attack( title="Tackle", diff --git a/spirit/game/scripts/cards/SWSH11/Machamp_88.py b/spirit/game/scripts/cards/SWSH11/Machamp_88.py index 0deb1ab..6a5a692 100644 --- a/spirit/game/scripts/cards/SWSH11/Machamp_88.py +++ b/spirit/game/scripts/cards/SWSH11/Machamp_88.py @@ -1,5 +1,37 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.session.passives import Passive +from spirit.game.card_effects.attacks_common import lock_all_attacks + + +class CrisisMusclesPassive(Passive): + def max_hp_bonus(self, pokemon, carrier): + if pokemon is not carrier: + return 0 + root = pokemon + while root.parent is not None: + root = root.parent + for child in root.children: + if getattr(child, "owning_player_id", None) and \ + child.owning_player_id != pokemon.owning_player_id: + for sub in child.children: + if sub.get_attribute(AttrID.NAME) == "prizePile" and \ + len(sub.children) <= 3: + return 150 + return 0 + + +async def strong_arm_lariat(ctx): + """You may do 100 more damage; if you do, this Pokémon can't attack next turn.""" + if await ctx.ask_yes_no( + "Do 100 more damage? If you do, this Pokémon can't attack during " + "your next turn." + ): + await ctx.deal_damage(200) + lock_all_attacks(ctx, ctx.attacker) + else: + await ctx.deal_damage(100) + card = PokemonCardDef( guid="f300a416-8ca5-5ebb-b0b0-e2f31615d632", @@ -22,7 +54,7 @@ card = PokemonCardDef( Ability( title="Crisis Muscles", game_text="If your opponent has 3 or fewer Prize cards remaining, this Pok\u00e9mon gets +150 HP.", - effect=unimplemented, + passive=CrisisMusclesPassive(), ), Attack( title="Strong-Arm Lariat", @@ -30,7 +62,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 2}, damage=100, damage_operator="+", - effect=unimplemented, + effect=strong_arm_lariat, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Magcargo_22.py b/spirit/game/scripts/cards/SWSH11/Magcargo_22.py index 8e20811..437cd4a 100644 --- a/spirit/game/scripts/cards/SWSH11/Magcargo_22.py +++ b/spirit/game/scripts/cards/SWSH11/Magcargo_22.py @@ -1,6 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def lost_volcano(ctx): + """220. Put all Energy attached to this Pokemon in the Lost Zone.""" + await ctx.deal_damage() + energies = ctx.attached_energies(ctx.attacker) + if energies: + await ctx.move_to_lost_zone(energies) + + card = PokemonCardDef( guid="c2559ece-50ea-54b5-aafd-2fa1b2fa8cc5", key="SWSH11", @@ -26,10 +35,10 @@ card = PokemonCardDef( ), Attack( title="Lost Volcano", - game_text="Put all Energy attached to this Pok\u00e9mon in the Lost Zone.", + game_text="Put all Energy attached to this Pokémon in the Lost Zone.", cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=220, - effect=unimplemented, + effect=lost_volcano, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH11/Magearna_128.py b/spirit/game/scripts/cards/SWSH11/Magearna_128.py index 08f27d7..f8338fe 100644 --- a/spirit/game/scripts/cards/SWSH11/Magearna_128.py +++ b/spirit/game/scripts/cards/SWSH11/Magearna_128.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import has_tool + + +async def windup_beam(ctx): + """60, +60 more and Confuse the opponent's Active if this Pokémon has a + Pokémon Tool attached.""" + tooled = has_tool(ctx.attacker) + await ctx.deal_damage(120 if tooled else 60) + if tooled: + await ctx.apply_special_condition(ctx.defender, SpecialConditions.CONFUSED) + card = PokemonCardDef( guid="63b38191-bde0-58ca-b717-3caf3c19053f", @@ -30,7 +41,7 @@ card = PokemonCardDef( cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 2}, damage=60, damage_operator="+", - effect=unimplemented, + effect=windup_beam, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/MagnezoneVSTAR_198.py b/spirit/game/scripts/cards/SWSH11/MagnezoneVSTAR_198.py index bbf99de..e57e33a 100644 --- a/spirit/game/scripts/cards/SWSH11/MagnezoneVSTAR_198.py +++ b/spirit/game/scripts/cards/SWSH11/MagnezoneVSTAR_198.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.card_effects.attacks_common import snipe_attack +from spirit.game.session.effects import is_item_card card = PokemonCardDef( guid="18cacb06-b824-5a47-bd3c-e623e51bf3b4", @@ -24,13 +27,14 @@ card = PokemonCardDef( game_text="Search your deck for up to 2 Item cards, reveal them, and put them into your hand. Then, shuffle your deck.", cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 2}, damage=180, - effect=unimplemented, + effect=search_to_hand(is_item_card, count=2, minimum=0, reveal=True), ), Attack( title="Electro Star", game_text="This attack does 90 damage to 2 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.) (You can't use more than 1 VSTAR Power in a game.)", cost={PokemonTypes.LIGHTNING: 2}, - effect=unimplemented, + vstar=True, + effect=snipe_attack(90, pool="bench", count=2), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/MagnezoneVSTAR_57.py b/spirit/game/scripts/cards/SWSH11/MagnezoneVSTAR_57.py index 36ce8e7..ff526fa 100644 --- a/spirit/game/scripts/cards/SWSH11/MagnezoneVSTAR_57.py +++ b/spirit/game/scripts/cards/SWSH11/MagnezoneVSTAR_57.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.card_effects.attacks_common import snipe_attack +from spirit.game.session.effects import is_item_card card = PokemonCardDef( guid="57328661-1060-546e-aaef-111692e4f69a", @@ -24,13 +27,14 @@ card = PokemonCardDef( game_text="Search your deck for up to 2 Item cards, reveal them, and put them into your hand. Then, shuffle your deck.", cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 2}, damage=180, - effect=unimplemented, + effect=search_to_hand(is_item_card, count=2, minimum=0, reveal=True), ), Attack( title="Electro Star", game_text="This attack does 90 damage to 2 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.) (You can't use more than 1 VSTAR Power in a game.)", cost={PokemonTypes.LIGHTNING: 2}, - effect=unimplemented, + vstar=True, + effect=snipe_attack(90, pool="bench", count=2), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/MagnezoneV_175.py b/spirit/game/scripts/cards/SWSH11/MagnezoneV_175.py index 6d78763..c118bde 100644 --- a/spirit/game/scripts/cards/SWSH11/MagnezoneV_175.py +++ b/spirit/game/scripts/cards/SWSH11/MagnezoneV_175.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import gust_attack +from spirit.game.card_effects.attacks_common import snipe_attack card = PokemonCardDef( guid="54549d34-58f5-5bac-a9de-70786d136caf", @@ -22,14 +24,14 @@ card = PokemonCardDef( title="Magnetic Tension", game_text="Switch 1 of your opponent's Benched Pok\u00e9mon with their Active Pok\u00e9mon. This attack does 40 damage to the new Active Pok\u00e9mon.", cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=gust_attack(damage_to_new_active=40, damage_before=0), ), Attack( title="Splitting Beam", game_text="This attack also does 30 damage to 2 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 2}, damage=90, - effect=unimplemented, + effect=snipe_attack(30, pool="bench", count=2, also_base=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/MagnezoneV_56.py b/spirit/game/scripts/cards/SWSH11/MagnezoneV_56.py index f801dbc..268f5fe 100644 --- a/spirit/game/scripts/cards/SWSH11/MagnezoneV_56.py +++ b/spirit/game/scripts/cards/SWSH11/MagnezoneV_56.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import gust_attack +from spirit.game.card_effects.attacks_common import snipe_attack card = PokemonCardDef( guid="91ac4eab-73b5-5c97-9be7-3b8a8c56d66a", @@ -22,14 +24,14 @@ card = PokemonCardDef( title="Magnetic Tension", game_text="Switch 1 of your opponent's Benched Pok\u00e9mon with their Active Pok\u00e9mon. This attack does 40 damage to the new Active Pok\u00e9mon.", cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=gust_attack(damage_to_new_active=40, damage_before=0), ), Attack( title="Splitting Beam", game_text="This attack also does 30 damage to 2 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 2}, damage=90, - effect=unimplemented, + effect=snipe_attack(30, pool="bench", count=2, also_base=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Makuhita_97.py b/spirit/game/scripts/cards/SWSH11/Makuhita_97.py index 2a0d196..c0f9ff8 100644 --- a/spirit/game/scripts/cards/SWSH11/Makuhita_97.py +++ b/spirit/game/scripts/cards/SWSH11/Makuhita_97.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="35b43a28-2009-52aa-a79c-5ed20976f878", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 1}, damage=20, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Malamar_78.py b/spirit/game/scripts/cards/SWSH11/Malamar_78.py index 75c2868..909b1ab 100644 --- a/spirit/game/scripts/cards/SWSH11/Malamar_78.py +++ b/spirit/game/scripts/cards/SWSH11/Malamar_78.py @@ -1,6 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def synchro_kinesis(ctx): + """Each player reveals their hand; +90 if a name is shared.""" + await ctx.reveal_hand(ctx.player_id, ctx.opponent_id) + await ctx.reveal_hand(ctx.opponent_id, ctx.player_id) + mine = {c.card_obj.display_name for c in ctx.hand(ctx.player_id)} + theirs = {c.card_obj.display_name for c in ctx.hand(ctx.opponent_id)} + bonus = 90 if mine & theirs else 0 + await ctx.deal_damage(30 + bonus) + + card = PokemonCardDef( guid="d6b0e441-098d-5018-8652-b61b66cc4365", key="SWSH11", @@ -26,7 +37,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 1}, damage=30, damage_operator="+", - effect=unimplemented, + effect=synchro_kinesis, ), Attack( title="Psychic Sphere", diff --git a/spirit/game/scripts/cards/SWSH11/Manectric_55.py b/spirit/game/scripts/cards/SWSH11/Manectric_55.py index 4f429e1..1cf9d6d 100644 --- a/spirit/game/scripts/cards/SWSH11/Manectric_55.py +++ b/spirit/game/scripts/cards/SWSH11/Manectric_55.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, active_is, has_tool card = PokemonCardDef( guid="bf7d193b-acd0-5722-990f-bc16286ccc81", @@ -30,7 +31,7 @@ card = PokemonCardDef( cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 2}, damage=80, damage_operator="+", - effect=unimplemented, + effect=bonus_if(active_is(has_tool), 80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Mawile_71.py b/spirit/game/scripts/cards/SWSH11/Mawile_71.py index 8957f80..e5db334 100644 --- a/spirit/game/scripts/cards/SWSH11/Mawile_71.py +++ b/spirit/game/scripts/cards/SWSH11/Mawile_71.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.passives import Passive + + +class _TemptingTrapVulnerability(Passive): + def modify_damage_taken(self, calc, carrier): + calc.amount += 90 + + +async def tempting_trap(ctx): + """Defender can't retreat next turn; takes 90 more (after W/R) during your next turn.""" + target = ctx.defender + if target is None or ctx.effects_blocked(target): + return + ctx.lock_retreat(target) + ctx.add_passive_through_own_next_turn(target, _TemptingTrapVulnerability()) + card = PokemonCardDef( guid="b5dc5855-de8b-5ac6-b805-6d911440ade7", @@ -22,7 +38,7 @@ card = PokemonCardDef( title="Tempting Trap", game_text="During your opponent's next turn, the Defending Pok\u00e9mon can't retreat. During your next turn, the Defending Pok\u00e9mon takes 90 more damage from attacks (after applying Weakness and Resistance).", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=tempting_trap, ), Attack( title="Bite", diff --git a/spirit/game/scripts/cards/SWSH11/Medicham_100.py b/spirit/game/scripts/cards/SWSH11/Medicham_100.py index de095b4..6ab1ea1 100644 --- a/spirit/game/scripts/cards/SWSH11/Medicham_100.py +++ b/spirit/game/scripts/cards/SWSH11/Medicham_100.py @@ -1,5 +1,23 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import distribute_energy +from spirit.game.card_effects.pokemon import energy_provides_type + + +async def battle_step(ctx): + """50 damage. Search your deck for up to 2 Fighting Energy cards and + attach them to your Benched Pokemon in any way you like. Then, shuffle.""" + await ctx.deal_damage() + picks = await ctx.search_deck( + lambda c: energy_provides_type(c, PokemonTypes.FIGHTING.value), + count=2, minimum=0, + prompt="Choose up to 2 Fighting Energy cards to attach.", + ) + bench = ctx.my_bench() + if picks and bench: + await distribute_energy(ctx, picks, bench) + await ctx.shuffle_deck() + card = PokemonCardDef( guid="7313494d-5c08-5313-8bce-4994e996457c", @@ -24,7 +42,7 @@ card = PokemonCardDef( game_text="Search your deck for up to 2 Fighting Energy cards and attach them to your Benched Pok\u00e9mon in any way you like. Then, shuffle your deck.", cost={PokemonTypes.FIGHTING: 1}, damage=50, - effect=unimplemented, + effect=battle_step, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Meditite_99.py b/spirit/game/scripts/cards/SWSH11/Meditite_99.py index d16159f..7c329c4 100644 --- a/spirit/game/scripts/cards/SWSH11/Meditite_99.py +++ b/spirit/game/scripts/cards/SWSH11/Meditite_99.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_or_nothing card = PokemonCardDef( guid="e019f858-f53e-531c-a1f4-bcee5b7373e5", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Flip a coin. If tails, this attack does nothing.", cost={PokemonTypes.FIGHTING: 1}, damage=30, - effect=unimplemented, + effect=flip_or_nothing(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Mienshao_104.py b/spirit/game/scripts/cards/SWSH11/Mienshao_104.py index f11b41d..40e407b 100644 --- a/spirit/game/scripts/cards/SWSH11/Mienshao_104.py +++ b/spirit/game/scripts/cards/SWSH11/Mienshao_104.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="b35bec1b-30c6-5fcf-a06c-b38d73210714", @@ -30,7 +31,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 1}, damage=70, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=2, per_heads=70), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Mimikyu_80.py b/spirit/game/scripts/cards/SWSH11/Mimikyu_80.py index eaf9884..196cae7 100644 --- a/spirit/game/scripts/cards/SWSH11/Mimikyu_80.py +++ b/spirit/game/scripts/cards/SWSH11/Mimikyu_80.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack + + +async def worst_gift(ctx): + """10 damage for each damage counter on all of your opponent's Pokémon.""" + total_counters = 0 + for pokemon in ctx.opponent_pokemon_in_play(): + total_counters += (ctx.max_hp(pokemon) - pokemon.get_attribute(AttrID.HP, 0)) // 10 + await ctx.deal_damage(10 * total_counters) + card = PokemonCardDef( guid="664d118d-9f67-597a-b47a-59daa0b5d3c5", @@ -23,7 +33,7 @@ card = PokemonCardDef( title="Perplex", game_text="Your opponent's Active Pok\u00e9mon is now Confused.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED), ), Attack( title="Worst Gift", @@ -31,7 +41,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=10, damage_operator="x", - effect=unimplemented, + effect=worst_gift, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/MissFortuneSisters_164.py b/spirit/game/scripts/cards/SWSH11/MissFortuneSisters_164.py index 39eb333..e746f43 100644 --- a/spirit/game/scripts/cards/SWSH11/MissFortuneSisters_164.py +++ b/spirit/game/scripts/cards/SWSH11/MissFortuneSisters_164.py @@ -1,5 +1,26 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.session.effects import is_item_card + + +async def miss_fortune_sisters(ctx): + """Look at the top 5 cards of your opponent's deck and discard any + number of Item cards you find there. Your opponent shuffles the other + cards back into their deck.""" + top = ctx.deck_top(5, player_id=ctx.opponent_id) + if not top: + return + items = [c for c in top if is_item_card(c)] + if items: + picks = await ctx.choose_cards( + items, len(items), minimum=0, + prompt="Choose any number of Item cards to discard", + display_cards=top if len(items) < len(top) else None, + ) + if picks: + await ctx.discard_cards(picks) + await ctx.shuffle_deck(ctx.opponent_id) + card = SupporterCardDef( guid="db4cb0ed-c806-5518-8a65-6c6e8aa0e9fa", @@ -11,5 +32,5 @@ card = SupporterCardDef( collector_number=164, set_code="SWSH11", rarity=Rarities.Uncommon, - effect=unimplemented + effect=miss_fortune_sisters, ) diff --git a/spirit/game/scripts/cards/SWSH11/MissFortuneSisters_194.py b/spirit/game/scripts/cards/SWSH11/MissFortuneSisters_194.py index ebe6d64..def012c 100644 --- a/spirit/game/scripts/cards/SWSH11/MissFortuneSisters_194.py +++ b/spirit/game/scripts/cards/SWSH11/MissFortuneSisters_194.py @@ -1,5 +1,26 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.session.effects import is_item_card + + +async def miss_fortune_sisters(ctx): + """Look at the top 5 cards of your opponent's deck and discard any + number of Item cards you find there. Your opponent shuffles the other + cards back into their deck.""" + top = ctx.deck_top(5, player_id=ctx.opponent_id) + if not top: + return + items = [c for c in top if is_item_card(c)] + if items: + picks = await ctx.choose_cards( + items, len(items), minimum=0, + prompt="Choose any number of Item cards to discard", + display_cards=top if len(items) < len(top) else None, + ) + if picks: + await ctx.discard_cards(picks) + await ctx.shuffle_deck(ctx.opponent_id) + card = SupporterCardDef( guid="a1cf83da-6ba3-531b-ba7d-336c76668721", @@ -11,5 +32,5 @@ card = SupporterCardDef( collector_number=194, set_code="SWSH11", rarity=Rarities.RareUltra, - effect=unimplemented + effect=miss_fortune_sisters, ) diff --git a/spirit/game/scripts/cards/SWSH11/MissFortuneSisters_209.py b/spirit/game/scripts/cards/SWSH11/MissFortuneSisters_209.py index e966fa9..d1b49a2 100644 --- a/spirit/game/scripts/cards/SWSH11/MissFortuneSisters_209.py +++ b/spirit/game/scripts/cards/SWSH11/MissFortuneSisters_209.py @@ -1,5 +1,26 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.session.effects import is_item_card + + +async def miss_fortune_sisters(ctx): + """Look at the top 5 cards of your opponent's deck and discard any + number of Item cards you find there. Your opponent shuffles the other + cards back into their deck.""" + top = ctx.deck_top(5, player_id=ctx.opponent_id) + if not top: + return + items = [c for c in top if is_item_card(c)] + if items: + picks = await ctx.choose_cards( + items, len(items), minimum=0, + prompt="Choose any number of Item cards to discard", + display_cards=top if len(items) < len(top) else None, + ) + if picks: + await ctx.discard_cards(picks) + await ctx.shuffle_deck(ctx.opponent_id) + card = SupporterCardDef( guid="a294cf50-f40b-57f8-bee5-a7e9f5f520ac", @@ -11,5 +32,5 @@ card = SupporterCardDef( collector_number=209, set_code="SWSH11", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=miss_fortune_sisters, ) diff --git a/spirit/game/scripts/cards/SWSH11/MrMime_67.py b/spirit/game/scripts/cards/SWSH11/MrMime_67.py index 8131c81..bc34883 100644 --- a/spirit/game/scripts/cards/SWSH11/MrMime_67.py +++ b/spirit/game/scripts/cards/SWSH11/MrMime_67.py @@ -1,5 +1,29 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +import random + +from spirit.game.data_utils import PokemonCardDef, Attack from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import apply_protection + +_RPS = ["Rock", "Paper", "Scissors"] + + +async def tricky_slap(ctx): + """Play Rock-Paper-Scissors until someone wins; if the attacker wins, + shield this Pokemon from all damage/effects next turn.""" + await ctx.deal_damage() + prompt = "Rock-Paper-Scissors: choose your play." + for _ in range(100): + mine = await ctx.choose(prompt, _RPS) + # Opponent sees a shuffled order so an AI (auto-first-button) plays randomly. + order = random.sample(_RPS, 3) + pick = await ctx.choose(prompt, order, player_id=ctx.opponent_id) + theirs = _RPS.index(order[pick]) + if mine != theirs: + if (mine - theirs) % 3 == 1: + await apply_protection(ctx, prevent=True, effects_too=True) + return + prompt = "It's a tie! Rock-Paper-Scissors: choose your play." + card = PokemonCardDef( guid="21585a85-4646-52f6-9140-311efd26202e", @@ -26,10 +50,10 @@ card = PokemonCardDef( ), Attack( title="Tricky Slap", - game_text="You and your opponent play Rock-Paper-Scissors until someone wins. If you win, during your opponent's next turn, prevent all damage from and effects of attacks done to this Pok\u00e9mon.", + game_text="You and your opponent play Rock-Paper-Scissors until someone wins. If you win, during your opponent's next turn, prevent all damage from and effects of attacks done to this Pokémon.", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 2}, damage=90, - effect=unimplemented, + effect=tricky_slap, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH11/Nuzleaf_12.py b/spirit/game/scripts/cards/SWSH11/Nuzleaf_12.py index 66e7553..9938960 100644 --- a/spirit/game/scripts/cards/SWSH11/Nuzleaf_12.py +++ b/spirit/game/scripts/cards/SWSH11/Nuzleaf_12.py @@ -1,6 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.support_common import opponent_switches +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def push_down(ctx): + await ctx.deal_damage() + opp_active = ctx.opponent_active() + if opp_active is None or not ctx.opponent_bench() or ctx.effects_blocked(opp_active): + return + if await ctx.ask_yes_no("Have your opponent switch their Active Pokémon with 1 of their Benched Pokémon?"): + await opponent_switches(ctx) + card = PokemonCardDef( guid="bee5752c-6963-5467-b0f6-3faa51d03894", key="SWSH11", @@ -24,7 +34,7 @@ card = PokemonCardDef( game_text="You may have your opponent switch their Active Pok\u00e9mon with 1 of their Benched Pok\u00e9mon.", cost={PokemonTypes.GRASS: 1}, damage=30, - effect=unimplemented, + effect=push_down, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Orbeetle_20.py b/spirit/game/scripts/cards/SWSH11/Orbeetle_20.py index bca6331..beaf7ae 100644 --- a/spirit/game/scripts/cards/SWSH11/Orbeetle_20.py +++ b/spirit/game/scripts/cards/SWSH11/Orbeetle_20.py @@ -1,5 +1,28 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.trainers import is_energy_card +from spirit.game.card_effects.support_common import distribute_energy +from spirit.game.card_effects.attacks_common import count_energy, damage_per + + +async def jamming_attachment(ctx): + discard = [c for c in ctx.discard_pile(ctx.opponent_id) if is_energy_card(c)] + if not discard: + return + if not await ctx.ask_yes_no( + "Attach up to 3 Energy cards from your opponent's discard pile to " + "your opponent's Pokémon in any way you like?" + ): + return + picks = await ctx.choose_cards( + discard, 3, minimum=0, + prompt="Choose up to 3 Energy cards to attach.", + ) + candidates = ctx.opponent_pokemon_in_play() + if not picks or not candidates: + return + await distribute_energy(ctx, picks, candidates) + card = PokemonCardDef( guid="a1069e17-851c-5fd0-a33f-c5248c8df9f1", @@ -22,7 +45,8 @@ card = PokemonCardDef( Ability( title="Jamming Attachment", game_text="When you play this Pok\u00e9mon from your hand to evolve 1 of your Pok\u00e9mon during your turn, you may attach up to 3 Energy cards from your opponent's discard pile to your opponent's Pok\u00e9mon in any way you like.", - effect=unimplemented, + trigger=Triggers.ON_EVOLVE, + effect=jamming_attachment, ), Attack( title="Mysterious Wave", @@ -30,7 +54,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=30, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("defender"), 50, base=30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/PanicMask_165.py b/spirit/game/scripts/cards/SWSH11/PanicMask_165.py index 7270aa9..103b0e4 100644 --- a/spirit/game/scripts/cards/SWSH11/PanicMask_165.py +++ b/spirit/game/scripts/cards/SWSH11/PanicMask_165.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.card_effects.passives_common import prevent_damage_when +from spirit.game.data_utils import PokemonToolCardDef +from spirit.game.attributes import AttrID, Rarities + + +def _panic_mask_shield(calc, carrier): + if calc.target is not carrier: + return False + attacker = calc.attacker + if attacker is None: + return False + return attacker.get_attribute(AttrID.HP, 0) <= 40 + card = PokemonToolCardDef( guid="0ed49119-8252-5cd1-826a-ef12e56e80fb", @@ -11,5 +22,5 @@ card = PokemonToolCardDef( collector_number=165, set_code="SWSH11", rarity=Rarities.Uncommon, - effect=unimplemented + passive=prevent_damage_when(_panic_mask_shield), ) diff --git a/spirit/game/scripts/cards/SWSH11/Paras_4.py b/spirit/game/scripts/cards/SWSH11/Paras_4.py index 2dd3dc0..ca0dd3b 100644 --- a/spirit/game/scripts/cards/SWSH11/Paras_4.py +++ b/spirit/game/scripts/cards/SWSH11/Paras_4.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import heal_attack card = PokemonCardDef( guid="1d9c008f-33a7-5ec6-9a9b-888ad5836faf", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Heal 10 damage from this Pok\u00e9mon.", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=20, - effect=unimplemented, + effect=heal_attack(10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Parasect_5.py b/spirit/game/scripts/cards/SWSH11/Parasect_5.py index 307dba9..22f1b0a 100644 --- a/spirit/game/scripts/cards/SWSH11/Parasect_5.py +++ b/spirit/game/scripts/cards/SWSH11/Parasect_5.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import flip_bonus + + +async def lethargy_spores(ctx): + """On evolve: you may make both Active Pokemon Asleep and Poisoned.""" + if not await ctx.ask_yes_no("Make both Active Pokémon Asleep and Poisoned?"): + return + for target in (ctx.my_active(), ctx.opponent_active()): + if target is None: + continue + await ctx.apply_special_condition(target, SpecialConditions.ASLEEP) + await ctx.apply_special_condition(target, SpecialConditions.POISONED) card = PokemonCardDef( guid="ed449976-a244-5eda-9c4b-493f8e47ce51", @@ -22,7 +34,8 @@ card = PokemonCardDef( Ability( title="Lethargy Spores", game_text="When you play this Pok\u00e9mon from your hand to evolve 1 of your Pok\u00e9mon during your turn, you may make both Active Pok\u00e9mon Asleep and Poisoned.", - effect=unimplemented, + trigger=Triggers.ON_EVOLVE, + effect=lethargy_spores, ), Attack( title="X-Scissor", @@ -30,7 +43,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=50, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(50), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/PidgeotV_137.py b/spirit/game/scripts/cards/SWSH11/PidgeotV_137.py index 0ae1866..08021c0 100644 --- a/spirit/game/scripts/cards/SWSH11/PidgeotV_137.py +++ b/spirit/game/scripts/cards/SWSH11/PidgeotV_137.py @@ -1,5 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import full_stack +from spirit.game.card_effects.pokemon import in_active_spot +from spirit.game.card_effects.attacks_common import bonus_if + + +def _on_bench(board, player_id, pokemon): + return not in_active_spot(board, player_id, pokemon) + + +async def vanishing_wings(ctx): + """Once during your turn, if this Pokemon is on your Bench, you may + shuffle it and all attached cards into your deck.""" + if await ctx.ask_yes_no("Shuffle Pidgeot V and all attached cards into your deck?"): + await ctx.shuffle_into_deck(full_stack(ctx.source), ctx.player_id) + card = PokemonCardDef( guid="38a556a8-210f-50df-9cba-7703f3d9df49", @@ -22,7 +37,9 @@ card = PokemonCardDef( Ability( title="Vanishing Wings", game_text="Once during your turn, if this Pok\u00e9mon is on your Bench, you may shuffle it and all attached cards into your deck.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=_on_bench, + effect=vanishing_wings, ), Attack( title="Flight Surf", @@ -30,7 +47,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=80, damage_operator="+", - effect=unimplemented, + effect=bonus_if(lambda ctx: ctx.stadium_in_play() is not None, 80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/PidgeotV_188.py b/spirit/game/scripts/cards/SWSH11/PidgeotV_188.py index c328bd7..3097e6d 100644 --- a/spirit/game/scripts/cards/SWSH11/PidgeotV_188.py +++ b/spirit/game/scripts/cards/SWSH11/PidgeotV_188.py @@ -1,5 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import full_stack +from spirit.game.card_effects.pokemon import in_active_spot +from spirit.game.card_effects.attacks_common import bonus_if + + +def _on_bench(board, player_id, pokemon): + return not in_active_spot(board, player_id, pokemon) + + +async def vanishing_wings(ctx): + """Once during your turn, if this Pokemon is on your Bench, you may + shuffle it and all attached cards into your deck.""" + if await ctx.ask_yes_no("Shuffle Pidgeot V and all attached cards into your deck?"): + await ctx.shuffle_into_deck(full_stack(ctx.source), ctx.player_id) + card = PokemonCardDef( guid="f7f1ecfb-c51a-592c-8f53-c0699cf6d49a", @@ -22,7 +37,9 @@ card = PokemonCardDef( Ability( title="Vanishing Wings", game_text="Once during your turn, if this Pok\u00e9mon is on your Bench, you may shuffle it and all attached cards into your deck.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=_on_bench, + effect=vanishing_wings, ), Attack( title="Flight Surf", @@ -30,7 +47,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=80, damage_operator="+", - effect=unimplemented, + effect=bonus_if(lambda ctx: ctx.stadium_in_play() is not None, 80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Pikachu_52.py b/spirit/game/scripts/cards/SWSH11/Pikachu_52.py index ebb4100..6ad87ba 100644 --- a/spirit/game/scripts/cards/SWSH11/Pikachu_52.py +++ b/spirit/game/scripts/cards/SWSH11/Pikachu_52.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_or_nothing +from spirit.game.card_effects.passives_common import retreat_free_when +from spirit.game.card_effects.pokemon import is_energy_card + + +def _pika_dash(pokemon, carrier): + return pokemon is carrier and any(is_energy_card(c) for c in carrier.children) card = PokemonCardDef( guid="53432f36-8edc-55f0-bcc6-052b1938bc91", @@ -21,14 +28,14 @@ card = PokemonCardDef( Ability( title="Pika Dash", game_text="If this Pok\u00e9mon has any Energy attached, it has no Retreat Cost.", - effect=unimplemented, + passive=retreat_free_when(_pika_dash), ), Attack( title="Whimsy Tackle", game_text="Flip a coin. If tails, this attack does nothing.", cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 2}, damage=50, - effect=unimplemented, + effect=flip_or_nothing(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Politoed_32.py b/spirit/game/scripts/cards/SWSH11/Politoed_32.py index 2bc6c34..73ef10f 100644 --- a/spirit/game/scripts/cards/SWSH11/Politoed_32.py +++ b/spirit/game/scripts/cards/SWSH11/Politoed_32.py @@ -1,6 +1,23 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, def_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def lordly_songleader(ctx): + bench_names = { + card_def.display_name + for card_def in (def_for(p.archetype_id) for p in ctx.my_bench()) + if card_def + } + bonus = 0 + if "Poliwag" in bench_names: + bonus += 60 + if "Poliwhirl" in bench_names: + bonus += 90 + if "Poliwrath" in bench_names: + bonus += 120 + await ctx.deal_damage(10 + bonus) + + card = PokemonCardDef( guid="5e379c27-b182-5d29-bd53-6242eb15e1ee", key="SWSH11", @@ -25,7 +42,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=10, damage_operator="+", - effect=unimplemented, + effect=lordly_songleader, ), Attack( title="Hydro Splash", diff --git a/spirit/game/scripts/cards/SWSH11/Poliwag_30.py b/spirit/game/scripts/cards/SWSH11/Poliwag_30.py index 4e3d56b..f3e78fe 100644 --- a/spirit/game/scripts/cards/SWSH11/Poliwag_30.py +++ b/spirit/game/scripts/cards/SWSH11/Poliwag_30.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import flip_protection card = PokemonCardDef( guid="a07e154d-c5e1-55f0-a43a-0582312ed61e", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, during your opponent's next turn, prevent all damage from and effects of attacks done to this Pok\u00e9mon.", cost={PokemonTypes.WATER: 1}, damage=10, - effect=unimplemented, + effect=flip_protection(prevent=True, effects_too=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Poliwhirl_31.py b/spirit/game/scripts/cards/SWSH11/Poliwhirl_31.py index f85ffce..d4b0b2b 100644 --- a/spirit/game/scripts/cards/SWSH11/Poliwhirl_31.py +++ b/spirit/game/scripts/cards/SWSH11/Poliwhirl_31.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="58d9da12-aa28-5239-98b0-3c97b9f00a60", @@ -30,7 +31,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=50, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=2, per_heads=50), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Poliwrath_85.py b/spirit/game/scripts/cards/SWSH11/Poliwrath_85.py index 5013450..5da4d5a 100644 --- a/spirit/game/scripts/cards/SWSH11/Poliwrath_85.py +++ b/spirit/game/scripts/cards/SWSH11/Poliwrath_85.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack + + +async def splash_loop(ctx): + """160. Put 2 Energy attached to this Pokémon into your hand.""" + await ctx.deal_damage() + energies = ctx.attached_energies(ctx.source) + if not energies: + return + count = min(2, len(energies)) + picks = await ctx.choose_cards( + energies, count, minimum=count, + prompt="Choose Energy to put into your hand", + ) + await ctx.put_in_hand(picks, reveal=False) + card = PokemonCardDef( guid="cfd4bfd0-7fbb-556d-a67d-41323c344d6d", @@ -24,14 +40,14 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Confused.", cost={PokemonTypes.WATER: 1}, damage=60, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED), ), Attack( title="Splash Loop", game_text="Put 2 Energy attached to this Pok\u00e9mon into your hand.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=160, - effect=unimplemented, + effect=splash_loop, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Porygon2_141.py b/spirit/game/scripts/cards/SWSH11/Porygon2_141.py index fbbd3e4..bdade36 100644 --- a/spirit/game/scripts/cards/SWSH11/Porygon2_141.py +++ b/spirit/game/scripts/cards/SWSH11/Porygon2_141.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID, TrainerType +from spirit.game.card_effects.attacks_common import damage_per + + +def _is_pokemon_tool_card(card): + return card.get_attribute(AttrID.TRAINER_TYPE) in ( + TrainerType.POKEMON_TOOL.value, TrainerType.POKEMON_TOOL_F.value, + ) + + +def _tool_count_in_lost_zones(ctx): + return sum( + 1 for pid in (ctx.player_id, ctx.opponent_id) + for c in ctx.lost_zone(pid) + if _is_pokemon_tool_card(c) + ) + card = PokemonCardDef( guid="666df858-b9db-54ae-9f46-e5a334267fc2", @@ -21,11 +37,11 @@ card = PokemonCardDef( abilities=[ Attack( title="Garbage Attack", - game_text="This attack does 20 damage for each Pok\u00e9mon Tool card in the Lost Zone (both yours and your opponent's).", + game_text="This attack does 20 damage for each Pokémon Tool card in the Lost Zone (both yours and your opponent's).", cost={PokemonTypes.COLORLESS: 1}, damage=20, damage_operator="x", - effect=unimplemented, + effect=damage_per(_tool_count_in_lost_zones, 20), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH11/PorygonZ_142.py b/spirit/game/scripts/cards/SWSH11/PorygonZ_142.py index 7d3192c..03c8385 100644 --- a/spirit/game/scripts/cards/SWSH11/PorygonZ_142.py +++ b/spirit/game/scripts/cards/SWSH11/PorygonZ_142.py @@ -1,4 +1,5 @@ from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.pokemon import downgrading_beam from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -23,7 +24,7 @@ card = PokemonCardDef( title="Downgrading Beam", game_text="Devolve 1 of your opponent's evolved Pok\u00e9mon by removing any number of Evolution cards from it. Your opponent shuffles those cards into their deck.", cost={PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + effect=downgrading_beam, ), Attack( title="Power Beam", diff --git a/spirit/game/scripts/cards/SWSH11/Porygon_140.py b/spirit/game/scripts/cards/SWSH11/Porygon_140.py index a2c7203..cd052f7 100644 --- a/spirit/game/scripts/cards/SWSH11/Porygon_140.py +++ b/spirit/game/scripts/cards/SWSH11/Porygon_140.py @@ -1,6 +1,31 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def branch_calculation(ctx): + """Look at the top 4 cards of either player's deck, put back in any order.""" + choice = await ctx.choose( + "Look at the top 4 cards of which deck?", + ["Your deck", "Opponent's deck"], use_panel=False, + ) + target_pid = ctx.player_id if choice == 0 else ctx.opponent_id + top = ctx.deck_top(4, target_pid) + if len(top) <= 1: + return + order = await ctx.choose_cards( + top, len(top), minimum=len(top), ordered=True, player_id=ctx.player_id, + prompt="Put the cards back in any order", + ) + if not order: + order = top + deck = ctx.board.find_player_area(target_pid, "deck") + for card in order: + if card in deck.children: + deck.children.remove(card) + for card in reversed(order): + deck.children.append(card) + + card = PokemonCardDef( guid="efa4694c-67df-5fcb-aee3-9f4adfba1a1a", key="SWSH11", @@ -22,7 +47,7 @@ card = PokemonCardDef( title="Branch Calculation", game_text="Look at the top 4 cards of either player's deck and put them back in any order.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=branch_calculation, ), Attack( title="Beam", @@ -30,4 +55,4 @@ card = PokemonCardDef( damage=10, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH11/Pyroar_29.py b/spirit/game/scripts/cards/SWSH11/Pyroar_29.py index ff20232..8dea06d 100644 --- a/spirit/game/scripts/cards/SWSH11/Pyroar_29.py +++ b/spirit/game/scripts/cards/SWSH11/Pyroar_29.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.session.passives import Passive + + +class ScorchingAuraPassive(Passive): + def modify_burn_counters(self, counters, pokemon, carrier): + if pokemon.owning_player_id != carrier.owning_player_id: + return 4 + return counters + card = PokemonCardDef( guid="5ab3f627-e8a9-53d1-bf94-d55f130bf78b", @@ -22,14 +32,14 @@ card = PokemonCardDef( Ability( title="Scorching Aura", game_text="During Pok\u00e9mon Checkup, put 4 damage counters on your opponent's Burned Pok\u00e9mon instead of 2.", - effect=unimplemented, + passive=ScorchingAuraPassive(), ), Attack( title="Fire Fang", game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Burned.", cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 2}, damage=90, - effect=unimplemented, + effect=condition_attack(SpecialConditions.BURNED, flip=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/RadiantGardevoir_69.py b/spirit/game/scripts/cards/SWSH11/RadiantGardevoir_69.py index fc1378f..da3b4c6 100644 --- a/spirit/game/scripts/cards/SWSH11/RadiantGardevoir_69.py +++ b/spirit/game/scripts/cards/SWSH11/RadiantGardevoir_69.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, is_pokemon_v from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy +from spirit.game.card_effects.passives_common import takes_less_passive card = PokemonCardDef( guid="bb21c8f8-7fe3-58e6-8e3f-e062f9f2a50d", @@ -21,7 +23,10 @@ card = PokemonCardDef( Ability( title="Loving Veil", game_text="All of your Pok\u00e9mon take 20 less damage from attacks from your opponent's Pok\u00e9mon V (after applying Weakness and Resistance).", - effect=unimplemented, + passive=takes_less_passive( + 20, protects="team", + attacker_pred=lambda p: is_pokemon_v(p.archetype_id), + ), ), Attack( title="Psychic", @@ -29,7 +34,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 2}, damage=70, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("defender"), 20, base=70), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/RadiantHisuianSneasler_123.py b/spirit/game/scripts/cards/SWSH11/RadiantHisuianSneasler_123.py index f441137..13435aa 100644 --- a/spirit/game/scripts/cards/SWSH11/RadiantHisuianSneasler_123.py +++ b/spirit/game/scripts/cards/SWSH11/RadiantHisuianSneasler_123.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.pokemon import more_poison card = PokemonCardDef( guid="1b3d96e7-5b0f-5b4e-9c89-2413908a0fcd", @@ -21,14 +23,15 @@ card = PokemonCardDef( Ability( title="Poison Peak", game_text="During Pok\u00e9mon Checkup, put 2 more damage counters on your opponent's Poisoned Pok\u00e9mon.", - effect=unimplemented, + trigger=Triggers.BETWEEN_TURNS, + effect=more_poison, ), Attack( title="Poison Jab", game_text="Your opponent's Active Pok\u00e9mon is now Poisoned.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 2}, damage=90, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/RadiantSteelix_124.py b/spirit/game/scripts/cards/SWSH11/RadiantSteelix_124.py index 9855717..e5352e1 100644 --- a/spirit/game/scripts/cards/SWSH11/RadiantSteelix_124.py +++ b/spirit/game/scripts/cards/SWSH11/RadiantSteelix_124.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import attach_from_discard +from spirit.game.card_effects.trainers import is_metal_energy_card +from spirit.game.card_effects.pokemon import is_energy_card + + +async def destructive_finish(ctx): + """Discard the top of your deck down to 1 card; +30 damage per Energy discarded.""" + deck_size = len(ctx.deck()) + mill_count = max(0, deck_size - 1) + cards = ctx.deck_top(mill_count) + await ctx.discard_cards(cards) + energy_count = sum(1 for c in cards if is_energy_card(c)) + await ctx.deal_damage(60 + 30 * energy_count) + card = PokemonCardDef( guid="7fd5c2df-c315-5671-bd24-b18256169dce", @@ -24,7 +38,11 @@ card = PokemonCardDef( game_text="Attach up to 2 Metal Energy cards from your discard pile to this Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, damage=20, - effect=unimplemented, + effect=attach_from_discard( + predicate=is_metal_energy_card, count=2, target="self", + minimum=0, + prompt="Choose up to 2 Metal Energy from your discard pile to attach", + ), ), Attack( title="Destructive Finish", @@ -32,7 +50,7 @@ card = PokemonCardDef( cost={PokemonTypes.METAL: 2, PokemonTypes.COLORLESS: 1}, damage=60, damage_operator="+", - effect=unimplemented, + effect=destructive_finish, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Raichu_53.py b/spirit/game/scripts/cards/SWSH11/Raichu_53.py index 1c039b4..2c613b3 100644 --- a/spirit/game/scripts/cards/SWSH11/Raichu_53.py +++ b/spirit/game/scripts/cards/SWSH11/Raichu_53.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack, bonus_if + + +def _vstar_power_used(ctx) -> bool: + return ctx.player_id in ctx.session.turn_state.vstar_used + card = PokemonCardDef( guid="466b339d-d657-5893-aec3-8a41c253375b", @@ -24,7 +30,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.LIGHTNING: 1}, damage=30, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), Attack( title="Ace Spark", @@ -32,7 +38,7 @@ card = PokemonCardDef( cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 2}, damage=100, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_vstar_power_used, 120), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Relicanth_101.py b/spirit/game/scripts/cards/SWSH11/Relicanth_101.py index 84f48c7..5208ddc 100644 --- a/spirit/game/scripts/cards/SWSH11/Relicanth_101.py +++ b/spirit/game/scripts/cards/SWSH11/Relicanth_101.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.card_effects.support_common import recover_from_discard card = PokemonCardDef( guid="2ad74522-fb3a-5063-a1ba-96740c750070", @@ -22,7 +24,7 @@ card = PokemonCardDef( title="Into the Deep", game_text="Put up to 2 basic Energy cards from your discard pile into your hand.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=recover_from_discard(is_basic_energy_card, count=2, to="hand"), ), Attack( title="Tackle", diff --git a/spirit/game/scripts/cards/SWSH11/Rhydon_90.py b/spirit/game/scripts/cards/SWSH11/Rhydon_90.py index 4b7ecf2..93dce31 100644 --- a/spirit/game/scripts/cards/SWSH11/Rhydon_90.py +++ b/spirit/game/scripts/cards/SWSH11/Rhydon_90.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack card = PokemonCardDef( guid="b67256c6-72dc-52ef-907f-59a9e60ff77e", @@ -29,7 +30,7 @@ card = PokemonCardDef( game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 3}, damage=120, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Rhyhorn_89.py b/spirit/game/scripts/cards/SWSH11/Rhyhorn_89.py index 4cb9d32..67bca47 100644 --- a/spirit/game/scripts/cards/SWSH11/Rhyhorn_89.py +++ b/spirit/game/scripts/cards/SWSH11/Rhyhorn_89.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack card = PokemonCardDef( guid="8d359812-4579-51a5-a1c1-2585da1e81a2", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="This Pok\u00e9mon also does 20 damage to itself.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 2}, damage=70, - effect=unimplemented, + effect=recoil_attack(20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Rhyperior_91.py b/spirit/game/scripts/cards/SWSH11/Rhyperior_91.py index 020e10e..0cd9883 100644 --- a/spirit/game/scripts/cards/SWSH11/Rhyperior_91.py +++ b/spirit/game/scripts/cards/SWSH11/Rhyperior_91.py @@ -1,5 +1,23 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack +from spirit.game.card_effects.pokemon import is_energy_card + + +async def geo_cannon(ctx): + """50, +100 and attach the top card if it's an Energy card (else discard it).""" + top = ctx.deck_top(1) + if not top: + await ctx.deal_damage(50) + return + card = top[0] + if is_energy_card(card): + await ctx.attach_energy(card, ctx.attacker) + await ctx.deal_damage(150) + else: + await ctx.discard_cards([card]) + await ctx.deal_damage(50) + card = PokemonCardDef( guid="2511b893-133b-552f-bd82-df0be95def33", @@ -25,14 +43,14 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1}, damage=50, damage_operator="+", - effect=unimplemented, + effect=geo_cannon, ), Attack( title="Rocky Tackle", game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 3}, damage=180, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Riley_166.py b/spirit/game/scripts/cards/SWSH11/Riley_166.py index a4c6f58..284fd16 100644 --- a/spirit/game/scripts/cards/SWSH11/Riley_166.py +++ b/spirit/game/scripts/cards/SWSH11/Riley_166.py @@ -1,6 +1,26 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities + +async def _riley_effect(ctx): + """Reveal the top 5 cards of your deck; your opponent chooses 2 to + discard, the rest go to your hand.""" + top = ctx.deck_top(5) + if not top: + return + count = min(2, len(top)) + discard_picks = await ctx.choose_cards( + top, count, player_id=ctx.opponent_id, + prompt="Choose 2 cards to discard.", + display_cards=top, + ) + keep = [c for c in top if c not in discard_picks] + if discard_picks: + await ctx.discard_cards(discard_picks) + if keep: + await ctx.put_in_hand(keep, reveal=False) + + card = SupporterCardDef( guid="882fea74-0f0d-57a7-a1a5-b537ffcca399", key="SWSH11", @@ -11,5 +31,5 @@ card = SupporterCardDef( collector_number=166, set_code="SWSH11", rarity=Rarities.Uncommon, - effect=unimplemented + effect=_riley_effect, ) diff --git a/spirit/game/scripts/cards/SWSH11/Rockruff_109.py b/spirit/game/scripts/cards/SWSH11/Rockruff_109.py index c423d82..57aa7bd 100644 --- a/spirit/game/scripts/cards/SWSH11/Rockruff_109.py +++ b/spirit/game/scripts/cards/SWSH11/Rockruff_109.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import draw_attack card = PokemonCardDef( guid="de453574-d4e1-5221-aca4-f1562808a243", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Double Draw", game_text="Draw 2 cards.", cost={PokemonTypes.FIGHTING: 1}, - effect=unimplemented, + effect=draw_attack(2), ), Attack( title="Rear Kick", diff --git a/spirit/game/scripts/cards/SWSH11/Roselia_14.py b/spirit/game/scripts/cards/SWSH11/Roselia_14.py index 532de85..fd80b6f 100644 --- a/spirit/game/scripts/cards/SWSH11/Roselia_14.py +++ b/spirit/game/scripts/cards/SWSH11/Roselia_14.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="60a2b34a-3189-5574-b1e7-23475041719e", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Soothing Scent", game_text="Your opponent's Active Pok\u00e9mon is now Asleep.", cost={PokemonTypes.GRASS: 1}, - effect=unimplemented, + effect=condition_attack(SpecialConditions.ASLEEP), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Roserade_15.py b/spirit/game/scripts/cards/SWSH11/Roserade_15.py index 4ca15e7..21fb97e 100644 --- a/spirit/game/scripts/cards/SWSH11/Roserade_15.py +++ b/spirit/game/scripts/cards/SWSH11/Roserade_15.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions, AttrID +from spirit.game.card_effects.attacks_common import condition_attack + + +async def assassins_rose(ctx): + """60. If the opponent's Active is affected by a Special Condition, + also do 60 to 1 of the opponent's Benched Pokemon (no W/R).""" + defender = ctx.defender + conditions = defender.get_attribute(AttrID.SPECIAL_CONDITIONS) if defender else None + affected = bool(conditions) + await ctx.deal_damage() + if affected: + bench = ctx.opponent_bench() + if bench: + target = await ctx.choose_pokemon(bench, "Choose 1 of your opponent's Benched Pokémon") + if target is not None: + await ctx.deal_damage(60, target=target, apply_modifiers=False) + card = PokemonCardDef( guid="0874fc18-71fa-5571-bf00-ce33b2b9defa", @@ -24,14 +41,14 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Poisoned.", cost={PokemonTypes.GRASS: 1}, damage=30, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED), ), Attack( title="Assassin's Rose", game_text="If your opponent's Active Pok\u00e9mon is affected by a Special Condition, this attack also does 60 damage to 1 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=60, - effect=unimplemented, + effect=assassins_rose, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/RotomV_176.py b/spirit/game/scripts/cards/SWSH11/RotomV_176.py index cc5e140..37d05ac 100644 --- a/spirit/game/scripts/cards/SWSH11/RotomV_176.py +++ b/spirit/game/scripts/cards/SWSH11/RotomV_176.py @@ -1,5 +1,31 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID, TrainerType + + +def _is_pokemon_tool_card(card): + return card.get_attribute(AttrID.TRAINER_TYPE) in ( + TrainerType.POKEMON_TOOL.value, TrainerType.POKEMON_TOOL_F.value, + ) + + +async def instant_charge(ctx): + """Draw 3 cards.""" + await ctx.draw_cards(3) + + +async def scrap_short(ctx): + """40. Put any number of Pokémon Tool cards from discard into the Lost Zone; +40 damage per card moved this way.""" + tools = [c for c in ctx.discard_pile() if _is_pokemon_tool_card(c)] + picks = [] + if tools: + picks = await ctx.choose_cards( + tools, len(tools), minimum=0, + prompt="Put any number of Pokémon Tool cards from your discard pile in the Lost Zone.", + ) + if picks: + await ctx.move_to_lost_zone(picks) + await ctx.deal_damage(40 + 40 * len(picks)) + card = PokemonCardDef( guid="c8d915de-3d25-5682-9ccb-a20870d35f49", @@ -21,15 +47,17 @@ card = PokemonCardDef( Ability( title="Instant Charge", game_text="Once during your turn, you may draw 3 cards. If you use this Ability, your turn ends.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + ends_turn=True, + effect=instant_charge, ), Attack( title="Scrap Short", - game_text="Put any number of Pok\u00e9mon Tool cards from your discard pile in the Lost Zone. This attack does 40 more damage for each card you put in the Lost Zone in this way.", + game_text="Put any number of Pokémon Tool cards from your discard pile in the Lost Zone. This attack does 40 more damage for each card you put in the Lost Zone in this way.", cost={PokemonTypes.LIGHTNING: 2}, damage=40, damage_operator="+", - effect=unimplemented, + effect=scrap_short, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH11/RotomV_177.py b/spirit/game/scripts/cards/SWSH11/RotomV_177.py index 9fa11aa..6b8f8ca 100644 --- a/spirit/game/scripts/cards/SWSH11/RotomV_177.py +++ b/spirit/game/scripts/cards/SWSH11/RotomV_177.py @@ -1,5 +1,31 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID, TrainerType + + +def _is_pokemon_tool_card(card): + return card.get_attribute(AttrID.TRAINER_TYPE) in ( + TrainerType.POKEMON_TOOL.value, TrainerType.POKEMON_TOOL_F.value, + ) + + +async def instant_charge(ctx): + """Draw 3 cards.""" + await ctx.draw_cards(3) + + +async def scrap_short(ctx): + """40. Put any number of Pokémon Tool cards from discard into the Lost Zone; +40 damage per card moved this way.""" + tools = [c for c in ctx.discard_pile() if _is_pokemon_tool_card(c)] + picks = [] + if tools: + picks = await ctx.choose_cards( + tools, len(tools), minimum=0, + prompt="Put any number of Pokémon Tool cards from your discard pile in the Lost Zone.", + ) + if picks: + await ctx.move_to_lost_zone(picks) + await ctx.deal_damage(40 + 40 * len(picks)) + card = PokemonCardDef( guid="4cb2dd9d-aa7a-536f-8d31-40135a080fd9", @@ -21,15 +47,17 @@ card = PokemonCardDef( Ability( title="Instant Charge", game_text="Once during your turn, you may draw 3 cards. If you use this Ability, your turn ends.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + ends_turn=True, + effect=instant_charge, ), Attack( title="Scrap Short", - game_text="Put any number of Pok\u00e9mon Tool cards from your discard pile in the Lost Zone. This attack does 40 more damage for each card you put in the Lost Zone in this way.", + game_text="Put any number of Pokémon Tool cards from your discard pile in the Lost Zone. This attack does 40 more damage for each card you put in the Lost Zone in this way.", cost={PokemonTypes.LIGHTNING: 2}, damage=40, damage_operator="+", - effect=unimplemented, + effect=scrap_short, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH11/RotomV_58.py b/spirit/game/scripts/cards/SWSH11/RotomV_58.py index 9a5ef64..d438fe5 100644 --- a/spirit/game/scripts/cards/SWSH11/RotomV_58.py +++ b/spirit/game/scripts/cards/SWSH11/RotomV_58.py @@ -1,5 +1,31 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID, TrainerType + + +def _is_pokemon_tool_card(card): + return card.get_attribute(AttrID.TRAINER_TYPE) in ( + TrainerType.POKEMON_TOOL.value, TrainerType.POKEMON_TOOL_F.value, + ) + + +async def instant_charge(ctx): + """Draw 3 cards.""" + await ctx.draw_cards(3) + + +async def scrap_short(ctx): + """40. Put any number of Pokémon Tool cards from discard into the Lost Zone; +40 damage per card moved this way.""" + tools = [c for c in ctx.discard_pile() if _is_pokemon_tool_card(c)] + picks = [] + if tools: + picks = await ctx.choose_cards( + tools, len(tools), minimum=0, + prompt="Put any number of Pokémon Tool cards from your discard pile in the Lost Zone.", + ) + if picks: + await ctx.move_to_lost_zone(picks) + await ctx.deal_damage(40 + 40 * len(picks)) + card = PokemonCardDef( guid="0c092723-8f2e-5403-9617-2f0369019af6", @@ -21,15 +47,17 @@ card = PokemonCardDef( Ability( title="Instant Charge", game_text="Once during your turn, you may draw 3 cards. If you use this Ability, your turn ends.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + ends_turn=True, + effect=instant_charge, ), Attack( title="Scrap Short", - game_text="Put any number of Pok\u00e9mon Tool cards from your discard pile in the Lost Zone. This attack does 40 more damage for each card you put in the Lost Zone in this way.", + game_text="Put any number of Pokémon Tool cards from your discard pile in the Lost Zone. This attack does 40 more damage for each card you put in the Lost Zone in this way.", cost={PokemonTypes.LIGHTNING: 2}, damage=40, damage_operator="+", - effect=unimplemented, + effect=scrap_short, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH11/Seadra_36.py b/spirit/game/scripts/cards/SWSH11/Seadra_36.py index fb19a8c..5586d99 100644 --- a/spirit/game/scripts/cards/SWSH11/Seadra_36.py +++ b/spirit/game/scripts/cards/SWSH11/Seadra_36.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import count_energy +from spirit.game.card_effects.passives_common import flip_protection + + +async def hydro_jet(ctx): + amount = 20 * count_energy("self", energy_type=PokemonTypes.WATER)(ctx) + candidates = ctx.opponent_pokemon_in_play() + if not candidates: + return + target = await ctx.choose_pokemon( + candidates, "Choose 1 of your opponent's Pokémon") + if target is not None: + await ctx.deal_damage(amount, target=target) card = PokemonCardDef( guid="792c1768-a945-512b-875e-15b44660a7a0", @@ -24,13 +37,13 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, during your opponent's next turn, prevent all damage from and effects of attacks done to this Pok\u00e9mon.", cost={PokemonTypes.WATER: 1}, damage=10, - effect=unimplemented, + effect=flip_protection(prevent=True, effects_too=True), ), Attack( title="Hydro Jet", game_text="This attack does 20 damage to 1 of your opponent's Pok\u00e9mon for each Water Energy attached to this Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.WATER: 1}, - effect=unimplemented, + effect=hydro_jet, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Seedot_11.py b/spirit/game/scripts/cards/SWSH11/Seedot_11.py index 8462b3b..5876316 100644 --- a/spirit/game/scripts/cards/SWSH11/Seedot_11.py +++ b/spirit/game/scripts/cards/SWSH11/Seedot_11.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import heal_attack card = PokemonCardDef( guid="c75c5d10-052a-50c1-860f-f024b5c32620", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Heal 10 damage from this Pok\u00e9mon.", cost={PokemonTypes.GRASS: 1}, damage=10, - effect=unimplemented, + effect=heal_attack(10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Seviper_116.py b/spirit/game/scripts/cards/SWSH11/Seviper_116.py index 546a4ce..30d61aa 100644 --- a/spirit/game/scripts/cards/SWSH11/Seviper_116.py +++ b/spirit/game/scripts/cards/SWSH11/Seviper_116.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import switch_self_attack +from spirit.game.card_effects.trainers import is_darkness_pokemon card = PokemonCardDef( guid="d1dc3ce1-d055-5cf0-a510-ede30fc3df28", @@ -23,7 +25,7 @@ card = PokemonCardDef( game_text="Switch this Pok\u00e9mon with 1 of your Benched Darkness Pok\u00e9mon.", cost={PokemonTypes.DARKNESS: 2}, damage=60, - effect=unimplemented, + effect=switch_self_attack(bench_predicate=is_darkness_pokemon), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Shiftry_13.py b/spirit/game/scripts/cards/SWSH11/Shiftry_13.py index 86d0fef..0af705a 100644 --- a/spirit/game/scripts/cards/SWSH11/Shiftry_13.py +++ b/spirit/game/scripts/cards/SWSH11/Shiftry_13.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import opponent_switches, remove_self_from_play + + +async def fan_tornado(ctx): + """50. You may have your opponent switch their Active with 1 of their Benched Pokemon.""" + await ctx.deal_damage() + if not ctx.opponent_bench(): + return + if await ctx.ask_yes_no( + "Have your opponent switch their Active Pokémon with 1 of their Benched Pokémon?"): + await opponent_switches(ctx) + card = PokemonCardDef( guid="e1e3b13c-790e-500a-b26e-e72d8bd3af1a", @@ -21,17 +33,17 @@ card = PokemonCardDef( abilities=[ Attack( title="Fan Tornado", - game_text="You may have your opponent switch their Active Pok\u00e9mon with 1 of their Benched Pok\u00e9mon.", + game_text="You may have your opponent switch their Active Pokémon with 1 of their Benched Pokémon.", cost={PokemonTypes.GRASS: 1}, damage=50, - effect=unimplemented, + effect=fan_tornado, ), Attack( title="Tearing Gust", - game_text="Put this Pok\u00e9mon and all attached cards in the Lost Zone.", + game_text="Put this Pokémon and all attached cards in the Lost Zone.", cost={PokemonTypes.GRASS: 1}, damage=210, - effect=unimplemented, + effect=remove_self_from_play("lost_zone"), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH11/Silcoon_7.py b/spirit/game/scripts/cards/SWSH11/Silcoon_7.py index b5c0fd8..0766083 100644 --- a/spirit/game/scripts/cards/SWSH11/Silcoon_7.py +++ b/spirit/game/scripts/cards/SWSH11/Silcoon_7.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import lock_defender_attacks + + +async def entangling_string(ctx): + heads = await ctx.flip_coins(1, "Entangling String") + if heads[0]: + lock_defender_attacks(ctx) card = PokemonCardDef( guid="17bf77ac-5e9f-582f-a0a5-87a9695f77a0", @@ -23,7 +30,7 @@ card = PokemonCardDef( title="Entangling String", game_text="Flip a coin. If heads, during your opponent's next turn, the Defending Pok\u00e9mon can't attack.", cost={PokemonTypes.GRASS: 1}, - effect=unimplemented, + effect=entangling_string, ), Attack( title="Ram", diff --git a/spirit/game/scripts/cards/SWSH11/Slugma_21.py b/spirit/game/scripts/cards/SWSH11/Slugma_21.py index 0e1962b..2c8fd2b 100644 --- a/spirit/game/scripts/cards/SWSH11/Slugma_21.py +++ b/spirit/game/scripts/cards/SWSH11/Slugma_21.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID, CardType +from spirit.game.card_effects.support_common import attach_from_discard + + +def _is_fire_energy_card(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return card.get_attribute(AttrID.CARD_TYPE) == CardType.ENERGY.value \ + and PokemonTypes.FIRE.value in types + + +draw_in = attach_from_discard( + predicate=_is_fire_energy_card, count=1, target="self", minimum=1, + prompt="Choose a Fire Energy card to attach to this Pokémon", +) card = PokemonCardDef( guid="c4f36c21-32b9-5413-b004-1fff5bc3f55a", @@ -22,7 +35,7 @@ card = PokemonCardDef( title="Draw In", game_text="Attach a Fire Energy card from your discard pile to this Pok\u00e9mon.", cost={PokemonTypes.FIRE: 1}, - effect=unimplemented, + effect=draw_in, ), Attack( title="Combustion", diff --git a/spirit/game/scripts/cards/SWSH11/Spectrier_81.py b/spirit/game/scripts/cards/SWSH11/Spectrier_81.py index 85c8b40..b8168d7 100644 --- a/spirit/game/scripts/cards/SWSH11/Spectrier_81.py +++ b/spirit/game/scripts/cards/SWSH11/Spectrier_81.py @@ -1,6 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def night_footsteps(ctx): + """Choose 2 of your opponent's Pokemon and put 2 damage counters on each.""" + candidates = ctx.opponent_pokemon_in_play() + if not candidates: + return + count = min(2, len(candidates)) + targets = await ctx.choose_cards( + candidates, count, prompt="Choose 2 of your opponent's Pokémon" + ) + for target in targets: + await ctx.deal_damage(20, target=target, apply_modifiers=False, as_counters=True) + + card = PokemonCardDef( guid="6876c033-5158-5bb9-b7fd-51b525cb65b1", key="SWSH11", @@ -23,14 +37,14 @@ card = PokemonCardDef( title="Night Footsteps", game_text="Choose 2 of your opponent's Pok\u00e9mon and put 2 damage counters on each of them.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=night_footsteps, ), Attack( title="Phantom Strike", game_text="During your next turn, this Pok\u00e9mon can't use Phantom Strike.", cost={PokemonTypes.PSYCHIC: 2, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Spinarak_112.py b/spirit/game/scripts/cards/SWSH11/Spinarak_112.py index 1922beb..934afc3 100644 --- a/spirit/game/scripts/cards/SWSH11/Spinarak_112.py +++ b/spirit/game/scripts/cards/SWSH11/Spinarak_112.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="862c1c07-5687-578c-a458-4e838a8e5107", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Poisoned.", cost={PokemonTypes.DARKNESS: 1}, damage=10, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Spiritomb_117.py b/spirit/game/scripts/cards/SWSH11/Spiritomb_117.py index 9cbbb93..b816e2c 100644 --- a/spirit/game/scripts/cards/SWSH11/Spiritomb_117.py +++ b/spirit/game/scripts/cards/SWSH11/Spiritomb_117.py @@ -1,5 +1,26 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers, def_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_discard + + +def _is_spiritomb(card): + definition = def_for(card.archetype_id) + return definition is not None and getattr(definition, "display_name", None) == "Spiritomb" + + +async def cursed_message(ctx): + """If Knocked Out by damage from an attack from your opponent's Pokemon: + search the deck for a card and put it into your hand. Then shuffle.""" + if not ctx.ko_from_attack: + return + picks = await ctx.search_deck( + count=1, minimum=0, prompt="Choose a card to put into your hand.", + ) + await ctx.put_in_hand(picks, reveal=False) + await ctx.shuffle_deck() + + +chain_of_spirits = damage_per(count_discard("mine", _is_spiritomb), 60, base=10) card = PokemonCardDef( guid="ed6b43c2-d914-59d9-a17c-dba4c5b9ad99", @@ -21,7 +42,8 @@ card = PokemonCardDef( Ability( title="Cursed Message", game_text="If this Pok\u00e9mon is Knocked Out by damage from an attack from your opponent's Pok\u00e9mon, search your deck for a card and put it into your hand. Then, shuffle your deck.", - effect=unimplemented, + trigger=Triggers.ON_KNOCKED_OUT, + effect=cursed_message, ), Attack( title="Chain of Spirits", @@ -29,7 +51,7 @@ card = PokemonCardDef( cost={PokemonTypes.DARKNESS: 2}, damage=10, damage_operator="+", - effect=unimplemented, + effect=chain_of_spirits, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Stonjourner_111.py b/spirit/game/scripts/cards/SWSH11/Stonjourner_111.py index 257f653..e65f26d 100644 --- a/spirit/game/scripts/cards/SWSH11/Stonjourner_111.py +++ b/spirit/game/scripts/cards/SWSH11/Stonjourner_111.py @@ -1,5 +1,38 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.trainers import is_energy_card +from spirit.game.card_effects.support_common import distribute_energy + + +def _is_fighting_energy_card(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_energy_card(card) and PokemonTypes.FIGHTING.value in types + + +async def power_stone(ctx): + """Attach up to 2 Fighting Energy cards from your hand to your Pokemon in any way you like.""" + hand_fighting = [c for c in ctx.hand() if _is_fighting_energy_card(c)] + if not hand_fighting: + return + picks = await ctx.choose_cards( + hand_fighting, 2, minimum=0, + prompt="Attach up to 2 Fighting Energy cards to your Pokémon.", + ) + if not picks: + return + candidates = ctx.my_pokemon_in_play() + if not candidates: + return + await distribute_energy(ctx, picks, candidates) + + +async def lost_shot(ctx): + """120. Put the top card of your opponent's deck in the Lost Zone.""" + await ctx.deal_damage() + top = ctx.deck_top(1, player_id=ctx.opponent_id) + if top: + await ctx.move_to_lost_zone(top) + card = PokemonCardDef( guid="9f5472cf-1f35-562d-af9d-7372d55adcb8", @@ -20,16 +53,16 @@ card = PokemonCardDef( abilities=[ Attack( title="Power Stone", - game_text="Attach up to 2 Fighting Energy cards from your hand to your Pok\u00e9mon in any way you like.", + game_text="Attach up to 2 Fighting Energy cards from your hand to your Pokémon in any way you like.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=power_stone, ), Attack( title="Lost Shot", game_text="Put the top card of your opponent's deck in the Lost Zone.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 3}, damage=120, - effect=unimplemented, + effect=lost_shot, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH11/Sudowoodo_94.py b/spirit/game/scripts/cards/SWSH11/Sudowoodo_94.py index ccb3036..ed45fef 100644 --- a/spirit/game/scripts/cards/SWSH11/Sudowoodo_94.py +++ b/spirit/game/scripts/cards/SWSH11/Sudowoodo_94.py @@ -1,6 +1,23 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def joust(ctx): + defender = ctx.opponent_active() + if defender is not None and not ctx.effects_blocked(defender): + tools = [tool for tool, pokemon in ctx.tools_in_play() if pokemon is defender] + if tools: + await ctx.discard_cards(tools) + await ctx.deal_damage() + + +async def impound(ctx): + await ctx.deal_damage() + defender = ctx.defender + if defender is not None and not ctx.effects_blocked(defender): + ctx.lock_retreat(defender) + + card = PokemonCardDef( guid="58307792-4a94-5143-ba5c-e2d03fa733a0", key="SWSH11", @@ -23,14 +40,14 @@ card = PokemonCardDef( game_text="Before doing damage, discard all Pok\u00e9mon Tools from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.FIGHTING: 1}, damage=20, - effect=unimplemented, + effect=joust, ), Attack( title="Impound", game_text="During your opponent's next turn, the Defending Pok\u00e9mon can't retreat.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 1}, damage=50, - effect=unimplemented, + effect=impound, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Swanna_47.py b/spirit/game/scripts/cards/SWSH11/Swanna_47.py index 4bdf55b..02afd17 100644 --- a/spirit/game/scripts/cards/SWSH11/Swanna_47.py +++ b/spirit/game/scripts/cards/SWSH11/Swanna_47.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack, self_energy_discard_attack card = PokemonCardDef( guid="cf413240-bd7a-5ccb-a0a1-d9e065de33aa", @@ -25,14 +26,14 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Asleep.", cost={PokemonTypes.WATER: 1}, damage=20, - effect=unimplemented, + effect=condition_attack(SpecialConditions.ASLEEP), ), Attack( title="Air Slash", game_text="Discard an Energy from this Pok\u00e9mon.", cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, damage=130, - effect=unimplemented, + effect=self_energy_discard_attack(count=1), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Thorton_167.py b/spirit/game/scripts/cards/SWSH11/Thorton_167.py index b707b5a..e7eb1a3 100644 --- a/spirit/game/scripts/cards/SWSH11/Thorton_167.py +++ b/spirit/game/scripts/cards/SWSH11/Thorton_167.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.card_effects.trainers import thorton, thorton_condition +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities card = SupporterCardDef( @@ -11,5 +12,6 @@ card = SupporterCardDef( collector_number=167, set_code="SWSH11", rarity=Rarities.Uncommon, - effect=unimplemented + condition=thorton_condition, + effect=thorton, ) diff --git a/spirit/game/scripts/cards/SWSH11/Thorton_195.py b/spirit/game/scripts/cards/SWSH11/Thorton_195.py index 63cbae1..47756f5 100644 --- a/spirit/game/scripts/cards/SWSH11/Thorton_195.py +++ b/spirit/game/scripts/cards/SWSH11/Thorton_195.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.card_effects.trainers import thorton, thorton_condition +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities card = SupporterCardDef( @@ -11,5 +12,6 @@ card = SupporterCardDef( collector_number=195, set_code="SWSH11", rarity=Rarities.RareUltra, - effect=unimplemented + condition=thorton_condition, + effect=thorton, ) diff --git a/spirit/game/scripts/cards/SWSH11/Thorton_210.py b/spirit/game/scripts/cards/SWSH11/Thorton_210.py index 0c93bcb..ded0b79 100644 --- a/spirit/game/scripts/cards/SWSH11/Thorton_210.py +++ b/spirit/game/scripts/cards/SWSH11/Thorton_210.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.card_effects.trainers import thorton, thorton_condition +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities card = SupporterCardDef( @@ -11,5 +12,6 @@ card = SupporterCardDef( collector_number=210, set_code="SWSH11", rarity=Rarities.RareRainbow, - effect=unimplemented + condition=thorton_condition, + effect=thorton, ) diff --git a/spirit/game/scripts/cards/SWSH11/ToolBox_168.py b/spirit/game/scripts/cards/SWSH11/ToolBox_168.py index 0798f21..02b85f3 100644 --- a/spirit/game/scripts/cards/SWSH11/ToolBox_168.py +++ b/spirit/game/scripts/cards/SWSH11/ToolBox_168.py @@ -1,5 +1,32 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import ItemCardDef +from spirit.game.attributes import Rarities, AttrID, TrainerType + + +def _is_pokemon_tool_card(card): + return card.get_attribute(AttrID.TRAINER_TYPE) in ( + TrainerType.POKEMON_TOOL.value, TrainerType.POKEMON_TOOL_F.value, + ) + + +async def tool_box(ctx): + """Look at the top 7 cards of your deck. You may reveal any number of + Pokémon Tool cards found there and put them into your hand. Shuffle the + other cards back into your deck.""" + top = ctx.deck_top(7) + if not top: + return + tools = [c for c in top if _is_pokemon_tool_card(c)] + picks = [] + if tools: + picks = await ctx.choose_cards( + tools, len(tools), minimum=0, + prompt="Choose any number of Pokémon Tool cards to put into your hand.", + display_cards=top, + ) + if picks: + await ctx.put_in_hand(picks, reveal=True) + await ctx.shuffle_deck() + card = ItemCardDef( guid="92ce36bc-12f1-5ba5-8541-46203ea28615", @@ -11,5 +38,5 @@ card = ItemCardDef( collector_number=168, set_code="SWSH11", rarity=Rarities.Uncommon, - effect=unimplemented + effect=tool_box, ) diff --git a/spirit/game/scripts/cards/SWSH11/Torkoal_23.py b/spirit/game/scripts/cards/SWSH11/Torkoal_23.py index fad0028..bd12bcc 100644 --- a/spirit/game/scripts/cards/SWSH11/Torkoal_23.py +++ b/spirit/game/scripts/cards/SWSH11/Torkoal_23.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import self_energy_discard_attack + +flamethrower = self_energy_discard_attack(count=1) card = PokemonCardDef( guid="edcc2b59-38a6-5d82-828f-04082e9e46ad", @@ -28,7 +31,7 @@ card = PokemonCardDef( game_text="Discard an Energy from this Pok\u00e9mon.", cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 3}, damage=130, - effect=unimplemented, + effect=flamethrower, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Trevenant_17.py b/spirit/game/scripts/cards/SWSH11/Trevenant_17.py index c41d729..97e26b1 100644 --- a/spirit/game/scripts/cards/SWSH11/Trevenant_17.py +++ b/spirit/game/scripts/cards/SWSH11/Trevenant_17.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, is_pokemon_v from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.passives import Passive + + +class ElderTreeBarrierPassive(Passive): + def modify_prizes_for_knockout(self, pokemon, ctx, count, carrier): + if pokemon is not carrier or not ctx.is_attack_effect(): + return count + attacker = ctx.attacker + if attacker is None or attacker.owning_player_id == pokemon.owning_player_id: + return count + if not is_pokemon_v(attacker.archetype_id): + return count + return 0 + card = PokemonCardDef( guid="19554151-d920-58d3-bd38-1fa326759400", @@ -22,14 +36,14 @@ card = PokemonCardDef( Ability( title="Elder Tree Barrier", game_text="If this Pok\u00e9mon is Knocked Out by damage from an attack from your opponent's Pok\u00e9mon V, your opponent can't take any Prize cards for it.", - effect=unimplemented, + passive=ElderTreeBarrierPassive(), ), Attack( title="Giga Impact", game_text="During your next turn, this Pok\u00e9mon can't attack.", cost={PokemonTypes.GRASS: 2, PokemonTypes.COLORLESS: 1}, damage=150, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Tynamo_59.py b/spirit/game/scripts/cards/SWSH11/Tynamo_59.py index d182730..1cda422 100644 --- a/spirit/game/scripts/cards/SWSH11/Tynamo_59.py +++ b/spirit/game/scripts/cards/SWSH11/Tynamo_59.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.session.effects import is_pokemon_card +from spirit.game.card_effects.support_common import search_to_hand + + +def _is_lightning_pokemon(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_pokemon_card(card) and PokemonTypes.LIGHTNING.value in types card = PokemonCardDef( guid="13c7c8bb-d6ad-5070-99f4-62ca80a3d142", @@ -22,7 +29,7 @@ card = PokemonCardDef( title="Call Sign", game_text="Search your deck for a Lightning Pok\u00e9mon, reveal it, and put it into your hand. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_to_hand(_is_lightning_pokemon, count=1, minimum=0, reveal=True), ), Attack( title="Tiny Charge", diff --git a/spirit/game/scripts/cards/SWSH11/Vileplume_3.py b/spirit/game/scripts/cards/SWSH11/Vileplume_3.py index 856a2e7..fa2d9b6 100644 --- a/spirit/game/scripts/cards/SWSH11/Vileplume_3.py +++ b/spirit/game/scripts/cards/SWSH11/Vileplume_3.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import heal_attack +from spirit.game.session.effects import is_supporter_card, is_item_card + + +async def allergy_storm(ctx): + """90. Flip a coin: heads locks Supporters, tails locks Items, next opponent turn.""" + await ctx.deal_damage() + heads = (await ctx.flip_coins(1, "Allergy Storm"))[0] + if heads: + ctx.lock_plays(ctx.opponent_id, is_supporter_card) + else: + ctx.lock_plays(ctx.opponent_id, is_item_card) + card = PokemonCardDef( guid="139b1799-a247-5785-b7c2-3daa812983f8", @@ -24,14 +37,14 @@ card = PokemonCardDef( game_text="Heal 30 damage from this Pok\u00e9mon.", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=50, - effect=unimplemented, + effect=heal_attack(30), ), Attack( title="Allergy Storm", game_text="Flip a coin. If heads, during your opponent's next turn, they can't play any Supporter cards from their hand. If tails, during your opponent's next turn, they can't play any Item cards from their hand.", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 2}, damage=90, - effect=unimplemented, + effect=allergy_storm, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH11/Volo_169.py b/spirit/game/scripts/cards/SWSH11/Volo_169.py index 7e9e780..9680ec5 100644 --- a/spirit/game/scripts/cards/SWSH11/Volo_169.py +++ b/spirit/game/scripts/cards/SWSH11/Volo_169.py @@ -1,5 +1,27 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef, is_pokemon_v from spirit.game.attributes import Rarities +from spirit.game.models.board import PokemonEntity +from spirit.game.session.effects import full_stack + + +def _volo_condition(board, player_id): + bench = board.find_player_area(player_id, "bench") + return bool(bench) and any( + isinstance(c, PokemonEntity) and is_pokemon_v(c.archetype_id) + for c in bench.children + ) + + +async def volo(ctx): + """Discard 1 of your Benched Pokemon V and all attached cards.""" + candidates = [p for p in ctx.my_bench() if is_pokemon_v(p.archetype_id)] + target = await ctx.choose_pokemon( + candidates, "Choose 1 of your Benched Pokémon V to discard" + ) + if target is None: + return + await ctx.discard_cards(full_stack(target)) + card = SupporterCardDef( guid="81375ea6-9130-55f6-b90f-d3fdcde878e0", @@ -11,5 +33,6 @@ card = SupporterCardDef( collector_number=169, set_code="SWSH11", rarity=Rarities.RareHolo, - effect=unimplemented + condition=_volo_condition, + effect=volo, ) diff --git a/spirit/game/scripts/cards/SWSH11/Volo_196.py b/spirit/game/scripts/cards/SWSH11/Volo_196.py index 0b5d04c..a3d2baa 100644 --- a/spirit/game/scripts/cards/SWSH11/Volo_196.py +++ b/spirit/game/scripts/cards/SWSH11/Volo_196.py @@ -1,5 +1,27 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef, is_pokemon_v from spirit.game.attributes import Rarities +from spirit.game.models.board import PokemonEntity +from spirit.game.session.effects import full_stack + + +def _volo_condition(board, player_id): + bench = board.find_player_area(player_id, "bench") + return bool(bench) and any( + isinstance(c, PokemonEntity) and is_pokemon_v(c.archetype_id) + for c in bench.children + ) + + +async def volo(ctx): + """Discard 1 of your Benched Pokemon V and all attached cards.""" + candidates = [p for p in ctx.my_bench() if is_pokemon_v(p.archetype_id)] + target = await ctx.choose_pokemon( + candidates, "Choose 1 of your Benched Pokémon V to discard" + ) + if target is None: + return + await ctx.discard_cards(full_stack(target)) + card = SupporterCardDef( guid="e979da37-e334-5d2c-b018-d5c74fa5e840", @@ -11,5 +33,6 @@ card = SupporterCardDef( collector_number=196, set_code="SWSH11", rarity=Rarities.RareUltra, - effect=unimplemented + condition=_volo_condition, + effect=volo, ) diff --git a/spirit/game/scripts/cards/SWSH11/Volo_211.py b/spirit/game/scripts/cards/SWSH11/Volo_211.py index 1a9bc20..f0545d4 100644 --- a/spirit/game/scripts/cards/SWSH11/Volo_211.py +++ b/spirit/game/scripts/cards/SWSH11/Volo_211.py @@ -1,5 +1,27 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef, is_pokemon_v from spirit.game.attributes import Rarities +from spirit.game.models.board import PokemonEntity +from spirit.game.session.effects import full_stack + + +def _volo_condition(board, player_id): + bench = board.find_player_area(player_id, "bench") + return bool(bench) and any( + isinstance(c, PokemonEntity) and is_pokemon_v(c.archetype_id) + for c in bench.children + ) + + +async def volo(ctx): + """Discard 1 of your Benched Pokemon V and all attached cards.""" + candidates = [p for p in ctx.my_bench() if is_pokemon_v(p.archetype_id)] + target = await ctx.choose_pokemon( + candidates, "Choose 1 of your Benched Pokémon V to discard" + ) + if target is None: + return + await ctx.discard_cards(full_stack(target)) + card = SupporterCardDef( guid="92cd4372-1eb1-5061-a468-37f069a4f6e0", @@ -11,5 +33,6 @@ card = SupporterCardDef( collector_number=211, set_code="SWSH11", rarity=Rarities.RareRainbow, - effect=unimplemented + condition=_volo_condition, + effect=volo, ) diff --git a/spirit/game/scripts/cards/SWSH11/WindupArm_170.py b/spirit/game/scripts/cards/SWSH11/WindupArm_170.py index 5f0cd18..5a316f0 100644 --- a/spirit/game/scripts/cards/SWSH11/WindupArm_170.py +++ b/spirit/game/scripts/cards/SWSH11/WindupArm_170.py @@ -1,15 +1,24 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented +from spirit.game.data_utils import PokemonToolCardDef from spirit.game.attributes import Rarities +from spirit.game.session.passives import Passive, carrier_pokemon + + +class WindupArmPassive(Passive): + """The holder can attack even if it's Asleep or Paralyzed.""" + + def attacks_despite_conditions(self, pokemon, carrier): + return carrier_pokemon(carrier) is pokemon + card = PokemonToolCardDef( guid="eb68dba7-7f54-5ee1-a0a7-4e94152a92d1", key="SWSH11", name="com.direwolfdigital.cake.data.archetypes.trainer.WindupArm.Name", display_name="Windup Arm", - searchable_by=["Windup Arm", "Item", "Pok\u00c3\u00a9mon Tool"], - subtypes=["Item", "Pok\u00e9mon Tool"], + searchable_by=["Windup Arm", "Item", "Pokémon Tool"], + subtypes=["Item", "Pokémon Tool"], collector_number=170, set_code="SWSH11", rarity=Rarities.Uncommon, - effect=unimplemented + passive=WindupArmPassive(), ) diff --git a/spirit/game/scripts/cards/SWSH11/Wurmple_6.py b/spirit/game/scripts/cards/SWSH11/Wurmple_6.py index 02c7689..008a491 100644 --- a/spirit/game/scripts/cards/SWSH11/Wurmple_6.py +++ b/spirit/game/scripts/cards/SWSH11/Wurmple_6.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.support_common import search_to_hand + +_CREEPY_CRAWLY_NAMES = ("Wurmple", "Silcoon", "Beautifly", "Cascoon", "Dustox") + + +def _is_creepy_crawly(card): + return card.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) in _CREEPY_CRAWLY_NAMES + card = PokemonCardDef( guid="86568dbe-e9ff-5434-ac85-21555ae5f63d", @@ -27,7 +35,7 @@ card = PokemonCardDef( title="Creepy-Crawly Congregation", game_text="Search your deck for any number of Wurmple, Silcoon, Beautifly, Cascoon, and Dustox, reveal them, and put them into your hand. Then, shuffle your deck.", cost={PokemonTypes.GRASS: 3}, - effect=unimplemented, + effect=search_to_hand(_is_creepy_crawly, count=60, minimum=0, reveal=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/AlolanVulpixVSTAR_197.py b/spirit/game/scripts/cards/SWSH12/AlolanVulpixVSTAR_197.py index 48c0f59..db9d4ca 100644 --- a/spirit/game/scripts/cards/SWSH12/AlolanVulpixVSTAR_197.py +++ b/spirit/game/scripts/cards/SWSH12/AlolanVulpixVSTAR_197.py @@ -1,5 +1,34 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, is_pokemon_v +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.session.passives import Passive + + +class _SnowMirageShield(Passive): + """Prevent all damage from an opposing attacker with an Ability.""" + + def prevents_damage(self, calc, carrier): + if not (calc.is_attack and calc.is_opposing and calc.target is carrier): + return False + attacker = calc.attacker + if attacker is None: + return False + return any( + isinstance(entry, dict) and entry.get("abilityType") != "Attack" + for entry in (attacker.get_attribute(AttrID.PIE_ABILITIES) or []) + ) + + +async def snow_mirage(ctx): + """160, ignoring effects on the opponent's Active. Shield this Pokemon from + Ability-Pokemon attacks during your opponent's next turn.""" + await ctx.deal_damage(ignore_target_effects=True) + ctx.add_passive_through_opponents_turn(ctx.attacker, _SnowMirageShield()) + + +async def silvery_snow_star(ctx): + """70 for each of the opponent's Pokemon V in play; not affected by W/R.""" + count = sum(1 for p in ctx.opponent_pokemon_in_play() if is_pokemon_v(p.archetype_id)) + await ctx.deal_damage(70 * count, apply_modifiers=False) card = PokemonCardDef( guid="728c4d6d-24df-58ab-9e66-535803077792", @@ -24,7 +53,7 @@ card = PokemonCardDef( game_text="This attack's damage isn't affected by any effects on your opponent's Active Pok\u00e9mon. During your opponent's next turn, prevent all damage done to this Pok\u00e9mon by attacks from Pok\u00e9mon that have an Ability.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=160, - effect=unimplemented, + effect=snow_mirage, ), Attack( title="Silvery Snow Star", @@ -32,7 +61,8 @@ card = PokemonCardDef( cost={}, damage=70, damage_operator="x", - effect=unimplemented, + vstar=True, + effect=silvery_snow_star, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/AlolanVulpixVSTAR_34.py b/spirit/game/scripts/cards/SWSH12/AlolanVulpixVSTAR_34.py index 67dc331..ab20a9d 100644 --- a/spirit/game/scripts/cards/SWSH12/AlolanVulpixVSTAR_34.py +++ b/spirit/game/scripts/cards/SWSH12/AlolanVulpixVSTAR_34.py @@ -1,5 +1,34 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, is_pokemon_v +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.session.passives import Passive + + +class _SnowMirageShield(Passive): + """Prevent all damage from an opposing attacker with an Ability.""" + + def prevents_damage(self, calc, carrier): + if not (calc.is_attack and calc.is_opposing and calc.target is carrier): + return False + attacker = calc.attacker + if attacker is None: + return False + return any( + isinstance(entry, dict) and entry.get("abilityType") != "Attack" + for entry in (attacker.get_attribute(AttrID.PIE_ABILITIES) or []) + ) + + +async def snow_mirage(ctx): + """160, ignoring effects on the opponent's Active. Shield this Pokemon from + Ability-Pokemon attacks during your opponent's next turn.""" + await ctx.deal_damage(ignore_target_effects=True) + ctx.add_passive_through_opponents_turn(ctx.attacker, _SnowMirageShield()) + + +async def silvery_snow_star(ctx): + """70 for each of the opponent's Pokemon V in play; not affected by W/R.""" + count = sum(1 for p in ctx.opponent_pokemon_in_play() if is_pokemon_v(p.archetype_id)) + await ctx.deal_damage(70 * count, apply_modifiers=False) card = PokemonCardDef( guid="040f2a64-dffa-52b3-8248-30a1faacf403", @@ -24,7 +53,7 @@ card = PokemonCardDef( game_text="This attack's damage isn't affected by any effects on your opponent's Active Pok\u00e9mon. During your opponent's next turn, prevent all damage done to this Pok\u00e9mon by attacks from Pok\u00e9mon that have an Ability.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=160, - effect=unimplemented, + effect=snow_mirage, ), Attack( title="Silvery Snow Star", @@ -32,7 +61,8 @@ card = PokemonCardDef( cost={}, damage=70, damage_operator="x", - effect=unimplemented, + vstar=True, + effect=silvery_snow_star, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/AlolanVulpixV_173.py b/spirit/game/scripts/cards/SWSH12/AlolanVulpixV_173.py index 511ccb0..46ac8ee 100644 --- a/spirit/game/scripts/cards/SWSH12/AlolanVulpixV_173.py +++ b/spirit/game/scripts/cards/SWSH12/AlolanVulpixV_173.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, defender_is_v card = PokemonCardDef( guid="4ac16aea-9296-5604-b682-7e30d81feab6", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={}, damage=10, damage_operator="+", - effect=unimplemented, + effect=bonus_if(defender_is_v, 50), ), Attack( title="Frost Smash", diff --git a/spirit/game/scripts/cards/SWSH12/AlolanVulpixV_33.py b/spirit/game/scripts/cards/SWSH12/AlolanVulpixV_33.py index a69013d..9dff10b 100644 --- a/spirit/game/scripts/cards/SWSH12/AlolanVulpixV_33.py +++ b/spirit/game/scripts/cards/SWSH12/AlolanVulpixV_33.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, defender_is_v card = PokemonCardDef( guid="bdb5337c-364b-5083-b357-9cc80d63f281", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={}, damage=10, damage_operator="+", - effect=unimplemented, + effect=bonus_if(defender_is_v, 50), ), Attack( title="Frost Smash", diff --git a/spirit/game/scripts/cards/SWSH12/Altaria_143.py b/spirit/game/scripts/cards/SWSH12/Altaria_143.py index 2cdb648..db57d6b 100644 --- a/spirit/game/scripts/cards/SWSH12/Altaria_143.py +++ b/spirit/game/scripts/cards/SWSH12/Altaria_143.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID + + +async def magical_echo(ctx): + """Move all damage counters from 1 of your Benched Pokemon to the opponent's Active.""" + damaged = [p for p in ctx.my_bench() + if p.get_attribute(AttrID.HP, 0) < ctx.max_hp(p)] + if not damaged or ctx.defender is None: + return + source = await ctx.choose_pokemon( + damaged, "Choose a Benched Pokémon to move all damage counters from") + if source is None: + return + await ctx.move_damage_counters(source, ctx.defender) + card = PokemonCardDef( guid="f2d2baea-17a0-5dc7-b6aa-bbe37feca0ec", @@ -22,9 +36,9 @@ card = PokemonCardDef( abilities=[ Attack( title="Magical Echo", - game_text="Move all damage counters from 1 of your Benched Pok\u00e9mon to your opponent's Active Pok\u00e9mon.", + game_text="Move all damage counters from 1 of your Benched Pokémon to your opponent's Active Pokémon.", cost={PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + effect=magical_echo, ), Attack( title="Blasting Wind", @@ -32,4 +46,4 @@ card = PokemonCardDef( damage=90, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH12/Amoonguss_12.py b/spirit/game/scripts/cards/SWSH12/Amoonguss_12.py index 1d80205..848ec0f 100644 --- a/spirit/game/scripts/cards/SWSH12/Amoonguss_12.py +++ b/spirit/game/scripts/cards/SWSH12/Amoonguss_12.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions + + +async def surprise_spores(ctx): + """Discarded from hand by the opponent's effect: discard their hand.""" + # "During your opponent's turn": an opposing effect that somehow discards + # this from hand during the OWNER's turn does not trigger. + if ctx.session.turn_state.active_player_id == ctx.player_id: + return + hand = list(ctx.hand(ctx.opponent_id)) + if hand: + await ctx.discard_cards(hand) card = PokemonCardDef( guid="7632dddb-eb5d-5bb1-bb77-38c2649203e4", @@ -21,15 +33,16 @@ card = PokemonCardDef( abilities=[ Ability( title="Surprise Spores", - game_text="During your opponent's turn, if this Pok\u00e9mon is discarded from your hand by an effect of an attack or Ability from your opponent's Pok\u00e9mon, or by an effect of your opponent's Item or Supporter cards, discard your opponent's hand.", - effect=unimplemented, + game_text="During your opponent's turn, if this Pokémon is discarded from your hand by an effect of an attack or Ability from your opponent's Pokémon, or by an effect of your opponent's Item or Supporter cards, discard your opponent's hand.", + trigger=Triggers.ON_DISCARDED_FROM_HAND, + effect=surprise_spores, ), Attack( title="Hypno Hammer", - game_text="Your opponent's Active Pok\u00e9mon is now Asleep.", + game_text="Your opponent's Active Pokémon is now Asleep.", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=50, - effect=unimplemented, + effect=condition_attack(SpecialConditions.ASLEEP), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH12/Anorith_95.py b/spirit/game/scripts/cards/SWSH12/Anorith_95.py index 6b93d72..87d91ee 100644 --- a/spirit/game/scripts/cards/SWSH12/Anorith_95.py +++ b/spirit/game/scripts/cards/SWSH12/Anorith_95.py @@ -1,6 +1,28 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, def_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +def _is_unidentified_fossil(card): + definition = def_for(getattr(card, "archetype_id", None) or "") + return getattr(definition, "display_name", None) == "Unidentified Fossil" + + +async def fossil_drop(ctx): + """30, +120 if you discard an Unidentified Fossil card from your hand.""" + bonus = 0 + fossils = [c for c in ctx.hand() if _is_unidentified_fossil(c)] + if fossils and await ctx.ask_yes_no( + "Discard an Unidentified Fossil card from your hand? " + "If you do, this attack does 120 more damage." + ): + discarded = await ctx.discard_from_hand( + 1, prompt="Discard an Unidentified Fossil card", + predicate=_is_unidentified_fossil, + ) + if discarded: + bonus = 120 + await ctx.deal_damage(30 + bonus) + card = PokemonCardDef( guid="3f686a69-d104-5d41-8ff7-ebf38540bbb1", key="SWSH12", @@ -25,7 +47,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=30, damage_operator="+", - effect=unimplemented, + effect=fossil_drop, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Araquanid_48.py b/spirit/game/scripts/cards/SWSH12/Araquanid_48.py index b31491d..b5a3480 100644 --- a/spirit/game/scripts/cards/SWSH12/Araquanid_48.py +++ b/spirit/game/scripts/cards/SWSH12/Araquanid_48.py @@ -1,5 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions + + +async def drowning_ball(ctx): + """20 damage; on heads, Paralyze the Defending Pokémon and discard an Energy from it.""" + await ctx.deal_damage() + heads = (await ctx.flip_coins(1, "Drowning Ball"))[0] + if not heads: + return + defender = ctx.defender + if defender is None or ctx.effects_blocked(defender): + return + await ctx.apply_special_condition(defender, SpecialConditions.PARALYZED) + await ctx.discard_energy_from( + defender, 1, prompt="Choose an Energy to discard from the Defending Pokémon") + card = PokemonCardDef( guid="a64cf849-dc93-5c33-af8e-7a7f496cfa05", @@ -24,7 +39,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed, and discard an Energy from that Pok\u00e9mon.", cost={PokemonTypes.WATER: 1}, damage=20, - effect=unimplemented, + effect=drowning_ball, ), Attack( title="Headbutt Bounce", diff --git a/spirit/game/scripts/cards/SWSH12/Arcanine_20.py b/spirit/game/scripts/cards/SWSH12/Arcanine_20.py index 59fff1f..626017b 100644 --- a/spirit/game/scripts/cards/SWSH12/Arcanine_20.py +++ b/spirit/game/scripts/cards/SWSH12/Arcanine_20.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, CardType, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, defender_is_v +from spirit.game.card_effects.support_common import attach_from_discard + + +def _is_fire_energy_card(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return card.get_attribute(AttrID.CARD_TYPE) == CardType.ENERGY.value \ + and PokemonTypes.FIRE.value in types card = PokemonCardDef( guid="4432221a-c964-504a-8130-09a271fd25b8", @@ -24,7 +32,7 @@ card = PokemonCardDef( game_text="Attach a Fire Energy card from your discard pile to this Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=attach_from_discard(predicate=_is_fire_energy_card, count=1, target="self"), ), Attack( title="Fighting Tackle", @@ -32,7 +40,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=100, damage_operator="+", - effect=unimplemented, + effect=bonus_if(defender_is_v, 100), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Ariados_4.py b/spirit/game/scripts/cards/SWSH12/Ariados_4.py index 2cf5220..e81e158 100644 --- a/spirit/game/scripts/cards/SWSH12/Ariados_4.py +++ b/spirit/game/scripts/cards/SWSH12/Ariados_4.py @@ -1,5 +1,23 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, subtypes_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.passives import Passive, active_passives + + +class HiddenThreadsPassive(Passive): + """Non-stacking: only the first active carrier of this shared instance + applies the tax, matching "can't apply more than 1 ... at a time".""" + + def modify_attack_cost(self, cost, pokemon, carrier, board): + if pokemon.owning_player_id == carrier.owning_player_id: + return cost + if "VSTAR" not in subtypes_for(pokemon.archetype_id): + return cost + carriers = [c for p, c in active_passives(board) if p is self] + if carriers and carrier is not carriers[0]: + return cost + cost["Colorless"] = cost.get("Colorless", 0) + 1 + return cost + card = PokemonCardDef( guid="138f50a7-55e3-5a0f-a365-61587be8883e", @@ -22,7 +40,7 @@ card = PokemonCardDef( Ability( title="Hidden Threads", game_text="Your opponent's Pok\u00e9mon VSTAR's attacks cost Colorless more. You can't apply more than 1 Hidden Threads Ability at a time.", - effect=unimplemented, + passive=HiddenThreadsPassive(), ), Attack( title="Pierce", diff --git a/spirit/game/scripts/cards/SWSH12/Armaldo_96.py b/spirit/game/scripts/cards/SWSH12/Armaldo_96.py index 0faa7d3..9cdd858 100644 --- a/spirit/game/scripts/cards/SWSH12/Armaldo_96.py +++ b/spirit/game/scripts/cards/SWSH12/Armaldo_96.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID + + +async def reaping_claw(ctx): + defender = ctx.defender + if defender is not None and defender.get_attribute(AttrID.HP, 0) <= 100: + await ctx.knock_out(defender) + card = PokemonCardDef( guid="fce33d95-a617-5a10-b332-2f32a0d09033", @@ -23,7 +30,7 @@ card = PokemonCardDef( title="Reaping Claw", game_text="If your opponent's Active Pok\u00e9mon has 100 HP or less remaining, it is Knocked Out.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=reaping_claw, ), Attack( title="Boulder Crush", diff --git a/spirit/game/scripts/cards/SWSH12/Articuno_36.py b/spirit/game/scripts/cards/SWSH12/Articuno_36.py index dd57b84..cf5130e 100644 --- a/spirit/game/scripts/cards/SWSH12/Articuno_36.py +++ b/spirit/game/scripts/cards/SWSH12/Articuno_36.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions + + +async def wild_freeze(ctx): + """70, plus 50 to itself; the opponent's Active is now Paralyzed.""" + await ctx.deal_damage() + await ctx.deal_damage(50, target=ctx.attacker, apply_modifiers=False) + await ctx.apply_special_condition(ctx.defender, SpecialConditions.PARALYZED) + card = PokemonCardDef( guid="ce4a0c8d-9f0f-5848-979a-46ff0a40d69e", @@ -25,10 +33,10 @@ card = PokemonCardDef( ), Attack( title="Wild Freeze", - game_text="This Pok\u00e9mon also does 50 damage to itself. Your opponent's Active Pok\u00e9mon is now Paralyzed.", + game_text="This Pokémon also does 50 damage to itself. Your opponent's Active Pokémon is now Paralyzed.", cost={PokemonTypes.WATER: 2}, damage=70, - effect=unimplemented, + effect=wild_freeze, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH12/Baltoy_93.py b/spirit/game/scripts/cards/SWSH12/Baltoy_93.py index fbd7c8e..606b4f2 100644 --- a/spirit/game/scripts/cards/SWSH12/Baltoy_93.py +++ b/spirit/game/scripts/cards/SWSH12/Baltoy_93.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.session.effects import is_pokemon_card card = PokemonCardDef( guid="0571fb38-d32d-5464-a5dd-278503ef7c3e", @@ -22,7 +24,10 @@ card = PokemonCardDef( title="Find a Friend", game_text="Search your deck for a Pok\u00e9mon, reveal it, and put it into your hand. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_to_hand( + is_pokemon_card, count=1, minimum=0, reveal=True, + prompt="Choose a Pok\u00e9mon to put into your hand.", + ), ), Attack( title="Slap", diff --git a/spirit/game/scripts/cards/SWSH12/Beheeyem_80.py b/spirit/game/scripts/cards/SWSH12/Beheeyem_80.py index 5bc3577..8c06e6b 100644 --- a/spirit/game/scripts/cards/SWSH12/Beheeyem_80.py +++ b/spirit/game/scripts/cards/SWSH12/Beheeyem_80.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import snipe_attack card = PokemonCardDef( guid="8a6d4e92-ed5d-5776-a6d6-621e03948b94", @@ -29,7 +30,8 @@ card = PokemonCardDef( title="Psychic Arrow", game_text="This attack does 60 damage to 1 of your opponent's Pok\u00e9mon. Also apply Weakness and Resistance for Benched Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + damage=60, + effect=snipe_attack(60, pool="any", count=1, apply_modifiers=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Beldum_117.py b/spirit/game/scripts/cards/SWSH12/Beldum_117.py index 5926d71..2d1ab2e 100644 --- a/spirit/game/scripts/cards/SWSH12/Beldum_117.py +++ b/spirit/game/scripts/cards/SWSH12/Beldum_117.py @@ -1,6 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def magnetic_lift(ctx): + """Search your deck for a card, shuffle, then put that card on top.""" + picks = await ctx.search_deck( + count=1, minimum=1, prompt="Choose a card.", + ) + if not picks: + return + await ctx.shuffle_deck() + await ctx.put_on_top_of_deck(picks[0]) + + card = PokemonCardDef( guid="bac913e8-e369-59d1-9a81-0c7871e41ed4", key="SWSH12", @@ -23,7 +35,7 @@ card = PokemonCardDef( title="Magnetic Lift", game_text="Search your deck for a card. Shuffle your deck, then put that card on top of it.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=magnetic_lift, ), Attack( title="Tackle", diff --git a/spirit/game/scripts/cards/SWSH12/Braixen_26.py b/spirit/game/scripts/cards/SWSH12/Braixen_26.py index d2485d8..cf65dbb 100644 --- a/spirit/game/scripts/cards/SWSH12/Braixen_26.py +++ b/spirit/game/scripts/cards/SWSH12/Braixen_26.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, def_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_discard + + +def _is_serena_card(card): + definition = def_for(getattr(card, "archetype_id", None) or "") + return getattr(definition, "display_name", None) == "Serena" + card = PokemonCardDef( guid="df24acfb-0268-5a75-920d-ef3a6039bdef", @@ -30,7 +37,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=60, damage_operator="x", - effect=unimplemented, + effect=damage_per(count_discard("mine", _is_serena_card), 60), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH12/Buneary_144.py b/spirit/game/scripts/cards/SWSH12/Buneary_144.py index 864e747..7e1939d 100644 --- a/spirit/game/scripts/cards/SWSH12/Buneary_144.py +++ b/spirit/game/scripts/cards/SWSH12/Buneary_144.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_or_nothing card = PokemonCardDef( guid="c3df2d1d-bb0d-5782-93d9-d7c7fa64af5c", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Flip a coin. If tails, this attack does nothing.", cost={PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=flip_or_nothing(), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH12/ChesnaughtV_15.py b/spirit/game/scripts/cards/SWSH12/ChesnaughtV_15.py index de07834..74d6bde 100644 --- a/spirit/game/scripts/cards/SWSH12/ChesnaughtV_15.py +++ b/spirit/game/scripts/cards/SWSH12/ChesnaughtV_15.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import in_active_spot +from spirit.game.card_effects.support_common import heal_attack + + +async def needle_line(ctx): + """Active Spot only: if damaged by an opponent's attack (even if + Knocked Out), put 3 damage counters on the Attacking Pokémon.""" + if not in_active_spot(ctx.board, ctx.player_id, ctx.source): + return + attacker = ctx.damaged_by + if attacker is None: + return + await ctx.deal_damage(30, target=attacker, apply_modifiers=False, as_counters=True) + card = PokemonCardDef( guid="5a993c9c-cb33-5d6c-9e2e-bf88dad4ef6f", @@ -21,14 +35,15 @@ card = PokemonCardDef( Ability( title="Needle Line", game_text="If your Active Chesnaught V is damaged by an attack from your opponent's Pok\u00e9mon (even if it is Knocked Out), put 3 damage counters on the Attacking Pok\u00e9mon.", - effect=unimplemented, + trigger=Triggers.ON_DAMAGED_BY_ATTACK, + effect=needle_line, ), Attack( title="Touchdown", game_text="Heal 30 damage from this Pok\u00e9mon.", cost={PokemonTypes.GRASS: 2, PokemonTypes.COLORLESS: 1}, damage=130, - effect=unimplemented, + effect=heal_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/ChesnaughtV_171.py b/spirit/game/scripts/cards/SWSH12/ChesnaughtV_171.py index 200dba0..d36b83a 100644 --- a/spirit/game/scripts/cards/SWSH12/ChesnaughtV_171.py +++ b/spirit/game/scripts/cards/SWSH12/ChesnaughtV_171.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import in_active_spot +from spirit.game.card_effects.support_common import heal_attack + + +async def needle_line(ctx): + """Active Spot only: if damaged by an opponent's attack (even if + Knocked Out), put 3 damage counters on the Attacking Pokémon.""" + if not in_active_spot(ctx.board, ctx.player_id, ctx.source): + return + attacker = ctx.damaged_by + if attacker is None: + return + await ctx.deal_damage(30, target=attacker, apply_modifiers=False, as_counters=True) + card = PokemonCardDef( guid="60948566-e9f8-520b-b220-f9584a527ae4", @@ -21,14 +35,15 @@ card = PokemonCardDef( Ability( title="Needle Line", game_text="If your Active Chesnaught V is damaged by an attack from your opponent's Pok\u00e9mon (even if it is Knocked Out), put 3 damage counters on the Attacking Pok\u00e9mon.", - effect=unimplemented, + trigger=Triggers.ON_DAMAGED_BY_ATTACK, + effect=needle_line, ), Attack( title="Touchdown", game_text="Heal 30 damage from this Pok\u00e9mon.", cost={PokemonTypes.GRASS: 2, PokemonTypes.COLORLESS: 1}, damage=130, - effect=unimplemented, + effect=heal_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Chimecho_74.py b/spirit/game/scripts/cards/SWSH12/Chimecho_74.py index 6099bf6..b7e1639 100644 --- a/spirit/game/scripts/cards/SWSH12/Chimecho_74.py +++ b/spirit/game/scripts/cards/SWSH12/Chimecho_74.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions + + +async def dreaming_tone_watch(ctx): + """During opponent's next turn, if an Energy card is attached to the + Defending Pokémon from their hand, that Pokémon becomes Asleep.""" + if not ctx.attack_used_last_turn(title="Dreaming Tone", entity=ctx.source): + return + if ctx.attaching_player_id != ctx.opponent_id: + return + receiver = ctx.energy_receiver + if receiver is None or receiver is not ctx.opponent_active(): + return + await ctx.apply_special_condition(receiver, SpecialConditions.ASLEEP) + card = PokemonCardDef( guid="eda95826-9622-5b5a-82fb-167d6a136d6a", @@ -23,7 +37,12 @@ card = PokemonCardDef( title="Dreaming Tone", game_text="During your opponent's next turn, if an Energy card is attached to the Defending Pok\u00e9mon from your opponent's hand, that Pok\u00e9mon will be Asleep.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + ), + Ability( + title="Dreaming Tone", + game_text="During your opponent's next turn, if an Energy card is attached to the Defending Pok\u00e9mon from your opponent's hand, that Pok\u00e9mon will be Asleep.", + trigger=Triggers.ON_ENERGY_ATTACHED, + effect=dreaming_tone_watch, ), Attack( title="Hang Down", diff --git a/spirit/game/scripts/cards/SWSH12/Claydol_94.py b/spirit/game/scripts/cards/SWSH12/Claydol_94.py index a787083..8d1f63a 100644 --- a/spirit/game/scripts/cards/SWSH12/Claydol_94.py +++ b/spirit/game/scripts/cards/SWSH12/Claydol_94.py @@ -1,5 +1,29 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.support_common import attach_from_discard, requires_discard +from spirit.game.card_effects.pokemon import energy_provides_type +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_supporter_card + + +def _is_fighting_energy(card): + return energy_provides_type(card, PokemonTypes.FIGHTING.value) + + +_has_fighting_energy_in_discard = requires_discard(_is_fighting_energy) + + +def mystery_charge_condition(board, player_id, pokemon): + area = board.find_player_area(player_id, "discard") + discard = list(area.children) if area else [] + if any(is_supporter_card(c) for c in discard): + return False + return _has_fighting_energy_in_discard(board, player_id, pokemon) + + +mystery_charge = attach_from_discard( + predicate=_is_fighting_energy, count=1, target="choice", + prompt="Choose a Fighting Energy card to attach.", +) card = PokemonCardDef( guid="9fffbd7c-da97-5852-bc46-548959b8a728", @@ -22,7 +46,9 @@ card = PokemonCardDef( Ability( title="Mystery Charge", game_text="You can use this Ability only if you have no Supporter cards in your discard pile. Once during your turn, you may attach a Fighting Energy card from your discard pile to 1 of your Pok\u00e9mon.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=mystery_charge_condition, + effect=mystery_charge, ), Attack( title="Spinning Attack", diff --git a/spirit/game/scripts/cards/SWSH12/Cobalion_126.py b/spirit/game/scripts/cards/SWSH12/Cobalion_126.py index 012ca07..e4cfa28 100644 --- a/spirit/game/scripts/cards/SWSH12/Cobalion_126.py +++ b/spirit/game/scripts/cards/SWSH12/Cobalion_126.py @@ -1,5 +1,36 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import team_damage_boost_passive +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_basic_pokemon + + +def _is_darkness(pokemon) -> bool: + types = pokemon.get_attribute(AttrID.POKEMON_TYPES) or [] + return PokemonTypes.DARKNESS.value in types + + +async def follow_up(ctx): + """30 damage. Choose up to 2 Benched Pokémon; for each, search a basic + Energy card and attach it to that Pokémon. Then, shuffle your deck.""" + await ctx.deal_damage() + bench = ctx.my_bench() + if not bench: + return + picks = await ctx.choose_cards( + bench, 2, minimum=0, prompt="Choose up to 2 of your Benched Pokémon.", + ) + if not picks: + return + for target in picks: + found = await ctx.search_deck( + is_basic_energy_card, count=1, minimum=0, + prompt="Choose a basic Energy card to attach.", + ) + if found: + await ctx.attach_energy(found[0], target) + await ctx.shuffle_deck() + card = PokemonCardDef( guid="e7302307-c6a3-5a2b-9054-3b3128f1fd6f", @@ -22,14 +53,16 @@ card = PokemonCardDef( Ability( title="Justified Law", game_text="Your Basic Pok\u00e9mon's attacks do 30 more damage to your opponent's Active Darkness Pok\u00e9mon (before applying Weakness and Resistance).", - effect=unimplemented, + passive=team_damage_boost_passive( + 30, attacker_pred=is_basic_pokemon, target_pred=_is_darkness, + ), ), Attack( title="Follow-Up", game_text="Choose up to 2 of your Benched Pok\u00e9mon. For each of those Pok\u00e9mon, search your deck for a basic Energy card and attach it to that Pok\u00e9mon. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 2}, damage=30, - effect=unimplemented, + effect=follow_up, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Crobat_105.py b/spirit/game/scripts/cards/SWSH12/Crobat_105.py index 644b409..42d42dd 100644 --- a/spirit/game/scripts/cards/SWSH12/Crobat_105.py +++ b/spirit/game/scripts/cards/SWSH12/Crobat_105.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack + + +async def critical_bite(ctx): + """30 to 1 of your opponent's Pokémon (no W/R on Benched); +2 Prizes if it's KO'd.""" + candidates = ctx.opponent_pokemon_in_play() + if not candidates: + return + target = await ctx.choose_pokemon(candidates, "Choose 1 of your opponent's Pokémon") + if target is None: + return + await ctx.deal_damage(30, target=target) + if target in ctx.knockouts: + ctx.extra_prizes += 2 card = PokemonCardDef( guid="00417300-8a78-5909-a397-195e08cc3c94", @@ -24,13 +38,13 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Poisoned.", cost={PokemonTypes.DARKNESS: 1}, damage=50, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED), ), Attack( title="Critical Bite", game_text="This attack does 30 damage to 1 of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.) If 1 of your opponent's Pok\u00e9mon is Knocked Out by damage from this attack, take 2 more Prize cards.", cost={PokemonTypes.COLORLESS: 3}, - effect=unimplemented, + effect=critical_bite, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Dedenne_85.py b/spirit/game/scripts/cards/SWSH12/Dedenne_85.py index 4a34f22..dcd6ef0 100644 --- a/spirit/game/scripts/cards/SWSH12/Dedenne_85.py +++ b/spirit/game/scripts/cards/SWSH12/Dedenne_85.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, def_for +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack + + +def _togedemaru_toge_dash_condition(board, player_id, pokemon): + for p in board.pokemon_in_play(player_id): + card_def = def_for(p.archetype_id) + if card_def and card_def.display_name == "Togedemaru": + for used_id, _archetype, used_title in board.turn_state.attacks_used_last_turn: + if used_id == p.entity_id and used_title == "Toge Dash": + return True + return False card = PokemonCardDef( guid="39aa098f-3cab-5aeb-a8e0-99750798b010", @@ -28,7 +39,8 @@ card = PokemonCardDef( game_text="You can use this attack only if 1 of your Togedemaru used Toge Dash during your last turn. Your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.LIGHTNING: 1}, damage=60, - effect=unimplemented, + condition=_togedemaru_toge_dash_condition, + effect=condition_attack(SpecialConditions.PARALYZED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Delphox_27.py b/spirit/game/scripts/cards/SWSH12/Delphox_27.py index 5be6223..6f6f917 100644 --- a/spirit/game/scripts/cards/SWSH12/Delphox_27.py +++ b/spirit/game/scripts/cards/SWSH12/Delphox_27.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, def_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_discard, count_energy + + +def _is_serena_card(card): + definition = def_for(card.archetype_id) + return bool(definition and (definition.display_name or "") == "Serena") + card = PokemonCardDef( guid="8ba6ad51-dee9-5ae8-9411-399283a51c82", @@ -25,15 +32,15 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 1}, damage=60, damage_operator="x", - effect=unimplemented, + effect=damage_per(count_discard("mine", _is_serena_card), 60), ), Attack( title="Energy Crush", - game_text="This attack does 50 damage for each Energy attached to all of your opponent's Pok\u00e9mon.", + game_text="This attack does 50 damage for each Energy attached to all of your opponent's Pokémon.", cost={PokemonTypes.COLORLESS: 2}, damage=50, damage_operator="x", - effect=unimplemented, + effect=damage_per(count_energy("opponent"), 50), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH12/Donphan_92.py b/spirit/game/scripts/cards/SWSH12/Donphan_92.py index c8b2d61..3c52999 100644 --- a/spirit/game/scripts/cards/SWSH12/Donphan_92.py +++ b/spirit/game/scripts/cards/SWSH12/Donphan_92.py @@ -1,6 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def overspin(ctx): + state = ctx.session.turn_state + if state.entered_play_turn.get(ctx.attacker.entity_id) == state.turn_number: + return + await ctx.deal_damage() + + card = PokemonCardDef( guid="eb78afbe-8fda-5b51-a486-dc74f3b1a34a", key="SWSH12", @@ -24,7 +32,7 @@ card = PokemonCardDef( game_text="If this Pok\u00e9mon evolved during this turn, this attack does nothing.", cost={PokemonTypes.FIGHTING: 1}, damage=110, - effect=unimplemented, + effect=overspin, ), Attack( title="Giant Fangs", diff --git a/spirit/game/scripts/cards/SWSH12/Dragapult_89.py b/spirit/game/scripts/cards/SWSH12/Dragapult_89.py index e70357a..1ad18d1 100644 --- a/spirit/game/scripts/cards/SWSH12/Dragapult_89.py +++ b/spirit/game/scripts/cards/SWSH12/Dragapult_89.py @@ -1,5 +1,37 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.session.effects import full_stack + + +async def dragon_launcher(ctx): + """Discard up to (opponent's Pokémon in play) Benched Dreepy; for each + discarded, choose a different opposing Pokémon and do 100 raw damage.""" + dreepy = [p for p in ctx.my_bench() + if p.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) == "Dreepy"] + opponents = ctx.opponent_pokemon_in_play() + max_discard = min(len(dreepy), len(opponents)) + if max_discard <= 0: + return + picks = await ctx.choose_cards( + dreepy, max_discard, minimum=0, + prompt=f"Choose up to {max_discard} Dreepy to discard from your Bench", + ) + if not picks: + return + for pokemon in picks: + await ctx.discard_cards(full_stack(pokemon)) + remaining_targets = list(opponents) + for _ in picks: + if not remaining_targets: + break + target = await ctx.choose_pokemon( + remaining_targets, "Choose 1 of your opponent's Pokémon to damage" + ) + if target is None: + break + await ctx.deal_damage(100, target=target, apply_modifiers=False) + remaining_targets.remove(target) + card = PokemonCardDef( guid="00fe26b0-03f9-5898-b0e5-34d69140da53", @@ -22,9 +54,9 @@ card = PokemonCardDef( abilities=[ Attack( title="Dragon Launcher", - game_text="Discard a number of your Benched Dreepy up to the number of your opponent's Pok\u00e9mon in play. Then, for each Dreepy you discarded in this way, choose 1 of your opponent's Pok\u00e9mon and do 100 damage to it. You can't choose the same Pok\u00e9mon more than once. This damage isn't affected by Weakness or Resistance.", + game_text="Discard a number of your Benched Dreepy up to the number of your opponent's Pokémon in play. Then, for each Dreepy you discarded in this way, choose 1 of your opponent's Pokémon and do 100 damage to it. You can't choose the same Pokémon more than once. This damage isn't affected by Weakness or Resistance.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=dragon_launcher, ), Attack( title="Spooky Shot", @@ -32,4 +64,4 @@ card = PokemonCardDef( damage=120, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH12/Dragonair_130.py b/spirit/game/scripts/cards/SWSH12/Dragonair_130.py index 7e4bfd2..47f7e87 100644 --- a/spirit/game/scripts/cards/SWSH12/Dragonair_130.py +++ b/spirit/game/scripts/cards/SWSH12/Dragonair_130.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="fd366d02-07e3-5328-9844-2d6361d9b65f", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.COLORLESS: 2}, damage=30, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), Attack( title="Surf", diff --git a/spirit/game/scripts/cards/SWSH12/Dragonite_131.py b/spirit/game/scripts/cards/SWSH12/Dragonite_131.py index 93ec44c..6a44908 100644 --- a/spirit/game/scripts/cards/SWSH12/Dragonite_131.py +++ b/spirit/game/scripts/cards/SWSH12/Dragonite_131.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_attach_energy +from spirit.game.card_effects.trainers import is_basic_energy_card card = PokemonCardDef( guid="125f90f4-a4c5-505d-9c2a-76523c000611", @@ -28,7 +30,7 @@ card = PokemonCardDef( game_text="Search your deck for up to 3 basic Energy cards and attach them to your Pok\u00e9mon in any way you like. Then, shuffle your deck.", cost={PokemonTypes.WATER: 1, PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 2}, damage=180, - effect=unimplemented, + effect=search_attach_energy(predicate=is_basic_energy_card, count=3, distribute=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Dratini_129.py b/spirit/game/scripts/cards/SWSH12/Dratini_129.py index aa28e67..bda9dd6 100644 --- a/spirit/game/scripts/cards/SWSH12/Dratini_129.py +++ b/spirit/game/scripts/cards/SWSH12/Dratini_129.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="db9d34c6-5ca1-54b2-9405-635b0675ec3b", @@ -23,7 +24,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=30, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=2, per_heads=30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Dreepy_87.py b/spirit/game/scripts/cards/SWSH12/Dreepy_87.py index b080646..68cc43f 100644 --- a/spirit/game/scripts/cards/SWSH12/Dreepy_87.py +++ b/spirit/game/scripts/cards/SWSH12/Dreepy_87.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack card = PokemonCardDef( guid="500a69fb-1abb-5c66-80b3-a60b4e1a52b2", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="This Pok\u00e9mon also does 10 damage to itself.", cost={PokemonTypes.PSYCHIC: 1}, damage=30, - effect=unimplemented, + effect=recoil_attack(10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Drowzee_60.py b/spirit/game/scripts/cards/SWSH12/Drowzee_60.py index 1ab1ea0..8bbf96e 100644 --- a/spirit/game/scripts/cards/SWSH12/Drowzee_60.py +++ b/spirit/game/scripts/cards/SWSH12/Drowzee_60.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="329a23c3-75ba-58ba-bd49-0b93b77cb2ff", @@ -29,7 +30,7 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Asleep.", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=20, - effect=unimplemented, + effect=condition_attack(SpecialConditions.ASLEEP), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Duosion_77.py b/spirit/game/scripts/cards/SWSH12/Duosion_77.py index 3dd2983..b7c8a15 100644 --- a/spirit/game/scripts/cards/SWSH12/Duosion_77.py +++ b/spirit/game/scripts/cards/SWSH12/Duosion_77.py @@ -1,6 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def cell_spear(ctx): + await ctx.deal_damage() + bench = ctx.opponent_bench() + if bench: + target = await ctx.choose_pokemon( + bench, "Choose 1 of your opponent's Benched Pokémon") + if target is not None: + await ctx.deal_damage(20, target=target, as_counters=True) + card = PokemonCardDef( guid="0b296202-f453-5378-8fb8-afc4582d9346", key="SWSH12", @@ -25,7 +35,7 @@ card = PokemonCardDef( game_text="Put 2 damage counters on 1 of your opponent's Benched Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 2}, damage=20, - effect=unimplemented, + effect=cell_spear, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Durant_13.py b/spirit/game/scripts/cards/SWSH12/Durant_13.py index 892d047..17a209f 100644 --- a/spirit/game/scripts/cards/SWSH12/Durant_13.py +++ b/spirit/game/scripts/cards/SWSH12/Durant_13.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.card_effects.trainers import is_basic_energy_card card = PokemonCardDef( guid="1078512e-71e0-53cf-ad16-79822e864f5f", @@ -22,7 +24,7 @@ card = PokemonCardDef( title="Energy Digging", game_text="Search your deck for up to 2 basic Energy cards, reveal them, and put them into your hand. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_to_hand(predicate=is_basic_energy_card, count=2, reveal=True), ), Attack( title="Bite", diff --git a/spirit/game/scripts/cards/SWSH12/Emolga_54.py b/spirit/game/scripts/cards/SWSH12/Emolga_54.py index bcbbb13..fd93df7 100644 --- a/spirit/game/scripts/cards/SWSH12/Emolga_54.py +++ b/spirit/game/scripts/cards/SWSH12/Emolga_54.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.session.effects import is_item_card card = PokemonCardDef( guid="ab408961-90f3-5b7e-9a28-6c7f8a1c8a01", @@ -22,7 +24,10 @@ card = PokemonCardDef( title="Lucky Find", game_text="Search your deck for an Item card, reveal it, and put it into your hand. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_to_hand( + is_item_card, count=1, minimum=0, reveal=True, + prompt="Choose an Item card to put into your hand.", + ), ), Attack( title="Static Shock", diff --git a/spirit/game/scripts/cards/SWSH12/Feebas_39.py b/spirit/game/scripts/cards/SWSH12/Feebas_39.py index e24c327..e987a59 100644 --- a/spirit/game/scripts/cards/SWSH12/Feebas_39.py +++ b/spirit/game/scripts/cards/SWSH12/Feebas_39.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities + + +async def ascension(ctx): + """Search the deck for a card that evolves from this Pokémon and put it + onto this Pokémon to evolve it. Then, shuffle the deck.""" + my_logic_name = ctx.source.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) + + def matches(c): + return c.get_attribute(AttrID.EVOLUTION_LOGIC_FROM) == my_logic_name + + picks = await ctx.search_deck( + matches, count=1, minimum=0, + prompt="Choose a card that evolves from this Pokémon.", + ) + if picks: + await ctx.evolve_pokemon(ctx.source, picks[0]) + await ctx.shuffle_deck() card = PokemonCardDef( guid="c8be909b-29ab-5047-bf86-c99670bfca5d", @@ -22,7 +39,7 @@ card = PokemonCardDef( title="Ascension", game_text="Search your deck for a card that evolves from this Pok\u00e9mon and put it onto this Pok\u00e9mon to evolve it. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=ascension, ), Attack( title="Splash", diff --git a/spirit/game/scripts/cards/SWSH12/Fennekin_25.py b/spirit/game/scripts/cards/SWSH12/Fennekin_25.py index 4505107..8ca9593 100644 --- a/spirit/game/scripts/cards/SWSH12/Fennekin_25.py +++ b/spirit/game/scripts/cards/SWSH12/Fennekin_25.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.session.effects import is_supporter_card card = PokemonCardDef( guid="8e5bbe0a-9b8e-5242-9e02-30ae1d696eee", @@ -22,7 +24,10 @@ card = PokemonCardDef( title="Lead", game_text="Search your deck for a Supporter card, reveal it, and put it into your hand. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_to_hand( + is_supporter_card, count=1, minimum=0, reveal=True, + prompt="Choose a Supporter card to put into your hand.", + ), ), Attack( title="Live Coal", diff --git a/spirit/game/scripts/cards/SWSH12/Froslass_43.py b/spirit/game/scripts/cards/SWSH12/Froslass_43.py index 7f7233d..b55348d 100644 --- a/spirit/game/scripts/cards/SWSH12/Froslass_43.py +++ b/spirit/game/scripts/cards/SWSH12/Froslass_43.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions + + +def _played_candice(ctx): + return ctx.played_trainer_this_turn("Candice") > 0 + + +async def frosty_jail(ctx): + """20+. If Candice was played from hand this turn, +90 damage and the + opponent's Active Pokemon is now Paralyzed.""" + boosted = _played_candice(ctx) + await ctx.deal_damage(110 if boosted else 20) + if boosted: + await ctx.apply_special_condition(ctx.defender, SpecialConditions.PARALYZED) + card = PokemonCardDef( guid="02297fec-5e45-54b1-a3d6-7b0fcd4eaf19", @@ -21,11 +35,11 @@ card = PokemonCardDef( abilities=[ Attack( title="Frosty Jail", - game_text="If you played Candice from your hand during this turn, this attack does 90 more damage, and your opponent's Active Pok\u00e9mon is now Paralyzed.", + game_text="If you played Candice from your hand during this turn, this attack does 90 more damage, and your opponent's Active Pokémon is now Paralyzed.", cost={PokemonTypes.WATER: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=frosty_jail, ), Attack( title="Frost Breath", @@ -33,4 +47,4 @@ card = PokemonCardDef( damage=60, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH12/GapejawBog_213.py b/spirit/game/scripts/cards/SWSH12/GapejawBog_213.py index 1289a1a..4fa20b5 100644 --- a/spirit/game/scripts/cards/SWSH12/GapejawBog_213.py +++ b/spirit/game/scripts/cards/SWSH12/GapejawBog_213.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import StadiumCardDef +from spirit.game.card_effects.trainers import gapejaw_bog_watch +from spirit.game.data_utils import StadiumCardDef, Ability, Triggers from spirit.game.attributes import Rarities card = StadiumCardDef( @@ -10,5 +11,13 @@ card = StadiumCardDef( subtypes=["Stadium"], collector_number=213, set_code="SWSH12", - rarity=Rarities.RareSecret + rarity=Rarities.RareSecret, + abilities=[ + Ability( + title="Gapejaw Bog", + game_text="Whenever either player puts a Basic Pokémon from their hand onto their Bench, put 2 damage counters on that Pokémon.", + trigger=Triggers.ON_POKEMON_BENCHED, + effect=gapejaw_bog_watch, + ), + ], ) diff --git a/spirit/game/scripts/cards/SWSH12/Gardevoir_69.py b/spirit/game/scripts/cards/SWSH12/Gardevoir_69.py index 0bb0e6b..386ba29 100644 --- a/spirit/game/scripts/cards/SWSH12/Gardevoir_69.py +++ b/spirit/game/scripts/cards/SWSH12/Gardevoir_69.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import requires_hand + + +async def refinement(ctx): + await ctx.discard_from_hand(1, prompt="Discard a card to use Refinement") + if await ctx.ask_yes_no("Draw 2 cards?"): + await ctx.draw_cards(2) + card = PokemonCardDef( guid="faddee7e-6330-5ee4-bf57-85b6e394f363", @@ -22,7 +30,9 @@ card = PokemonCardDef( Ability( title="Refinement", game_text="You must discard a card from your hand in order to use this Ability. Once during your turn, you may draw 2 cards.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=requires_hand(n=1), + effect=refinement, ), Attack( title="Magical Shot", diff --git a/spirit/game/scripts/cards/SWSH12/Glalie_42.py b/spirit/game/scripts/cards/SWSH12/Glalie_42.py index 6ad6785..a3a4830 100644 --- a/spirit/game/scripts/cards/SWSH12/Glalie_42.py +++ b/spirit/game/scripts/cards/SWSH12/Glalie_42.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if + + +def _played_candice(ctx): + return ctx.played_trainer_this_turn("Candice") > 0 + card = PokemonCardDef( guid="08abe688-454d-5dcd-bc3e-c6ae1bdf140e", @@ -30,7 +36,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 4}, damage=130, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_played_candice, 130), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Hawlucha_98.py b/spirit/game/scripts/cards/SWSH12/Hawlucha_98.py index 3c9112b..fbf6145 100644 --- a/spirit/game/scripts/cards/SWSH12/Hawlucha_98.py +++ b/spirit/game/scripts/cards/SWSH12/Hawlucha_98.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import draw_attack +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="07dfe2e5-0128-552d-b1fa-bd7c82016dd9", @@ -23,7 +25,7 @@ card = PokemonCardDef( title="Allure", game_text="Draw 2 cards.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=draw_attack(2), ), Attack( title="Acrobatics", @@ -31,7 +33,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 1}, damage=30, damage_operator="+", - effect=unimplemented, + effect=flip_damage(coins=2, bonus_per_heads=20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/HisuianArcanineV_179.py b/spirit/game/scripts/cards/SWSH12/HisuianArcanineV_179.py index cc6ed6e..64a45d2 100644 --- a/spirit/game/scripts/cards/SWSH12/HisuianArcanineV_179.py +++ b/spirit/game/scripts/cards/SWSH12/HisuianArcanineV_179.py @@ -1,5 +1,28 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy +from spirit.game.card_effects.pokemon import energy_provides_type + + +def _irresistible_force_condition(board, player_id, pokemon): + others = [p for p in board.pokemon_in_play(player_id) if p is not pokemon] + return any( + energy_provides_type(e, PokemonTypes.FIGHTING) + for p in others for e in board.attached_energies(p) + ) + + +async def irresistible_force(ctx): + others = [p for p in ctx.my_pokemon_in_play() if p is not ctx.source] + pool = [e for p in others for e in ctx.attached_energies(p) + if energy_provides_type(e, PokemonTypes.FIGHTING)] + if not pool: + return + picked = await ctx.choose_cards( + pool, 1, prompt="Choose a Fighting Energy to move to this Pokémon") + if picked: + await ctx.move_energy(picked[0], ctx.source) + card = PokemonCardDef( guid="433aa45a-eb4b-5b12-af98-0a4ac657a7e3", @@ -21,7 +44,9 @@ card = PokemonCardDef( Ability( title="Irresistible Force", game_text="As often as you like during your turn, you may move a Fighting Energy from 1 of your other Pok\u00e9mon to this Pok\u00e9mon.", - effect=unimplemented, + activation=Activations.UNLIMITED, + condition=_irresistible_force_condition, + effect=irresistible_force, ), Attack( title="Rock Bullet", @@ -29,7 +54,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 4}, damage=90, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("self", energy_type=PokemonTypes.FIGHTING), 30, base=90), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/HisuianArcanineV_90.py b/spirit/game/scripts/cards/SWSH12/HisuianArcanineV_90.py index 1759699..230fea8 100644 --- a/spirit/game/scripts/cards/SWSH12/HisuianArcanineV_90.py +++ b/spirit/game/scripts/cards/SWSH12/HisuianArcanineV_90.py @@ -1,5 +1,28 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy +from spirit.game.card_effects.pokemon import energy_provides_type + + +def _irresistible_force_condition(board, player_id, pokemon): + others = [p for p in board.pokemon_in_play(player_id) if p is not pokemon] + return any( + energy_provides_type(e, PokemonTypes.FIGHTING) + for p in others for e in board.attached_energies(p) + ) + + +async def irresistible_force(ctx): + others = [p for p in ctx.my_pokemon_in_play() if p is not ctx.source] + pool = [e for p in others for e in ctx.attached_energies(p) + if energy_provides_type(e, PokemonTypes.FIGHTING)] + if not pool: + return + picked = await ctx.choose_cards( + pool, 1, prompt="Choose a Fighting Energy to move to this Pokémon") + if picked: + await ctx.move_energy(picked[0], ctx.source) + card = PokemonCardDef( guid="6176cd9c-9e88-5d36-a093-b249df90d6a8", @@ -21,7 +44,9 @@ card = PokemonCardDef( Ability( title="Irresistible Force", game_text="As often as you like during your turn, you may move a Fighting Energy from 1 of your other Pok\u00e9mon to this Pok\u00e9mon.", - effect=unimplemented, + activation=Activations.UNLIMITED, + condition=_irresistible_force_condition, + effect=irresistible_force, ), Attack( title="Rock Bullet", @@ -29,7 +54,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 4}, damage=90, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("self", energy_type=PokemonTypes.FIGHTING), 30, base=90), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/HisuianBraviary_149.py b/spirit/game/scripts/cards/SWSH12/HisuianBraviary_149.py index 81dfded..e4c8277 100644 --- a/spirit/game/scripts/cards/SWSH12/HisuianBraviary_149.py +++ b/spirit/game/scripts/cards/SWSH12/HisuianBraviary_149.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions, AttrID +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.session.passives import effective_max_hp + + +async def _eerie_cry(ctx): + for pokemon in ctx.opponent_pokemon_in_play(): + if pokemon.get_attribute(AttrID.HP, 0) < effective_max_hp(ctx.board, pokemon): + await ctx.deal_damage(30, target=pokemon, as_counters=True) card = PokemonCardDef( guid="8da673b4-dbf9-5585-b21e-84e5ff94a019", @@ -24,14 +32,14 @@ card = PokemonCardDef( title="Eerie Cry", game_text="Put 3 damage counters on each of your opponent's Pok\u00e9mon that has any damage counters on it.", cost={}, - effect=unimplemented, + effect=_eerie_cry, ), Attack( title="Mind Bend", game_text="Your opponent's Active Pok\u00e9mon is now Confused.", cost={PokemonTypes.COLORLESS: 3}, damage=80, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/HisuianLilligant_10.py b/spirit/game/scripts/cards/SWSH12/HisuianLilligant_10.py index dd9f47b..88c7e79 100644 --- a/spirit/game/scripts/cards/SWSH12/HisuianLilligant_10.py +++ b/spirit/game/scripts/cards/SWSH12/HisuianLilligant_10.py @@ -1,5 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import distribute_energy +from spirit.game.card_effects.trainers import is_grass_energy_card + + +async def swelling_scent(ctx): + picks = await ctx.search_deck( + is_grass_energy_card, count=2, minimum=0, + prompt="Choose up to 2 Grass Energy cards to attach.", + ) + if picks: + bench = ctx.my_bench() + if bench: + await distribute_energy(ctx, picks, bench) + await ctx.shuffle_deck() + card = PokemonCardDef( guid="d21fafd9-95be-55db-88e0-51508533fc03", @@ -23,7 +38,7 @@ card = PokemonCardDef( title="Swelling Scent", game_text="Search your deck for up to 2 Grass Energy cards and attach them to your Benched Pok\u00e9mon in any way you like. Then, shuffle your deck.", cost={}, - effect=unimplemented, + effect=swelling_scent, ), Attack( title="Spiral Kick", diff --git a/spirit/game/scripts/cards/SWSH12/HoOhV_140.py b/spirit/game/scripts/cards/SWSH12/HoOhV_140.py index 810235c..7378c3a 100644 --- a/spirit/game/scripts/cards/SWSH12/HoOhV_140.py +++ b/spirit/game/scripts/cards/SWSH12/HoOhV_140.py @@ -1,5 +1,30 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.attacks_common import damage_per +from spirit.game.card_effects.support_common import attach_from_discard, requires_bench_space +from spirit.game.card_effects.trainers import is_basic_energy_card + + +async def reviving_flame(ctx): + """Once per turn from discard: you may Bench this Pokémon, then attach up + to 4 basic Energy from discard to it. Using this Ability ends your turn.""" + if not await ctx.ask_yes_no("Put this Pokémon onto your Bench?"): + return + if not await ctx.bench_pokemon(ctx.source): + return + ctx.ends_turn = True + await attach_from_discard(predicate=is_basic_energy_card, count=4, + target="self", minimum=0)(ctx) + + +def _basic_energy_types_count(ctx): + types = set() + for e in ctx.attached_energies(ctx.attacker): + if is_basic_energy_card(e): + for t in (e.get_attribute(AttrID.POKEMON_TYPES) or []): + types.add(t) + return len(types) + card = PokemonCardDef( guid="c3949c42-5090-52af-9991-9916a3013f65", @@ -22,7 +47,10 @@ card = PokemonCardDef( Ability( title="Reviving Flame", game_text="Once during your turn, if this Pok\u00e9mon is in your discard pile, you may put it onto your Bench. If you do, attach up to 4 basic Energy cards from your discard pile to this Pok\u00e9mon. If you use this Ability, your turn ends.", - effect=unimplemented, + usable_from="discard", + activation=Activations.ONCE_PER_TURN, + condition=requires_bench_space(1), + effect=reviving_flame, ), Attack( title="Rainbow Burn", @@ -30,7 +58,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 4}, damage=100, damage_operator="+", - effect=unimplemented, + effect=damage_per(_basic_energy_types_count, 30, base=100), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/HoOhV_187.py b/spirit/game/scripts/cards/SWSH12/HoOhV_187.py index 0e4791e..d56af0a 100644 --- a/spirit/game/scripts/cards/SWSH12/HoOhV_187.py +++ b/spirit/game/scripts/cards/SWSH12/HoOhV_187.py @@ -1,5 +1,30 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.attacks_common import damage_per +from spirit.game.card_effects.support_common import attach_from_discard, requires_bench_space +from spirit.game.card_effects.trainers import is_basic_energy_card + + +async def reviving_flame(ctx): + """Once per turn from discard: you may Bench this Pokémon, then attach up + to 4 basic Energy from discard to it. Using this Ability ends your turn.""" + if not await ctx.ask_yes_no("Put this Pokémon onto your Bench?"): + return + if not await ctx.bench_pokemon(ctx.source): + return + ctx.ends_turn = True + await attach_from_discard(predicate=is_basic_energy_card, count=4, + target="self", minimum=0)(ctx) + + +def _basic_energy_types_count(ctx): + types = set() + for e in ctx.attached_energies(ctx.attacker): + if is_basic_energy_card(e): + for t in (e.get_attribute(AttrID.POKEMON_TYPES) or []): + types.add(t) + return len(types) + card = PokemonCardDef( guid="1c0e13b7-d61c-50d2-9677-915ded60d739", @@ -22,7 +47,10 @@ card = PokemonCardDef( Ability( title="Reviving Flame", game_text="Once during your turn, if this Pok\u00e9mon is in your discard pile, you may put it onto your Bench. If you do, attach up to 4 basic Energy cards from your discard pile to this Pok\u00e9mon. If you use this Ability, your turn ends.", - effect=unimplemented, + usable_from="discard", + activation=Activations.ONCE_PER_TURN, + condition=requires_bench_space(1), + effect=reviving_flame, ), Attack( title="Rainbow Burn", @@ -30,7 +58,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 4}, damage=100, damage_operator="+", - effect=unimplemented, + effect=damage_per(_basic_energy_types_count, 30, base=100), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Honchkrow_107.py b/spirit/game/scripts/cards/SWSH12/Honchkrow_107.py index 39bb15d..bf8523d 100644 --- a/spirit/game/scripts/cards/SWSH12/Honchkrow_107.py +++ b/spirit/game/scripts/cards/SWSH12/Honchkrow_107.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.support_common import draw_attack, switch_self_attack + + +def _is_murkrow(pokemon): + return pokemon.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) == "Murkrow" + card = PokemonCardDef( guid="0aa6b004-ff3e-5730-9f32-4ca3d6c4c9da", @@ -24,14 +30,14 @@ card = PokemonCardDef( title="Triple Draw", game_text="Draw 3 cards.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=draw_attack(3), ), Attack( title="Callous Wings", game_text="You may switch this Pok\u00e9mon with 1 of your Benched Murkrow.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 2}, damage=120, - effect=unimplemented, + effect=switch_self_attack(optional=True, bench_predicate=_is_murkrow), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Hypno_61.py b/spirit/game/scripts/cards/SWSH12/Hypno_61.py index fbfaf63..dc68135 100644 --- a/spirit/game/scripts/cards/SWSH12/Hypno_61.py +++ b/spirit/game/scripts/cards/SWSH12/Hypno_61.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.support_common import search_to_bench +from spirit.game.session.effects import is_pokemon_card + + +def _is_stage1(card): + return is_pokemon_card(card) and card.get_attribute(AttrID.STAGE) == PokemonStage.STAGE1.value + card = PokemonCardDef( guid="cfef009a-2f2d-5e2a-ab36-98e452bd2dc5", @@ -24,7 +31,7 @@ card = PokemonCardDef( title="Psy Call", game_text="Search your deck for up to 2 Stage 1 Pok\u00e9mon and put them onto your Bench. Then, shuffle your deck.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=search_to_bench(predicate=_is_stage1, count=2), ), Attack( title="Zen Headbutt", diff --git a/spirit/game/scripts/cards/SWSH12/Incineroar_32.py b/spirit/game/scripts/cards/SWSH12/Incineroar_32.py index 5b6398e..a1982b8 100644 --- a/spirit/game/scripts/cards/SWSH12/Incineroar_32.py +++ b/spirit/game/scripts/cards/SWSH12/Incineroar_32.py @@ -1,5 +1,43 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import ( + PokemonCardDef, Attack, Ability, def_for, evolves_from_chain, +) +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import self_energy_discard_attack +from spirit.game.session.effects import full_stack, is_pokemon_card + + +def _logic_name(definition): + spec = definition.extra_attributes.get(str(AttrID.EVOLUTION_LOGIC_NAME.value)) + return spec.get("value") if isinstance(spec, dict) else None + + +async def secret_attack(ctx): + """Choose an attack from 1 of this Pokémon's previous Evolutions and use it as this attack.""" + names = set(evolves_from_chain(ctx.attacker.archetype_id)) + candidates = [] + if names: + for card in full_stack(ctx.attacker)[1:]: + if not is_pokemon_card(card): + continue + definition = def_for(card.archetype_id) + if definition is None or _logic_name(definition) not in names: + continue + for ability in getattr(definition, "abilities", []): + if isinstance(ability, Attack): + candidates.append((card, ability)) + if not candidates: + return + picked = await ctx.choose_attack_to_copy(candidates, "Choose an attack to copy") + if picked is None: + return + _, chosen = picked + if not await ctx.use_attack(chosen): + return + if getattr(chosen, "locks_next_turn", False): + for entry in ctx.attacker.get_attribute(AttrID.PIE_ABILITIES) or []: + if isinstance(entry, dict) and entry.get("abilityType") == "Attack": + ctx.session.turn_state.lock_attack(ctx.attacker.entity_id, entry["abilityID"]) + card = PokemonCardDef( guid="f959df23-74b4-575f-a3c4-ad53b717a10d", @@ -23,14 +61,14 @@ card = PokemonCardDef( title="Secret Attack", game_text="Choose an attack from 1 of this Pok\u00e9mon's previous Evolutions and use it as this attack.", cost={PokemonTypes.FIRE: 1}, - effect=unimplemented, + effect=secret_attack, ), Attack( title="Flare Shot", game_text="Discard all Energy from this Pok\u00e9mon.", cost={PokemonTypes.FIRE: 2}, damage=180, - effect=unimplemented, + effect=self_energy_discard_attack(all_energy=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Indeedee_86.py b/spirit/game/scripts/cards/SWSH12/Indeedee_86.py index 7294236..a2c0866 100644 --- a/spirit/game/scripts/cards/SWSH12/Indeedee_86.py +++ b/spirit/game/scripts/cards/SWSH12/Indeedee_86.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_hand card = PokemonCardDef( guid="e704c0e0-a43b-54fd-b219-8e08ae58c883", @@ -23,7 +24,8 @@ card = PokemonCardDef( title="Smart Service", game_text="If you go first, you can use this attack during your first turn. Search your deck for a card and put it into your hand. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + usable_first_turn=True, + effect=search_to_hand(count=1, reveal=False), ), Attack( title="Smack", diff --git a/spirit/game/scripts/cards/SWSH12/Jynx_62.py b/spirit/game/scripts/cards/SWSH12/Jynx_62.py index f931bb9..bbf2bb2 100644 --- a/spirit/game/scripts/cards/SWSH12/Jynx_62.py +++ b/spirit/game/scripts/cards/SWSH12/Jynx_62.py @@ -1,5 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import damage_per, damage_counters_on +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, is_pokemon_v from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.passives import Passive + + +class SelfishLipsPassive(Passive): + def modify_prizes_for_knockout(self, pokemon, ctx, count, carrier): + if pokemon is not carrier or not ctx.is_attack_effect(): + return count + attacker = getattr(ctx, "attacker", None) + if attacker is None or attacker.owning_player_id == pokemon.owning_player_id: + return count + if is_pokemon_v(attacker.archetype_id): + return 0 + return count + card = PokemonCardDef( guid="b8036ff5-5469-5c03-b35d-8af455498be8", @@ -21,16 +36,16 @@ card = PokemonCardDef( abilities=[ Ability( title="Selfish Lips", - game_text="If this Pok\u00e9mon is Knocked Out by damage from an attack from your opponent's Pok\u00e9mon V, your opponent can't take any Prize cards for it.", - effect=unimplemented, + game_text="If this Pokémon is Knocked Out by damage from an attack from your opponent's Pokémon V, your opponent can't take any Prize cards for it.", + passive=SelfishLipsPassive(), ), Attack( title="Psychic Assault", - game_text="This attack does 10 more damage for each damage counter on your opponent's Active Pok\u00e9mon.", + game_text="This attack does 10 more damage for each damage counter on your opponent's Active Pokémon.", cost={PokemonTypes.COLORLESS: 2}, damage=10, damage_operator="+", - effect=unimplemented, + effect=damage_per(damage_counters_on("defender"), 10, base=10), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH12/Keldeo_46.py b/spirit/game/scripts/cards/SWSH12/Keldeo_46.py index 1359356..88e6e41 100644 --- a/spirit/game/scripts/cards/SWSH12/Keldeo_46.py +++ b/spirit/game/scripts/cards/SWSH12/Keldeo_46.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, named_in_play card = PokemonCardDef( guid="53480145-ea0b-50e8-b575-e439d9db1497", @@ -29,7 +30,10 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=50, damage_operator="+", - effect=unimplemented, + effect=bonus_if( + named_in_play("Cobalion", "Terrakion", "Virizion", require_all=True), + 170, + ), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Kirlia_68.py b/spirit/game/scripts/cards/SWSH12/Kirlia_68.py index 1249058..592fd14 100644 --- a/spirit/game/scripts/cards/SWSH12/Kirlia_68.py +++ b/spirit/game/scripts/cards/SWSH12/Kirlia_68.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import discard_then_draw, requires_hand card = PokemonCardDef( guid="858d9b3b-6269-5934-8887-10a22a8b5a24", @@ -22,7 +23,10 @@ card = PokemonCardDef( Ability( title="Refinement", game_text="You must discard a card from your hand in order to use this Ability. Once during your turn, you may draw 2 cards.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=requires_hand(n=1), + effect=discard_then_draw(1, 2, optional=False, + prompt="Discard a card to use Refinement"), ), Attack( title="Slap", diff --git a/spirit/game/scripts/cards/SWSH12/Klang_124.py b/spirit/game/scripts/cards/SWSH12/Klang_124.py index f9a2197..327e5e0 100644 --- a/spirit/game/scripts/cards/SWSH12/Klang_124.py +++ b/spirit/game/scripts/cards/SWSH12/Klang_124.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="2f061ff4-5307-53f7-abdd-ec1e129962b8", @@ -26,7 +27,7 @@ card = PokemonCardDef( cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 2}, damage=80, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=2, per_heads=80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Klink_123.py b/spirit/game/scripts/cards/SWSH12/Klink_123.py index 70f2632..fb0bf53 100644 --- a/spirit/game/scripts/cards/SWSH12/Klink_123.py +++ b/spirit/game/scripts/cards/SWSH12/Klink_123.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.session.effects import is_pokemon_card card = PokemonCardDef( guid="87fb204a-a43c-57d2-ba60-0f450232415e", @@ -23,7 +25,10 @@ card = PokemonCardDef( title="Call Sign", game_text="Search your deck for a Pok\u00e9mon, reveal it, and put it into your hand. Then, shuffle your deck.", cost={PokemonTypes.METAL: 1}, - effect=unimplemented, + effect=search_to_hand( + is_pokemon_card, count=1, reveal=True, + prompt="Choose a Pokémon to put into your hand.", + ), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Klinklang_125.py b/spirit/game/scripts/cards/SWSH12/Klinklang_125.py index 95d9341..e0d49d5 100644 --- a/spirit/game/scripts/cards/SWSH12/Klinklang_125.py +++ b/spirit/game/scripts/cards/SWSH12/Klinklang_125.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.card_effects.support_common import search_attach_energy card = PokemonCardDef( guid="24dd334a-feb7-5f01-8dcd-0c2811f8afbe", @@ -23,7 +25,8 @@ card = PokemonCardDef( Ability( title="Triple Gears", game_text="When you play this Pok\u00e9mon from your hand to evolve 1 of your Pok\u00e9mon during your turn, you may search your deck for up to 3 basic Energy cards and attach them to your Pok\u00e9mon in any way you like. Then, shuffle your deck.", - effect=unimplemented, + trigger=Triggers.ON_EVOLVE, + effect=search_attach_energy(predicate=is_basic_energy_card, count=3, distribute=True), ), Attack( title="Power Beam", diff --git a/spirit/game/scripts/cards/SWSH12/Krokorok_112.py b/spirit/game/scripts/cards/SWSH12/Krokorok_112.py index 5c5a04b..cf65267 100644 --- a/spirit/game/scripts/cards/SWSH12/Krokorok_112.py +++ b/spirit/game/scripts/cards/SWSH12/Krokorok_112.py @@ -1,6 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def crunch(ctx): + """Flip a coin. If heads, discard an Energy from the opponent's Active.""" + await ctx.deal_damage() + heads = (await ctx.flip_coins(1, ctx.ability.title))[0] + if heads: + target = ctx.opponent_active() + if target is not None and not ctx.effects_blocked(target): + await ctx.discard_energy_from( + target, 1, prompt="Choose Energy to discard from the Defending Pokémon") + + card = PokemonCardDef( guid="e0292492-5890-5514-b8f6-75bb3b23b22f", key="SWSH12", @@ -24,7 +36,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, discard an Energy from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 2}, damage=60, - effect=unimplemented, + effect=crunch, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Krookodile_113.py b/spirit/game/scripts/cards/SWSH12/Krookodile_113.py index 8c812c6..afaaa24 100644 --- a/spirit/game/scripts/cards/SWSH12/Krookodile_113.py +++ b/spirit/game/scripts/cards/SWSH12/Krookodile_113.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack, discard_random_from_hand + + +async def bully_of_the_sands(ctx): + """On evolve, or when Active and KO'd by an opponent's attack: you may + discard a random card from the opponent's hand.""" + if ctx.source in ctx.discard_pile(ctx.player_id) and not ctx.ko_from_attack: + return + if ctx.hand_size(ctx.opponent_id) == 0: + return + if await ctx.ask_yes_no("Discard a random card from your opponent's hand?"): + await discard_random_from_hand(ctx, player_id=ctx.opponent_id, count=1) + card = PokemonCardDef( guid="8e87fac1-189b-5df0-8b56-e9feab320031", @@ -22,14 +35,15 @@ card = PokemonCardDef( Ability( title="Bully of the Sands", game_text="When you play this Pok\u00e9mon from your hand to evolve 1 of your Pok\u00e9mon during your turn, you may discard a random card from your opponent's hand. If this Pok\u00e9mon is your Active Pok\u00e9mon and is Knocked Out by damage from an opponent's attack, you may discard a random card from your opponent's hand.", - effect=unimplemented, + trigger=(Triggers.ON_EVOLVE, Triggers.ON_KNOCKED_OUT), + effect=bully_of_the_sands, ), Attack( title="Double-Edge", game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 2}, damage=160, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Lanturn_52.py b/spirit/game/scripts/cards/SWSH12/Lanturn_52.py index 4c2b380..464537b 100644 --- a/spirit/game/scripts/cards/SWSH12/Lanturn_52.py +++ b/spirit/game/scripts/cards/SWSH12/Lanturn_52.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import self_energy_discard_attack card = PokemonCardDef( guid="a07e2b8c-7aa7-5ad1-a846-f876dcdf9032", @@ -29,7 +30,7 @@ card = PokemonCardDef( game_text="Discard 2 Energy from this Pok\u00e9mon.", cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 1}, damage=160, - effect=unimplemented, + effect=self_energy_discard_attack(count=2), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Litten_30.py b/spirit/game/scripts/cards/SWSH12/Litten_30.py index 5746dea..bb30917 100644 --- a/spirit/game/scripts/cards/SWSH12/Litten_30.py +++ b/spirit/game/scripts/cards/SWSH12/Litten_30.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import damage_per, damage_counters_on +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -29,7 +30,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 2}, damage=20, damage_operator="x", - effect=unimplemented, + effect=damage_per(damage_counters_on("self"), 20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Lopunny_145.py b/spirit/game/scripts/cards/SWSH12/Lopunny_145.py index e3d8466..0024ae0 100644 --- a/spirit/game/scripts/cards/SWSH12/Lopunny_145.py +++ b/spirit/game/scripts/cards/SWSH12/Lopunny_145.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="30bbbfb9-8a78-5cfe-8e79-dfd830bfed8b", @@ -30,7 +31,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=100, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=2, per_heads=100), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/LugiaV_138.py b/spirit/game/scripts/cards/SWSH12/LugiaV_138.py index 5bb631f..019c54c 100644 --- a/spirit/game/scripts/cards/SWSH12/LugiaV_138.py +++ b/spirit/game/scripts/cards/SWSH12/LugiaV_138.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import read_the_wind, aero_dive card = PokemonCardDef( guid="cdf444af-6bf1-5811-a27b-288905df1cbb", @@ -23,14 +24,14 @@ card = PokemonCardDef( title="Read the Wind", game_text="Discard a card from your hand. If you do, draw 3 cards.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=read_the_wind, ), Attack( title="Aero Dive", game_text="You may discard a Stadium in play.", cost={PokemonTypes.COLORLESS: 4}, damage=130, - effect=unimplemented, + effect=aero_dive, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/LugiaV_185.py b/spirit/game/scripts/cards/SWSH12/LugiaV_185.py index 40273e1..bc8046f 100644 --- a/spirit/game/scripts/cards/SWSH12/LugiaV_185.py +++ b/spirit/game/scripts/cards/SWSH12/LugiaV_185.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import read_the_wind, aero_dive card = PokemonCardDef( guid="8f37693b-3a5b-5518-92f1-285083214ff7", @@ -23,14 +24,14 @@ card = PokemonCardDef( title="Read the Wind", game_text="Discard a card from your hand. If you do, draw 3 cards.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=read_the_wind, ), Attack( title="Aero Dive", game_text="You may discard a Stadium in play.", cost={PokemonTypes.COLORLESS: 4}, damage=130, - effect=unimplemented, + effect=aero_dive, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/MagearnaV_128.py b/spirit/game/scripts/cards/SWSH12/MagearnaV_128.py index 971b8bf..2d9bd9f 100644 --- a/spirit/game/scripts/cards/SWSH12/MagearnaV_128.py +++ b/spirit/game/scripts/cards/SWSH12/MagearnaV_128.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_special_energy +from spirit.game.card_effects.attacks_common import snipe_attack, bonus_if + + +def _has_special_energy(ctx) -> bool: + return any(is_special_energy(e) for e in ctx.attached_energies(ctx.attacker)) + card = PokemonCardDef( guid="09e45675-0e25-59f3-a42d-f3919594ce92", @@ -23,7 +30,7 @@ card = PokemonCardDef( title="Gear Throw", game_text="This attack does 30 damage to 1 of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.METAL: 1}, - effect=unimplemented, + effect=snipe_attack(30, pool="any", count=1), ), Attack( title="Special Laser", @@ -31,7 +38,7 @@ card = PokemonCardDef( cost={PokemonTypes.METAL: 2, PokemonTypes.COLORLESS: 1}, damage=100, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_has_special_energy, 120), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/MagearnaV_182.py b/spirit/game/scripts/cards/SWSH12/MagearnaV_182.py index 3b03f54..0684647 100644 --- a/spirit/game/scripts/cards/SWSH12/MagearnaV_182.py +++ b/spirit/game/scripts/cards/SWSH12/MagearnaV_182.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_special_energy +from spirit.game.card_effects.attacks_common import snipe_attack, bonus_if + + +def _has_special_energy(ctx) -> bool: + return any(is_special_energy(e) for e in ctx.attached_energies(ctx.attacker)) + card = PokemonCardDef( guid="e7c90e6e-d02d-570f-95ee-0b0688f0837a", @@ -23,7 +30,7 @@ card = PokemonCardDef( title="Gear Throw", game_text="This attack does 30 damage to 1 of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.METAL: 1}, - effect=unimplemented, + effect=snipe_attack(30, pool="any", count=1), ), Attack( title="Special Laser", @@ -31,7 +38,7 @@ card = PokemonCardDef( cost={PokemonTypes.METAL: 2, PokemonTypes.COLORLESS: 1}, damage=100, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_has_special_energy, 120), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Mareanie_114.py b/spirit/game/scripts/cards/SWSH12/Mareanie_114.py index 0c14128..afa7102 100644 --- a/spirit/game/scripts/cards/SWSH12/Mareanie_114.py +++ b/spirit/game/scripts/cards/SWSH12/Mareanie_114.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="01ec2a8b-31ab-5285-8c4c-7cce9810513a", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Poison Sting", game_text="Your opponent's Active Pok\u00e9mon is now Poisoned.", cost={PokemonTypes.DARKNESS: 1}, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/MawileVSTAR_200.py b/spirit/game/scripts/cards/SWSH12/MawileVSTAR_200.py index 9de12a2..f0b3202 100644 --- a/spirit/game/scripts/cards/SWSH12/MawileVSTAR_200.py +++ b/spirit/game/scripts/cards/SWSH12/MawileVSTAR_200.py @@ -1,6 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import bonus_if +from spirit.game.card_effects.pokemon import in_active_spot +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def star_rondo(ctx): + if not await ctx.ask_yes_no("Switch this Pokémon with your Active Pokémon?"): + return + await ctx.switch_active(ctx.player_id, ctx.source) + opp_active = ctx.opponent_active() + opp_bench = ctx.opponent_bench() + if opp_active is None or not opp_bench or ctx.effects_blocked(opp_active): + return + target = await ctx.choose_pokemon(opp_bench, "Choose the opponent's new Active Pokémon") + if target is not None: + await ctx.switch_active(ctx.opponent_id, target) + card = PokemonCardDef( guid="50374220-b966-5236-9452-92bc151d71a1", key="SWSH12", @@ -22,7 +37,9 @@ card = PokemonCardDef( Ability( title="Star Rondo", game_text="During your turn, if this Pok\u00e9mon is on your Bench, you may switch it with your Active Pok\u00e9mon. If you do, switch 1 of your opponent's Benched Pok\u00e9mon with their Active Pok\u00e9mon. (You can't use more than 1 VSTAR Power in a game.)", - effect=unimplemented, + vstar=True, + condition=lambda board, pid, poke: not in_active_spot(board, pid, poke), + effect=star_rondo, ), Attack( title="Sudden Eater", @@ -30,7 +47,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=90, damage_operator="+", - effect=unimplemented, + effect=bonus_if(lambda ctx: ctx.entered_active_this_turn(ctx.attacker), 90), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/MawileVSTAR_71.py b/spirit/game/scripts/cards/SWSH12/MawileVSTAR_71.py index 92dfc02..553d7e4 100644 --- a/spirit/game/scripts/cards/SWSH12/MawileVSTAR_71.py +++ b/spirit/game/scripts/cards/SWSH12/MawileVSTAR_71.py @@ -1,6 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import bonus_if +from spirit.game.card_effects.pokemon import in_active_spot +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def star_rondo(ctx): + if not await ctx.ask_yes_no("Switch this Pokémon with your Active Pokémon?"): + return + await ctx.switch_active(ctx.player_id, ctx.source) + opp_active = ctx.opponent_active() + opp_bench = ctx.opponent_bench() + if opp_active is None or not opp_bench or ctx.effects_blocked(opp_active): + return + target = await ctx.choose_pokemon(opp_bench, "Choose the opponent's new Active Pokémon") + if target is not None: + await ctx.switch_active(ctx.opponent_id, target) + card = PokemonCardDef( guid="18c8faf9-83e6-5fcf-bc5a-b2b6c90114bb", key="SWSH12", @@ -22,7 +37,9 @@ card = PokemonCardDef( Ability( title="Star Rondo", game_text="During your turn, if this Pok\u00e9mon is on your Bench, you may switch it with your Active Pok\u00e9mon. If you do, switch 1 of your opponent's Benched Pok\u00e9mon with their Active Pok\u00e9mon. (You can't use more than 1 VSTAR Power in a game.)", - effect=unimplemented, + vstar=True, + condition=lambda board, pid, poke: not in_active_spot(board, pid, poke), + effect=star_rondo, ), Attack( title="Sudden Eater", @@ -30,7 +47,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=90, damage_operator="+", - effect=unimplemented, + effect=bonus_if(lambda ctx: ctx.entered_active_this_turn(ctx.attacker), 90), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/MawileV_178.py b/spirit/game/scripts/cards/SWSH12/MawileV_178.py index bf85713..7d5b2c0 100644 --- a/spirit/game/scripts/cards/SWSH12/MawileV_178.py +++ b/spirit/game/scripts/cards/SWSH12/MawileV_178.py @@ -1,5 +1,24 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import discard_random_from_hand + + +async def pouty_slap(ctx): + """30 damage; flip a coin, discard an Energy from the Defending Pokemon on heads.""" + await ctx.deal_damage() + heads = (await ctx.flip_coins(1, ctx.ability.title))[0] + if not heads: + return + target = ctx.opponent_active() + if target is not None and not ctx.effects_blocked(target): + await ctx.discard_energy_from( + target, 1, prompt="Choose Energy to discard from the Defending Pokémon") + + +async def chomp_down(ctx): + """100 damage; discard a random card from the opponent's hand.""" + await ctx.deal_damage() + await discard_random_from_hand(ctx, ctx.opponent_id, 1) card = PokemonCardDef( guid="1159942f-461f-5b31-b4ae-ddf854286a1d", @@ -23,14 +42,14 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, discard an Energy from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.PSYCHIC: 1}, damage=30, - effect=unimplemented, + effect=pouty_slap, ), Attack( title="Chomp Down", game_text="Discard a random card from your opponent's hand.", cost={PokemonTypes.COLORLESS: 3}, damage=100, - effect=unimplemented, + effect=chomp_down, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/MawileV_70.py b/spirit/game/scripts/cards/SWSH12/MawileV_70.py index f6b9518..07d4532 100644 --- a/spirit/game/scripts/cards/SWSH12/MawileV_70.py +++ b/spirit/game/scripts/cards/SWSH12/MawileV_70.py @@ -1,5 +1,24 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import discard_random_from_hand + + +async def pouty_slap(ctx): + """30 damage; flip a coin, discard an Energy from the Defending Pokemon on heads.""" + await ctx.deal_damage() + heads = (await ctx.flip_coins(1, ctx.ability.title))[0] + if not heads: + return + target = ctx.opponent_active() + if target is not None and not ctx.effects_blocked(target): + await ctx.discard_energy_from( + target, 1, prompt="Choose Energy to discard from the Defending Pokémon") + + +async def chomp_down(ctx): + """100 damage; discard a random card from the opponent's hand.""" + await ctx.deal_damage() + await discard_random_from_hand(ctx, ctx.opponent_id, 1) card = PokemonCardDef( guid="600db496-b8e8-590f-835c-521edc922201", @@ -23,14 +42,14 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, discard an Energy from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.PSYCHIC: 1}, damage=30, - effect=unimplemented, + effect=pouty_slap, ), Attack( title="Chomp Down", game_text="Discard a random card from your opponent's hand.", cost={PokemonTypes.COLORLESS: 3}, damage=100, - effect=unimplemented, + effect=chomp_down, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Medicham_73.py b/spirit/game/scripts/cards/SWSH12/Medicham_73.py index 6241bc6..9b3bb92 100644 --- a/spirit/game/scripts/cards/SWSH12/Medicham_73.py +++ b/spirit/game/scripts/cards/SWSH12/Medicham_73.py @@ -1,5 +1,28 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.passives import Passive, carrier_pokemon +from spirit.game.card_effects.attacks_common import bonus_if +from spirit.game.card_effects.pokemon import is_pokemon_vmax + + +class ChakraAwakeningPassive(Passive): + def modify_attack_cost(self, cost, pokemon, carrier, board): + if carrier_pokemon(carrier) is not pokemon: + return cost + hand = board.find_player_area(carrier.owning_player_id, "hand") + if hand is None or len(hand.children) != 4: + return cost + remaining = cost.get("Colorless", 0) - 3 + if remaining > 0: + cost["Colorless"] = remaining + elif "Colorless" in cost: + del cost["Colorless"] + return cost + + +def _opponent_has_vmax(ctx) -> bool: + return any(is_pokemon_vmax(p.archetype_id) for p in ctx.opponent_pokemon_in_play()) + card = PokemonCardDef( guid="b321c397-5887-52e5-8f3a-0c5e6792afd0", @@ -23,7 +46,7 @@ card = PokemonCardDef( Ability( title="Chakra Awakening", game_text="If you have exactly 4 cards in your hand, this Pok\u00e9mon's attacks cost ColorlessColorlessColorless less.", - effect=unimplemented, + passive=ChakraAwakeningPassive(), ), Attack( title="Vigorous Kick", @@ -31,7 +54,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=80, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_opponent_has_vmax, 90), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Meditite_72.py b/spirit/game/scripts/cards/SWSH12/Meditite_72.py index b172fa3..627599a 100644 --- a/spirit/game/scripts/cards/SWSH12/Meditite_72.py +++ b/spirit/game/scripts/cards/SWSH12/Meditite_72.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import prevent_damage_when + + +def _yoga_guard_pred(calc, carrier): + return calc.is_attack and calc.target is carrier and not calc.to_active + card = PokemonCardDef( guid="ed8b924f-2399-5e48-b8b4-5357ab191590", @@ -22,7 +28,7 @@ card = PokemonCardDef( Ability( title="Yoga Guard", game_text="As long as this Pok\u00e9mon is on your Bench, prevent all damage done to this Pok\u00e9mon by attacks (both yours and your opponent's).", - effect=unimplemented, + passive=prevent_damage_when(_yoga_guard_pred, attacks_only=False), ), Attack( title="Psyshot", diff --git a/spirit/game/scripts/cards/SWSH12/Meowstic_82.py b/spirit/game/scripts/cards/SWSH12/Meowstic_82.py index 64c02dd..4ce65ff 100644 --- a/spirit/game/scripts/cards/SWSH12/Meowstic_82.py +++ b/spirit/game/scripts/cards/SWSH12/Meowstic_82.py @@ -1,5 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_supporter_card + + +async def inviting_ears(ctx): + """When played to evolve during your turn, you may search your deck for + up to 2 Supporter cards, reveal them, put them into your hand, shuffle.""" + if not await ctx.ask_yes_no("Search your deck for up to 2 Supporter cards?"): + return + picks = await ctx.search_deck( + is_supporter_card, count=2, minimum=0, + prompt="Choose up to 2 Supporter cards to put into your hand.", + ) + await ctx.put_in_hand(picks, reveal=True) + await ctx.shuffle_deck() + card = PokemonCardDef( guid="9987c931-7cb2-558d-9509-67055957e9a6", @@ -23,7 +38,8 @@ card = PokemonCardDef( Ability( title="Inviting Ears", game_text="When you play this Pok\u00e9mon from your hand to evolve 1 of your Pok\u00e9mon during your turn, you may search your deck for up to 2 Supporter cards, reveal them, and put them into your hand. Then, shuffle your deck.", - effect=unimplemented, + trigger=Triggers.ON_EVOLVE, + effect=inviting_ears, ), Attack( title="Super Psy Bolt", diff --git a/spirit/game/scripts/cards/SWSH12/Metagross_119.py b/spirit/game/scripts/cards/SWSH12/Metagross_119.py index 6236880..68cfb5c 100644 --- a/spirit/game/scripts/cards/SWSH12/Metagross_119.py +++ b/spirit/game/scripts/cards/SWSH12/Metagross_119.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented, Triggers +from spirit.game.card_effects.pokemon import top_entry from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import boost_own_next_turn card = PokemonCardDef( guid="4b4117c0-c560-5a3e-a6da-31db879bbac8", @@ -22,15 +24,16 @@ card = PokemonCardDef( abilities=[ Ability( title="Emergency Entry", + trigger=Triggers.ON_TURN_DRAWN, game_text="Once during your turn, if you drew this Pok\u00e9mon from your deck at the beginning of your turn and your Bench isn't full, before you put it into your hand, you may put it onto your Bench. If you do, draw 3 cards.", - effect=unimplemented, + effect=top_entry(3), ), Attack( title="Meteor Mash", game_text="During your next turn, this Pok\u00e9mon's Meteor Mash attack does 100 more damage (before applying Weakness and Resistance).", cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 1}, damage=100, - effect=unimplemented, + effect=boost_own_next_turn(100, attack_title="Meteor Mash"), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Metang_118.py b/spirit/game/scripts/cards/SWSH12/Metang_118.py index 2b3c00c..24e9e8d 100644 --- a/spirit/game/scripts/cards/SWSH12/Metang_118.py +++ b/spirit/game/scripts/cards/SWSH12/Metang_118.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="152d9ab9-6ad1-5975-a849-eecf8feea58d", @@ -26,7 +27,7 @@ card = PokemonCardDef( cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 1}, damage=30, damage_operator="+", - effect=unimplemented, + effect=flip_damage(coins=2, bonus_per_heads=30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Milotic_40.py b/spirit/game/scripts/cards/SWSH12/Milotic_40.py index fa8a5f0..1034230 100644 --- a/spirit/game/scripts/cards/SWSH12/Milotic_40.py +++ b/spirit/game/scripts/cards/SWSH12/Milotic_40.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack, snipe_attack card = PokemonCardDef( guid="c590e7f2-86a0-5334-9e64-ab6145646d21", @@ -23,14 +24,14 @@ card = PokemonCardDef( title="Water Arrow", game_text="This attack does 50 damage to 1 of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.WATER: 1}, - effect=unimplemented, + effect=snipe_attack(50, pool="any", count=1), ), Attack( title="Mellow Wave", game_text="Your opponent's Active Pok\u00e9mon is now Asleep.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 1}, damage=60, - effect=unimplemented, + effect=condition_attack(SpecialConditions.ASLEEP), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Misdreavus_63.py b/spirit/game/scripts/cards/SWSH12/Misdreavus_63.py index e92f4d0..7548022 100644 --- a/spirit/game/scripts/cards/SWSH12/Misdreavus_63.py +++ b/spirit/game/scripts/cards/SWSH12/Misdreavus_63.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="79cc9ca7-a8e5-5188-bd75-fc8374e9ec35", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Confused.", cost={PokemonTypes.PSYCHIC: 1}, damage=10, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED, flip=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Mismagius_64.py b/spirit/game/scripts/cards/SWSH12/Mismagius_64.py index 25b1449..6293872 100644 --- a/spirit/game/scripts/cards/SWSH12/Mismagius_64.py +++ b/spirit/game/scripts/cards/SWSH12/Mismagius_64.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.attacks_common import place_counters + + +async def spiteful_magic(ctx): + """If this Pokemon was at full HP and this attack knocks it out, put 8 + damage counters on the Attacking Pokemon.""" + if ctx.pre_hit_hp != ctx.max_hp(ctx.source): + return + if ctx.source.get_attribute(AttrID.HP, 0) > 0: + return + if ctx.damaged_by is not None: + await ctx.deal_damage(80, target=ctx.damaged_by, apply_modifiers=False, + as_counters=True) card = PokemonCardDef( guid="2f8c2153-b3e9-5196-9727-d6be25444dfe", @@ -23,13 +36,14 @@ card = PokemonCardDef( Ability( title="Spiteful Magic", game_text="If this Pok\u00e9mon has full HP and is Knocked Out by damage from an attack from your opponent's Pok\u00e9mon, put 8 damage counters on the Attacking Pok\u00e9mon.", - effect=unimplemented, + trigger=Triggers.ON_DAMAGED_BY_ATTACK, + effect=spiteful_magic, ), Attack( title="Eerie Voice", game_text="Put 2 damage counters on each of your opponent's Pok\u00e9mon.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=place_counters(2, "each_opponent"), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Morpeko_116.py b/spirit/game/scripts/cards/SWSH12/Morpeko_116.py index 5903b2c..ba3df77 100644 --- a/spirit/game/scripts/cards/SWSH12/Morpeko_116.py +++ b/spirit/game/scripts/cards/SWSH12/Morpeko_116.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, def_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_all_opponents + + +def _dedenne_dede_short_condition(board, player_id, pokemon): + for p in board.pokemon_in_play(player_id): + card_def = def_for(p.archetype_id) + if card_def and card_def.display_name == "Dedenne": + for used_id, _archetype, used_title in board.turn_state.attacks_used_last_turn: + if used_id == p.entity_id and used_title == "Dede-Short": + return True + return False + card = PokemonCardDef( guid="4dc26794-4408-51fb-85c0-79e1105be434", @@ -20,9 +32,10 @@ card = PokemonCardDef( abilities=[ Attack( title="Peko Blaster", - game_text="You can use this attack only if 1 of your Dedenne used Dede-Short during your last turn. This attack does 60 damage to each of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", + game_text="You can use this attack only if 1 of your Dedenne used Dede-Short during your last turn. This attack does 60 damage to each of your opponent's Pokémon. (Don't apply Weakness and Resistance for Benched Pokémon.)", cost={PokemonTypes.LIGHTNING: 1}, - effect=unimplemented, + condition=_dedenne_dede_short_condition, + effect=damage_all_opponents(60), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH12/Murkrow_106.py b/spirit/game/scripts/cards/SWSH12/Murkrow_106.py index ea8421a..538b8d5 100644 --- a/spirit/game/scripts/cards/SWSH12/Murkrow_106.py +++ b/spirit/game/scripts/cards/SWSH12/Murkrow_106.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, def_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_bench + + +def _is_murkrow(card) -> bool: + d = def_for(card.archetype_id) + return bool(d) and d.display_name == "Murkrow" + card = PokemonCardDef( guid="a1cc6396-8802-548d-9662-4288f7fb783f", @@ -23,7 +30,7 @@ card = PokemonCardDef( title="Flock", game_text="Search your deck for up to 2 Murkrow and put them onto your Bench. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_to_bench(predicate=_is_murkrow, count=2), ), Attack( title="Peck", diff --git a/spirit/game/scripts/cards/SWSH12/Ninetales_18.py b/spirit/game/scripts/cards/SWSH12/Ninetales_18.py index bab5bfa..e42cf1a 100644 --- a/spirit/game/scripts/cards/SWSH12/Ninetales_18.py +++ b/spirit/game/scripts/cards/SWSH12/Ninetales_18.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack, lock_all_attacks + + +async def scorching_breath(ctx): + """120. During your next turn, this Pokémon can't attack.""" + await ctx.deal_damage() + lock_all_attacks(ctx, ctx.attacker) + card = PokemonCardDef( guid="f26f951a-7bcc-5bf3-af54-5937e02f0b69", @@ -23,14 +31,14 @@ card = PokemonCardDef( title="Hypnotic Gaze", game_text="Your opponent's Active Pok\u00e9mon is now Asleep.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=condition_attack(SpecialConditions.ASLEEP), ), Attack( title="Scorching Breath", game_text="During your next turn, this Pok\u00e9mon can't attack.", cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + effect=scorching_breath, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Noivern_133.py b/spirit/game/scripts/cards/SWSH12/Noivern_133.py index 8a7be7c..817da7b 100644 --- a/spirit/game/scripts/cards/SWSH12/Noivern_133.py +++ b/spirit/game/scripts/cards/SWSH12/Noivern_133.py @@ -1,6 +1,24 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, subtypes_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def radiant_hunt(ctx): + targets = [p for p in ctx.opponent_pokemon_in_play() + if "Radiant" in subtypes_for(p.archetype_id)] + if not targets: + return + target = await ctx.choose_pokemon( + targets, "Choose 1 of your opponent's Radiant Pokémon to Knock Out." + ) + if target is not None: + await ctx.knock_out(target) + + +async def seventh_echo(ctx): + await ctx.deal_damage() + await ctx.draw_until(7) + + card = PokemonCardDef( guid="15764b2e-e508-5267-a4f9-8e201d92b1c3", key="SWSH12", @@ -22,14 +40,14 @@ card = PokemonCardDef( title="Radiant Hunt", game_text="Knock Out 1 of your opponent's Radiant Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + effect=radiant_hunt, ), Attack( title="Seventh Echo", game_text="Draw cards until you have 7 cards in your hand.", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.DARKNESS: 1}, damage=70, - effect=unimplemented, + effect=seventh_echo, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/OmastarV_174.py b/spirit/game/scripts/cards/SWSH12/OmastarV_174.py index 9a9efbc..bd2ad3e 100644 --- a/spirit/game/scripts/cards/SWSH12/OmastarV_174.py +++ b/spirit/game/scripts/cards/SWSH12/OmastarV_174.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, def_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import lock_defender_attacks +from spirit.game.card_effects.support_common import search_to_bench +from spirit.game.session.effects import is_evolution_pokemon + + +def _evolves_from_fossil(card) -> bool: + d = def_for(card.archetype_id) + return bool(d) and bool(getattr(d, "evolves_from", None)) and "Fossil" in d.evolves_from + + +async def tentacle_lock(ctx): + await ctx.deal_damage() + defender = ctx.defender + if defender is not None and is_evolution_pokemon(defender): + lock_defender_attacks(ctx) + card = PokemonCardDef( guid="09a33f5c-f76f-5f55-8475-5ae5fe5318c8", @@ -22,14 +38,14 @@ card = PokemonCardDef( title="Primal Guidance", game_text="Search your deck for up to 2 Pok\u00e9mon that evolve from an Item card that has \"Fossil\" in its name and put them onto your Bench. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_to_bench(predicate=_evolves_from_fossil, count=2), ), Attack( title="Tentacle Lock", game_text="If the Defending Pok\u00e9mon is an Evolution Pok\u00e9mon, it can't attack during your opponent's next turn.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=110, - effect=unimplemented, + effect=tentacle_lock, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/OmastarV_35.py b/spirit/game/scripts/cards/SWSH12/OmastarV_35.py index 17c12ea..2dfa3a6 100644 --- a/spirit/game/scripts/cards/SWSH12/OmastarV_35.py +++ b/spirit/game/scripts/cards/SWSH12/OmastarV_35.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, def_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import lock_defender_attacks +from spirit.game.card_effects.support_common import search_to_bench +from spirit.game.session.effects import is_evolution_pokemon + + +def _evolves_from_fossil(card) -> bool: + d = def_for(card.archetype_id) + return bool(d) and bool(getattr(d, "evolves_from", None)) and "Fossil" in d.evolves_from + + +async def tentacle_lock(ctx): + await ctx.deal_damage() + defender = ctx.defender + if defender is not None and is_evolution_pokemon(defender): + lock_defender_attacks(ctx) + card = PokemonCardDef( guid="951e64dd-a4a5-5e95-aca2-3b0e9d2247c3", @@ -22,14 +38,14 @@ card = PokemonCardDef( title="Primal Guidance", game_text="Search your deck for up to 2 Pok\u00e9mon that evolve from an Item card that has \"Fossil\" in its name and put them onto your Bench. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_to_bench(predicate=_evolves_from_fossil, count=2), ), Attack( title="Tentacle Lock", game_text="If the Defending Pok\u00e9mon is an Evolution Pok\u00e9mon, it can't attack during your opponent's next turn.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=110, - effect=unimplemented, + effect=tentacle_lock, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Palossand_100.py b/spirit/game/scripts/cards/SWSH12/Palossand_100.py index 003c055..9d10aa1 100644 --- a/spirit/game/scripts/cards/SWSH12/Palossand_100.py +++ b/spirit/game/scripts/cards/SWSH12/Palossand_100.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import damage_all_opponents +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -23,7 +24,7 @@ card = PokemonCardDef( title="Sandpot Trap", game_text="This attack does 30 damage to each of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.FIGHTING: 1}, - effect=unimplemented, + effect=damage_all_opponents(30), ), Attack( title="Land Crush", diff --git a/spirit/game/scripts/cards/SWSH12/Phanpy_91.py b/spirit/game/scripts/cards/SWSH12/Phanpy_91.py index f702f12..60e4376 100644 --- a/spirit/game/scripts/cards/SWSH12/Phanpy_91.py +++ b/spirit/game/scripts/cards/SWSH12/Phanpy_91.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus card = PokemonCardDef( guid="4d069ccc-e7a0-5b04-998c-6005b75cce9a", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1}, damage=10, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Phione_45.py b/spirit/game/scripts/cards/SWSH12/Phione_45.py index 7c40e4e..0ece2a8 100644 --- a/spirit/game/scripts/cards/SWSH12/Phione_45.py +++ b/spirit/game/scripts/cards/SWSH12/Phione_45.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_basic_pokemon, is_water_pokemon +from spirit.game.card_effects.support_common import search_to_bench + + +def _is_basic_water_pokemon(card): + return is_basic_pokemon(card) and is_water_pokemon(card) + card = PokemonCardDef( guid="ee7cc6fb-017d-5745-8653-bd3f2aa6464b", @@ -20,9 +27,9 @@ card = PokemonCardDef( abilities=[ Attack( title="Sea Feast", - game_text="Search your deck for up to 3 Basic Water Pok\u00e9mon and put them onto your Bench. Then, shuffle your deck.", + game_text="Search your deck for up to 3 Basic Water Pokémon and put them onto your Bench. Then, shuffle your deck.", cost={PokemonTypes.WATER: 1}, - effect=unimplemented, + effect=search_to_bench(predicate=_is_basic_water_pokemon, count=3), ), Attack( title="Wave Splash", @@ -30,4 +37,4 @@ card = PokemonCardDef( damage=20, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH12/Pikachu_49.py b/spirit/game/scripts/cards/SWSH12/Pikachu_49.py index 69b18a7..fae5e00 100644 --- a/spirit/game/scripts/cards/SWSH12/Pikachu_49.py +++ b/spirit/game/scripts/cards/SWSH12/Pikachu_49.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, def_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if + + +def _dedenne_dede_short_last_turn(ctx): + for pokemon in ctx.my_pokemon_in_play(): + card_def = def_for(pokemon.archetype_id) + if card_def and card_def.display_name == "Dedenne" and \ + ctx.attack_used_last_turn(title="Dede-Short", entity=pokemon): + return True + return False + card = PokemonCardDef( guid="caad81a2-cd85-5271-b05b-d83f3649e624", @@ -24,7 +35,7 @@ card = PokemonCardDef( cost={PokemonTypes.LIGHTNING: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_dedenne_dede_short_last_turn, 180), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Ponyta_21.py b/spirit/game/scripts/cards/SWSH12/Ponyta_21.py index b9c320f..79fee99 100644 --- a/spirit/game/scripts/cards/SWSH12/Ponyta_21.py +++ b/spirit/game/scripts/cards/SWSH12/Ponyta_21.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack card = PokemonCardDef( guid="0b9a25d9-9cc6-5154-a5f8-3f309e283a51", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="This Pok\u00e9mon also does 10 damage to itself.", cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 1}, damage=50, - effect=unimplemented, + effect=recoil_attack(10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/RadiantAlakazam_59.py b/spirit/game/scripts/cards/SWSH12/RadiantAlakazam_59.py index 7fed606..84bd572 100644 --- a/spirit/game/scripts/cards/SWSH12/RadiantAlakazam_59.py +++ b/spirit/game/scripts/cards/SWSH12/RadiantAlakazam_59.py @@ -1,5 +1,42 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.session.passives import effective_max_hp +from spirit.game.card_effects.attacks_common import damage_per, count_hand + + +def painful_spoons_condition(board, player_id, pokemon): + opponent = next((pid for pid in board.player_ids if pid != player_id), None) + if opponent is None: + return False + opp_pokemon = board.pokemon_in_play(opponent) + if len(opp_pokemon) < 2: + return False + return any(p.get_attribute(AttrID.HP, 0) < effective_max_hp(board, p) for p in opp_pokemon) + + +async def painful_spoons(ctx): + """Move up to 2 damage counters from 1 of your opponent's Pokemon to another.""" + damaged = [ + p for p in ctx.opponent_pokemon_in_play() + if p.get_attribute(AttrID.HP, 0) < ctx.max_hp(p) + ] + if not damaged: + return + source = await ctx.choose_pokemon( + damaged, "Choose 1 of your opponent's Pokémon to move damage counters from" + ) + if source is None: + return + targets = [p for p in ctx.opponent_pokemon_in_play() if p is not source] + if not targets: + return + dest = await ctx.choose_pokemon( + targets, "Choose a Pokémon to move the damage counters to" + ) + if dest is None: + return + await ctx.move_damage_counters(source, dest, max_count=2) + card = PokemonCardDef( guid="83366c0e-ddc2-5065-b76d-ddb9dea45513", @@ -22,7 +59,9 @@ card = PokemonCardDef( Ability( title="Painful Spoons", game_text="Once during your turn, you may move up to 2 damage counters from 1 of your opponent's Pok\u00e9mon to another of their Pok\u00e9mon.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=painful_spoons_condition, + effect=painful_spoons, ), Attack( title="Mind Ruler", @@ -30,7 +69,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=20, damage_operator="x", - effect=unimplemented, + effect=damage_per(count_hand("opponent"), 20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/RadiantJirachi_120.py b/spirit/game/scripts/cards/SWSH12/RadiantJirachi_120.py index ef3e1a1..cb7cc3c 100644 --- a/spirit/game/scripts/cards/SWSH12/RadiantJirachi_120.py +++ b/spirit/game/scripts/cards/SWSH12/RadiantJirachi_120.py @@ -1,6 +1,27 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def entrusted_wishes(ctx): + """If Active and KO'd by damage from an opponent's attack: search the + deck for up to 3 cards into hand, then shuffle.""" + if not ctx.ko_from_attack or not ctx.was_active_at_ko: + return + picks = await ctx.search_deck( + None, count=3, minimum=0, + prompt="Choose up to 3 cards to put into your hand.", + ) + await ctx.put_in_hand(picks, reveal=False) + await ctx.shuffle_deck() + + +async def astral_misfortune(ctx): + """Flip 2 coins. If both of them are heads, the opponent's Active is KO'd.""" + heads = await ctx.flip_coins(2, ctx.ability.title) + if all(heads): + await ctx.knock_out(ctx.defender) + + card = PokemonCardDef( guid="35f15b60-54b5-59f0-941c-64a13c2093c2", key="SWSH12", @@ -22,13 +43,14 @@ card = PokemonCardDef( Ability( title="Entrusted Wishes", game_text="If this Pok\u00e9mon is in the Active Spot and is Knocked Out by damage from an attack from your opponent's Pok\u00e9mon, search your deck for up to 3 cards and put them into your hand. Then, shuffle your deck.", - effect=unimplemented, + trigger=Triggers.ON_KNOCKED_OUT, + effect=entrusted_wishes, ), Attack( title="Astral Misfortune", game_text="Flip 2 coins. If both of them are heads, your opponent's Active Pok\u00e9mon is Knocked Out.", cost={PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + effect=astral_misfortune, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/RadiantTsareena_16.py b/spirit/game/scripts/cards/SWSH12/RadiantTsareena_16.py index d7377ff..684e3f1 100644 --- a/spirit/game/scripts/cards/SWSH12/RadiantTsareena_16.py +++ b/spirit/game/scripts/cards/SWSH12/RadiantTsareena_16.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import ( + heal_targets, requires_damaged_pokemon, cure_conditions_effect, +) + + +async def elegant_heal(ctx): + if await ctx.ask_yes_no("Heal 20 damage from each of your Pokémon?"): + await heal_targets(20, "each_own")(ctx) + card = PokemonCardDef( guid="6ce58ea2-6f3f-5f2d-abc9-326bb72fefa7", @@ -21,14 +30,16 @@ card = PokemonCardDef( Ability( title="Elegant Heal", game_text="Once during your turn, you may heal 20 damage from each of your Pok\u00e9mon.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=requires_damaged_pokemon(), + effect=elegant_heal, ), Attack( title="Aroma Shot", game_text="This Pok\u00e9mon recovers from all Special Conditions.", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 2}, damage=90, - effect=unimplemented, + effect=cure_conditions_effect("active"), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Raichu_50.py b/spirit/game/scripts/cards/SWSH12/Raichu_50.py index 6efb713..235a3a0 100644 --- a/spirit/game/scripts/cards/SWSH12/Raichu_50.py +++ b/spirit/game/scripts/cards/SWSH12/Raichu_50.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if card = PokemonCardDef( guid="55160fb3-981b-5e97-ad5d-f98e7ea11804", @@ -25,7 +26,10 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 1}, damage=40, damage_operator="+", - effect=unimplemented, + effect=bonus_if( + lambda ctx: ctx.opponent_id in ctx.session.turn_state.vstar_used, + 100, + ), ), Attack( title="Electric Ball", diff --git a/spirit/game/scripts/cards/SWSH12/Ralts_67.py b/spirit/game/scripts/cards/SWSH12/Ralts_67.py index ac0cea0..8656284 100644 --- a/spirit/game/scripts/cards/SWSH12/Ralts_67.py +++ b/spirit/game/scripts/cards/SWSH12/Ralts_67.py @@ -1,5 +1,25 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities + + +async def memory_skip(ctx): + """Choose 1 of the opponent's Active Pokemon's attacks; it can't be used + during their next turn.""" + await ctx.deal_damage() + defender = ctx.defender + if defender is None or ctx.effects_blocked(defender): + return + entries = [e for e in (defender.get_attribute(AttrID.PIE_ABILITIES) or []) + if isinstance(e, dict) and e.get("abilityType") == "Attack" + and e.get("abilityID")] + if not entries: + return + titles = [e.get("title", {}).get("id", "Attack") for e in entries] + idx = await ctx.choose("Choose an attack to lock", titles) + entry = entries[idx] + ctx.session.turn_state.attack_locks[(defender.entity_id, entry["abilityID"])] = \ + ctx.session.turn_state.turn_number + 1 + card = PokemonCardDef( guid="a573590e-327b-5e02-a35d-623c2e59cbe4", @@ -23,7 +43,7 @@ card = PokemonCardDef( game_text="Choose 1 of your opponent's Active Pok\u00e9mon's attacks. During your opponent's next turn, that Pok\u00e9mon can't use that attack.", cost={PokemonTypes.PSYCHIC: 1}, damage=10, - effect=unimplemented, + effect=memory_skip, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Rapidash_22.py b/spirit/game/scripts/cards/SWSH12/Rapidash_22.py index cc54b87..95abc56 100644 --- a/spirit/game/scripts/cards/SWSH12/Rapidash_22.py +++ b/spirit/game/scripts/cards/SWSH12/Rapidash_22.py @@ -1,5 +1,29 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.support_common import requires_hand +from spirit.game.card_effects.pokemon import is_energy_card, energy_provides_type +from spirit.game.session.passives import TurnDamageModifier + + +def is_fire_energy_card(card) -> bool: + return is_energy_card(card) and energy_provides_type(card, PokemonTypes.FIRE.value) + + +async def heat_boost(ctx): + """Discard a Fire Energy from hand: your Fire Pokémon's attacks do +30 + to the opponent's Active this turn (before applying W/R).""" + discarded = await ctx.discard_from_hand( + 1, predicate=is_fire_energy_card, + prompt="Discard a Fire Energy card for Heat Boost", + ) + if not discarded: + return + ctx.add_turn_damage_modifier(TurnDamageModifier( + 30, ctx.player_id, + source_predicate=lambda p: PokemonTypes.FIRE.value in ( + p.get_attribute(AttrID.POKEMON_TYPES) or []), + )) + card = PokemonCardDef( guid="47de76c5-1287-5f18-8b29-4641ab1a0801", @@ -22,7 +46,9 @@ card = PokemonCardDef( Ability( title="Heat Boost", game_text="Once during your turn, you may discard a Fire Energy card from your hand in order to use this Ability. During this turn, your Fire Pok\u00e9mon's attacks do 30 more damage to your opponent's Active Pok\u00e9mon (before applying Weakness and Resistance).", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=requires_hand(is_fire_energy_card), + effect=heat_boost, ), Attack( title="Fire Mane", diff --git a/spirit/game/scripts/cards/SWSH12/RegidragoVSTAR_136.py b/spirit/game/scripts/cards/SWSH12/RegidragoVSTAR_136.py index 1a3260f..2223a65 100644 --- a/spirit/game/scripts/cards/SWSH12/RegidragoVSTAR_136.py +++ b/spirit/game/scripts/cards/SWSH12/RegidragoVSTAR_136.py @@ -1,5 +1,57 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations, def_for +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.session.effects import is_pokemon_card + + +def _dragon_discard_attacks(ctx): + pairs = [] + seen = set() + for card in ctx.discard_pile(): + if not is_pokemon_card(card): + continue + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + if PokemonTypes.DRAGON.value not in types: + continue + definition = def_for(card.archetype_id) + if definition is None: + continue + for ability in getattr(definition, "abilities", []): + if isinstance(ability, Attack): + key = (ability.title, ability.game_text) + if key in seen: + continue + seen.add(key) + pairs.append((card, ability)) + return pairs + + +async def apex_dragon(ctx): + """Choose an attack from a Dragon Pokemon in your discard pile and use + it as this attack.""" + candidates = _dragon_discard_attacks(ctx) + if not candidates: + return + picked = await ctx.choose_attack_to_copy(candidates, "Choose an attack to copy") + if picked is None: + return + _, chosen = picked + await ctx.use_attack(chosen) + + +async def legacy_star(ctx): + """VSTAR Power: you may discard the top 7 of your deck, then put up to + 2 cards from your discard pile into your hand.""" + if await ctx.ask_yes_no("Discard the top 7 cards of your deck?"): + top = ctx.deck_top(7) + if top: + await ctx.discard_cards(top) + picks = await ctx.choose_cards( + ctx.discard_pile(), 2, minimum=0, + prompt="Put up to 2 cards from your discard pile into your hand.", + ) + if picks: + await ctx.put_in_hand(picks, reveal=False) + card = PokemonCardDef( guid="4e44085a-eaf9-5dd5-bd3e-8e74c068f349", @@ -21,13 +73,15 @@ card = PokemonCardDef( Ability( title="Legacy Star", game_text="During your turn, you may discard the top 7 cards of your deck. Then, put up to 2 cards from your discard pile into your hand. (You can't use more than 1 VSTAR Power in a game.)", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + vstar=True, + effect=legacy_star, ), Attack( title="Apex Dragon", game_text="Choose an attack from a Dragon Pok\u00e9mon in your discard pile and use it as this attack.", cost={PokemonTypes.GRASS: 2, PokemonTypes.FIRE: 1}, - effect=unimplemented, + effect=apex_dragon, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/RegidragoVSTAR_201.py b/spirit/game/scripts/cards/SWSH12/RegidragoVSTAR_201.py index 02f90ae..8930599 100644 --- a/spirit/game/scripts/cards/SWSH12/RegidragoVSTAR_201.py +++ b/spirit/game/scripts/cards/SWSH12/RegidragoVSTAR_201.py @@ -1,5 +1,57 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations, def_for +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.session.effects import is_pokemon_card + + +def _dragon_discard_attacks(ctx): + pairs = [] + seen = set() + for card in ctx.discard_pile(): + if not is_pokemon_card(card): + continue + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + if PokemonTypes.DRAGON.value not in types: + continue + definition = def_for(card.archetype_id) + if definition is None: + continue + for ability in getattr(definition, "abilities", []): + if isinstance(ability, Attack): + key = (ability.title, ability.game_text) + if key in seen: + continue + seen.add(key) + pairs.append((card, ability)) + return pairs + + +async def apex_dragon(ctx): + """Choose an attack from a Dragon Pokemon in your discard pile and use + it as this attack.""" + candidates = _dragon_discard_attacks(ctx) + if not candidates: + return + picked = await ctx.choose_attack_to_copy(candidates, "Choose an attack to copy") + if picked is None: + return + _, chosen = picked + await ctx.use_attack(chosen) + + +async def legacy_star(ctx): + """VSTAR Power: you may discard the top 7 of your deck, then put up to + 2 cards from your discard pile into your hand.""" + if await ctx.ask_yes_no("Discard the top 7 cards of your deck?"): + top = ctx.deck_top(7) + if top: + await ctx.discard_cards(top) + picks = await ctx.choose_cards( + ctx.discard_pile(), 2, minimum=0, + prompt="Put up to 2 cards from your discard pile into your hand.", + ) + if picks: + await ctx.put_in_hand(picks, reveal=False) + card = PokemonCardDef( guid="dc29d870-7d97-58cb-9baf-6185bd8733af", @@ -21,13 +73,15 @@ card = PokemonCardDef( Ability( title="Legacy Star", game_text="During your turn, you may discard the top 7 cards of your deck. Then, put up to 2 cards from your discard pile into your hand. (You can't use more than 1 VSTAR Power in a game.)", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + vstar=True, + effect=legacy_star, ), Attack( title="Apex Dragon", game_text="Choose an attack from a Dragon Pok\u00e9mon in your discard pile and use it as this attack.", cost={PokemonTypes.GRASS: 2, PokemonTypes.FIRE: 1}, - effect=unimplemented, + effect=apex_dragon, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/RegidragoV_135.py b/spirit/game/scripts/cards/SWSH12/RegidragoV_135.py index dc9318c..bac974e 100644 --- a/spirit/game/scripts/cards/SWSH12/RegidragoV_135.py +++ b/spirit/game/scripts/cards/SWSH12/RegidragoV_135.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import snipe_attack +from spirit.game.card_effects.pokemon import is_energy_card + + +async def celestial_roar(ctx): + """Discard the top 3 cards of your deck; attach any Energy among them.""" + top = ctx.deck_top(3) + await ctx.discard_cards(top) + for card in top: + if is_energy_card(card): + await ctx.attach_energy(card, ctx.source) + card = PokemonCardDef( guid="d4bfb8e5-7f25-52ce-ac5b-eca9f4fc14c1", @@ -21,14 +33,14 @@ card = PokemonCardDef( title="Celestial Roar", game_text="Discard the top 3 cards of your deck. If any of those cards are Energy cards, attach them to this Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=celestial_roar, ), Attack( title="Dragon Laser", game_text="This attack also does 30 damage to 1 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.GRASS: 2, PokemonTypes.FIRE: 1}, damage=130, - effect=unimplemented, + effect=snipe_attack(30, also_base=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/RegidragoV_183.py b/spirit/game/scripts/cards/SWSH12/RegidragoV_183.py index 3366963..fac6cdf 100644 --- a/spirit/game/scripts/cards/SWSH12/RegidragoV_183.py +++ b/spirit/game/scripts/cards/SWSH12/RegidragoV_183.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import snipe_attack +from spirit.game.card_effects.pokemon import is_energy_card + + +async def celestial_roar(ctx): + """Discard the top 3 cards of your deck; attach any Energy among them.""" + top = ctx.deck_top(3) + await ctx.discard_cards(top) + for card in top: + if is_energy_card(card): + await ctx.attach_energy(card, ctx.source) + card = PokemonCardDef( guid="7a47f11e-9717-5996-8a0f-bf8ffe530489", @@ -21,14 +33,14 @@ card = PokemonCardDef( title="Celestial Roar", game_text="Discard the top 3 cards of your deck. If any of those cards are Energy cards, attach them to this Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=celestial_roar, ), Attack( title="Dragon Laser", game_text="This attack also does 30 damage to 1 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.GRASS: 2, PokemonTypes.FIRE: 1}, damage=130, - effect=unimplemented, + effect=snipe_attack(30, also_base=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/RegidragoV_184.py b/spirit/game/scripts/cards/SWSH12/RegidragoV_184.py index 04d5c7b..91fc4d7 100644 --- a/spirit/game/scripts/cards/SWSH12/RegidragoV_184.py +++ b/spirit/game/scripts/cards/SWSH12/RegidragoV_184.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import snipe_attack +from spirit.game.card_effects.pokemon import is_energy_card + + +async def celestial_roar(ctx): + """Discard the top 3 cards of your deck; attach any Energy among them.""" + top = ctx.deck_top(3) + await ctx.discard_cards(top) + for card in top: + if is_energy_card(card): + await ctx.attach_energy(card, ctx.source) + card = PokemonCardDef( guid="838c5f94-c1cb-5151-bcac-84e437e151c0", @@ -21,14 +33,14 @@ card = PokemonCardDef( title="Celestial Roar", game_text="Discard the top 3 cards of your deck. If any of those cards are Energy cards, attach them to this Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=celestial_roar, ), Attack( title="Dragon Laser", game_text="This attack also does 30 damage to 1 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.GRASS: 2, PokemonTypes.FIRE: 1}, damage=130, - effect=unimplemented, + effect=snipe_attack(30, also_base=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/RegielekiVMAX_198.py b/spirit/game/scripts/cards/SWSH12/RegielekiVMAX_198.py index a7acbfc..141f45f 100644 --- a/spirit/game/scripts/cards/SWSH12/RegielekiVMAX_198.py +++ b/spirit/game/scripts/cards/SWSH12/RegielekiVMAX_198.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.session.effects import is_basic_pokemon +from spirit.game.card_effects.passives_common import team_damage_boost_passive +from spirit.game.card_effects.attacks_common import lock_all_attacks + + +async def max_thunder_and_lightning(ctx): + """220. During your next turn, this Pokémon can't attack.""" + await ctx.deal_damage() + lock_all_attacks(ctx, ctx.attacker) + card = PokemonCardDef( guid="34ebe757-910b-56f2-bd70-acfcd01ddd68", @@ -22,14 +32,18 @@ card = PokemonCardDef( Ability( title="Transistor", game_text="Your Basic Lightning Pok\u00e9mon's attacks do 30 more damage to your opponent's Active Pok\u00e9mon (before applying Weakness and Resistance).", - effect=unimplemented, + passive=team_damage_boost_passive( + 30, + attacker_pred=lambda p: is_basic_pokemon(p) and PokemonTypes.LIGHTNING.value in ( + p.get_attribute(AttrID.POKEMON_TYPES) or []), + ), ), Attack( title="Max Thunder and Lightning", game_text="During your next turn, this Pok\u00e9mon can't attack.", cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 2}, damage=220, - effect=unimplemented, + effect=max_thunder_and_lightning, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/RegielekiVMAX_58.py b/spirit/game/scripts/cards/SWSH12/RegielekiVMAX_58.py index 3ecdb15..14107ef 100644 --- a/spirit/game/scripts/cards/SWSH12/RegielekiVMAX_58.py +++ b/spirit/game/scripts/cards/SWSH12/RegielekiVMAX_58.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.session.effects import is_basic_pokemon +from spirit.game.card_effects.passives_common import team_damage_boost_passive +from spirit.game.card_effects.attacks_common import lock_all_attacks + + +async def max_thunder_and_lightning(ctx): + """220. During your next turn, this Pokémon can't attack.""" + await ctx.deal_damage() + lock_all_attacks(ctx, ctx.attacker) + card = PokemonCardDef( guid="34a85bb1-4c85-559b-aca7-a7ab5fa318fb", @@ -22,14 +32,18 @@ card = PokemonCardDef( Ability( title="Transistor", game_text="Your Basic Lightning Pok\u00e9mon's attacks do 30 more damage to your opponent's Active Pok\u00e9mon (before applying Weakness and Resistance).", - effect=unimplemented, + passive=team_damage_boost_passive( + 30, + attacker_pred=lambda p: is_basic_pokemon(p) and PokemonTypes.LIGHTNING.value in ( + p.get_attribute(AttrID.POKEMON_TYPES) or []), + ), ), Attack( title="Max Thunder and Lightning", game_text="During your next turn, this Pok\u00e9mon can't attack.", cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 2}, damage=220, - effect=unimplemented, + effect=max_thunder_and_lightning, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/RegielekiV_175.py b/spirit/game/scripts/cards/SWSH12/RegielekiV_175.py index 02df763..d214590 100644 --- a/spirit/game/scripts/cards/SWSH12/RegielekiV_175.py +++ b/spirit/game/scripts/cards/SWSH12/RegielekiV_175.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import switch_self_attack +from spirit.game.card_effects.passives_common import protect_next_turn card = PokemonCardDef( guid="dbee864e-2a1b-5afd-9eae-2840b98ef2a3", @@ -23,14 +25,14 @@ card = PokemonCardDef( game_text="Switch this Pok\u00e9mon with 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.LIGHTNING: 1}, damage=30, - effect=unimplemented, + effect=switch_self_attack(), ), Attack( title="Lightning Wall", game_text="During your opponent's next turn, this Pok\u00e9mon takes 100 less damage from attacks (after applying Weakness and Resistance).", cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 2}, damage=100, - effect=unimplemented, + effect=protect_next_turn(reduce=100), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/RegielekiV_57.py b/spirit/game/scripts/cards/SWSH12/RegielekiV_57.py index e2e9914..999080c 100644 --- a/spirit/game/scripts/cards/SWSH12/RegielekiV_57.py +++ b/spirit/game/scripts/cards/SWSH12/RegielekiV_57.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import switch_self_attack +from spirit.game.card_effects.passives_common import protect_next_turn card = PokemonCardDef( guid="ec75e675-4b6b-561b-a798-22c8d4f04c13", @@ -23,14 +25,14 @@ card = PokemonCardDef( game_text="Switch this Pok\u00e9mon with 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.LIGHTNING: 1}, damage=30, - effect=unimplemented, + effect=switch_self_attack(), ), Attack( title="Lightning Wall", game_text="During your opponent's next turn, this Pok\u00e9mon takes 100 less damage from attacks (after applying Weakness and Resistance).", cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 2}, damage=100, - effect=unimplemented, + effect=protect_next_turn(reduce=100), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Relicanth_44.py b/spirit/game/scripts/cards/SWSH12/Relicanth_44.py index 5798da2..c6c5843 100644 --- a/spirit/game/scripts/cards/SWSH12/Relicanth_44.py +++ b/spirit/game/scripts/cards/SWSH12/Relicanth_44.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, def_for +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.support_common import recover_from_discard + + +def _is_fossil_card(card): + d = def_for(card.archetype_id) + return d is not None and d.display_name in ("Unidentified Fossil", "Rare Fossil") + card = PokemonCardDef( guid="ac836f30-07e7-5379-9056-e2a6567ed92f", @@ -22,14 +30,17 @@ card = PokemonCardDef( title="Fossil Finding", game_text="Shuffle up to 4 in any combination of Unidentified Fossil and Rare Fossil cards from your discard pile into your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=recover_from_discard( + predicate=_is_fossil_card, count=4, minimum=0, to="deck_shuffle", + prompt="Shuffle up to 4 Fossil cards into your deck.", + ), ), Attack( title="Water Pulse", game_text="Your opponent's Active Pok\u00e9mon is now Asleep.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=condition_attack(SpecialConditions.ASLEEP), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/ReshiramV_172.py b/spirit/game/scripts/cards/SWSH12/ReshiramV_172.py index 89363b5..7830a22 100644 --- a/spirit/game/scripts/cards/SWSH12/ReshiramV_172.py +++ b/spirit/game/scripts/cards/SWSH12/ReshiramV_172.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_attach_energy +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.card_effects.attacks_common import lock_all_attacks + + +async def white_blaze(ctx): + """Flip a coin. If tails, during your next turn, this Pokémon can't attack.""" + await ctx.deal_damage() + heads = (await ctx.flip_coins(1, ctx.ability.title))[0] + if not heads: + lock_all_attacks(ctx, ctx.attacker) + card = PokemonCardDef( guid="e58f4ccb-e15c-5eb5-a0fa-3be90a37ce39", @@ -22,14 +34,14 @@ card = PokemonCardDef( title="Sparkling Wing", game_text="Search your deck for up to 2 basic Energy cards and attach them to 1 of your Pok\u00e9mon. Then, shuffle your deck.", cost={PokemonTypes.FIRE: 1}, - effect=unimplemented, + effect=search_attach_energy(is_basic_energy_card, count=2, distribute=False), ), Attack( title="White Blaze", game_text="Flip a coin. If tails, during your next turn, this Pok\u00e9mon can't attack.", cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=200, - effect=unimplemented, + effect=white_blaze, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/ReshiramV_24.py b/spirit/game/scripts/cards/SWSH12/ReshiramV_24.py index f410220..e8babdf 100644 --- a/spirit/game/scripts/cards/SWSH12/ReshiramV_24.py +++ b/spirit/game/scripts/cards/SWSH12/ReshiramV_24.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_attach_energy +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.card_effects.attacks_common import lock_all_attacks + + +async def white_blaze(ctx): + """Flip a coin. If tails, during your next turn, this Pokémon can't attack.""" + await ctx.deal_damage() + heads = (await ctx.flip_coins(1, ctx.ability.title))[0] + if not heads: + lock_all_attacks(ctx, ctx.attacker) + card = PokemonCardDef( guid="e7d6acfc-61c0-50e0-b7f6-8a9438848df6", @@ -22,14 +34,14 @@ card = PokemonCardDef( title="Sparkling Wing", game_text="Search your deck for up to 2 basic Energy cards and attach them to 1 of your Pok\u00e9mon. Then, shuffle your deck.", cost={PokemonTypes.FIRE: 1}, - effect=unimplemented, + effect=search_attach_energy(is_basic_energy_card, count=2, distribute=False), ), Attack( title="White Blaze", game_text="Flip a coin. If tails, during your next turn, this Pok\u00e9mon can't attack.", cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=200, - effect=unimplemented, + effect=white_blaze, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Reuniclus_78.py b/spirit/game/scripts/cards/SWSH12/Reuniclus_78.py index 5492826..9f6625c 100644 --- a/spirit/game/scripts/cards/SWSH12/Reuniclus_78.py +++ b/spirit/game/scripts/cards/SWSH12/Reuniclus_78.py @@ -1,6 +1,30 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def persistent_cells(ctx): + """Knocked out by an opponent's attack: return to hand instead of the + discard pile (attached cards are discarded normally).""" + if not ctx.ko_from_attack: + return + await ctx.put_in_hand([ctx.source], reveal=False) + + +async def cell_fork(ctx): + """60, then choose 2 of the opponent's Benched Pokemon and put 3 damage + counters on each of them.""" + await ctx.deal_damage() + bench = ctx.opponent_bench() + if not bench: + return + count = min(2, len(bench)) + targets = await ctx.choose_cards( + bench, count, prompt="Choose 2 of your opponent's Benched Pokémon", + ) + for target in targets: + await ctx.deal_damage(30, target=target, apply_modifiers=False, as_counters=True) + + card = PokemonCardDef( guid="79ea0736-1f12-54b1-b4c1-0c82144860b8", key="SWSH12", @@ -23,14 +47,15 @@ card = PokemonCardDef( Ability( title="Persistent Cells", game_text="If this Pok\u00e9mon is Knocked Out by damage from an attack from your opponent's Pok\u00e9mon, put it into your hand instead of the discard pile. (Discard all attached cards.)", - effect=unimplemented, + trigger=Triggers.ON_KNOCKED_OUT, + effect=persistent_cells, ), Attack( title="Cell Fork", game_text="Choose 2 of your opponent's Benched Pok\u00e9mon and put 3 damage counters on each of them.", cost={PokemonTypes.COLORLESS: 2}, damage=60, - effect=unimplemented, + effect=cell_fork, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Rotom_53.py b/spirit/game/scripts/cards/SWSH12/Rotom_53.py index c6e4e45..0e8d031 100644 --- a/spirit/game/scripts/cards/SWSH12/Rotom_53.py +++ b/spirit/game/scripts/cards/SWSH12/Rotom_53.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import shuffle_hand_into_deck_draw card = PokemonCardDef( guid="54a4460f-6456-545f-abed-df308eadc75e", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Overhaul", game_text="Shuffle your hand into your deck. Then, draw 6 cards.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=shuffle_hand_into_deck_draw(6), ), Attack( title="Mach Bolt", diff --git a/spirit/game/scripts/cards/SWSH12/Rufflet_148.py b/spirit/game/scripts/cards/SWSH12/Rufflet_148.py index c1dcfe7..9f7be3f 100644 --- a/spirit/game/scripts/cards/SWSH12/Rufflet_148.py +++ b/spirit/game/scripts/cards/SWSH12/Rufflet_148.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus card = PokemonCardDef( guid="7a234e8a-d5cb-57a1-93d4-32077a4c8765", @@ -25,7 +26,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=10, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Sandygast_99.py b/spirit/game/scripts/cards/SWSH12/Sandygast_99.py index c6bdf85..8822ce3 100644 --- a/spirit/game/scripts/cards/SWSH12/Sandygast_99.py +++ b/spirit/game/scripts/cards/SWSH12/Sandygast_99.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="8a067a01-2707-5a82-856e-6f70a811dfa4", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, the Defending Pok\u00e9mon can't retreat.", cost={PokemonTypes.FIGHTING: 1}, damage=10, - effect=unimplemented, + effect=condition_attack(no_retreat=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/SerperiorVSTAR_196.py b/spirit/game/scripts/cards/SWSH12/SerperiorVSTAR_196.py index d706df5..e603dd3 100644 --- a/spirit/game/scripts/cards/SWSH12/SerperiorVSTAR_196.py +++ b/spirit/game/scripts/cards/SWSH12/SerperiorVSTAR_196.py @@ -1,5 +1,30 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import count_energy + + +async def regal_blender(ctx): + """Printed damage, then you may move any amount of Energy between your + Pokémon in any way you like.""" + await ctx.deal_damage() + mine = ctx.my_pokemon_in_play() + if len(mine) > 1 and await ctx.ask_yes_no( + "Move any amount of Energy from your Pokémon to your other Pokémon?" + ): + await ctx.move_energy_freely(mine, mine) + + +async def star_winder(ctx): + """60 damage for each Energy attached to this Pokémon, then switch this + Pokémon with 1 of your Benched Pokémon.""" + await ctx.deal_damage(60 * count_energy("self")(ctx)) + bench = ctx.my_bench() + if bench: + target = await ctx.choose_pokemon( + bench, "Choose your new Active Pokémon" + ) or bench[0] + await ctx.switch_active(ctx.player_id, target) + card = PokemonCardDef( guid="2001a560-502f-5dad-a7e9-448fac25ad49", @@ -24,7 +49,7 @@ card = PokemonCardDef( game_text="You may move any amount of Energy from your Pok\u00e9mon to your other Pok\u00e9mon in any way you like.", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 2}, damage=190, - effect=unimplemented, + effect=regal_blender, ), Attack( title="Star Winder", @@ -32,7 +57,8 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1}, damage=60, damage_operator="x", - effect=unimplemented, + vstar=True, + effect=star_winder, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/SerperiorVSTAR_210.py b/spirit/game/scripts/cards/SWSH12/SerperiorVSTAR_210.py index 26165a9..8bf450c 100644 --- a/spirit/game/scripts/cards/SWSH12/SerperiorVSTAR_210.py +++ b/spirit/game/scripts/cards/SWSH12/SerperiorVSTAR_210.py @@ -1,5 +1,30 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import count_energy + + +async def regal_blender(ctx): + """Printed damage, then you may move any amount of Energy between your + Pokémon in any way you like.""" + await ctx.deal_damage() + mine = ctx.my_pokemon_in_play() + if len(mine) > 1 and await ctx.ask_yes_no( + "Move any amount of Energy from your Pokémon to your other Pokémon?" + ): + await ctx.move_energy_freely(mine, mine) + + +async def star_winder(ctx): + """60 damage for each Energy attached to this Pokémon, then switch this + Pokémon with 1 of your Benched Pokémon.""" + await ctx.deal_damage(60 * count_energy("self")(ctx)) + bench = ctx.my_bench() + if bench: + target = await ctx.choose_pokemon( + bench, "Choose your new Active Pokémon" + ) or bench[0] + await ctx.switch_active(ctx.player_id, target) + card = PokemonCardDef( guid="35cf95dc-b58c-55e6-9f31-38fa42473fc2", @@ -24,7 +49,7 @@ card = PokemonCardDef( game_text="You may move any amount of Energy from your Pok\u00e9mon to your other Pok\u00e9mon in any way you like.", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 2}, damage=190, - effect=unimplemented, + effect=regal_blender, ), Attack( title="Star Winder", @@ -32,7 +57,8 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1}, damage=60, damage_operator="x", - effect=unimplemented, + vstar=True, + effect=star_winder, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/SerperiorVSTAR_8.py b/spirit/game/scripts/cards/SWSH12/SerperiorVSTAR_8.py index ce72de7..39f5431 100644 --- a/spirit/game/scripts/cards/SWSH12/SerperiorVSTAR_8.py +++ b/spirit/game/scripts/cards/SWSH12/SerperiorVSTAR_8.py @@ -1,5 +1,30 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import count_energy + + +async def regal_blender(ctx): + """Printed damage, then you may move any amount of Energy between your + Pokémon in any way you like.""" + await ctx.deal_damage() + mine = ctx.my_pokemon_in_play() + if len(mine) > 1 and await ctx.ask_yes_no( + "Move any amount of Energy from your Pokémon to your other Pokémon?" + ): + await ctx.move_energy_freely(mine, mine) + + +async def star_winder(ctx): + """60 damage for each Energy attached to this Pokémon, then switch this + Pokémon with 1 of your Benched Pokémon.""" + await ctx.deal_damage(60 * count_energy("self")(ctx)) + bench = ctx.my_bench() + if bench: + target = await ctx.choose_pokemon( + bench, "Choose your new Active Pokémon" + ) or bench[0] + await ctx.switch_active(ctx.player_id, target) + card = PokemonCardDef( guid="c248dd62-7d8e-5a80-90ea-6315ac52d071", @@ -24,7 +49,7 @@ card = PokemonCardDef( game_text="You may move any amount of Energy from your Pok\u00e9mon to your other Pok\u00e9mon in any way you like.", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 2}, damage=190, - effect=unimplemented, + effect=regal_blender, ), Attack( title="Star Winder", @@ -32,7 +57,8 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1}, damage=60, damage_operator="x", - effect=unimplemented, + vstar=True, + effect=star_winder, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/SerperiorV_170.py b/spirit/game/scripts/cards/SWSH12/SerperiorV_170.py index f40ad07..8c4fa93 100644 --- a/spirit/game/scripts/cards/SWSH12/SerperiorV_170.py +++ b/spirit/game/scripts/cards/SWSH12/SerperiorV_170.py @@ -1,6 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def noble_light(ctx): + for pokemon in ctx.my_pokemon_in_play() + ctx.opponent_pokemon_in_play(): + await ctx.heal(30, pokemon) + + card = PokemonCardDef( guid="bd327d1b-180d-503a-a96b-6473ebcabf0c", key="SWSH12", @@ -22,7 +28,7 @@ card = PokemonCardDef( title="Noble Light", game_text="Heal 30 damage from each Pok\u00e9mon (both yours and your opponent's).", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=noble_light, ), Attack( title="Solar Beam", diff --git a/spirit/game/scripts/cards/SWSH12/SerperiorV_7.py b/spirit/game/scripts/cards/SWSH12/SerperiorV_7.py index d01d9f7..ffe2e3e 100644 --- a/spirit/game/scripts/cards/SWSH12/SerperiorV_7.py +++ b/spirit/game/scripts/cards/SWSH12/SerperiorV_7.py @@ -1,6 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def noble_light(ctx): + for pokemon in ctx.my_pokemon_in_play() + ctx.opponent_pokemon_in_play(): + await ctx.heal(30, pokemon) + + card = PokemonCardDef( guid="c9f62229-c761-503c-a0da-a942143af970", key="SWSH12", @@ -22,7 +28,7 @@ card = PokemonCardDef( title="Noble Light", game_text="Heal 30 damage from each Pok\u00e9mon (both yours and your opponent's).", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=noble_light, ), Attack( title="Solar Beam", diff --git a/spirit/game/scripts/cards/SWSH12/Sigilyph_75.py b/spirit/game/scripts/cards/SWSH12/Sigilyph_75.py index 20ba6db..fd1e291 100644 --- a/spirit/game/scripts/cards/SWSH12/Sigilyph_75.py +++ b/spirit/game/scripts/cards/SWSH12/Sigilyph_75.py @@ -1,5 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, is_pokemon_v from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_basic_pokemon + + +async def warning(ctx): + """Search for a Basic Pokemon onto the Bench; up to 5 if the opponent's + Active is a Pokemon V.""" + opponent_active = ctx.opponent_active() + count = 5 if opponent_active is not None and is_pokemon_v(opponent_active.archetype_id) else 1 + prompt = ("Choose up to 5 Basic Pokémon to put onto your Bench." + if count > 1 else "Choose a Basic Pokémon to put onto your Bench.") + picks = await ctx.search_deck(is_basic_pokemon, count=count, minimum=0, prompt=prompt) + for pick in picks: + await ctx.bench_pokemon(pick) + await ctx.shuffle_deck() + card = PokemonCardDef( guid="0ccaa42e-3154-5c82-a964-152a272f49b1", @@ -23,7 +38,7 @@ card = PokemonCardDef( title="Warning", game_text="Search your deck for a Basic Pok\u00e9mon and put it onto your Bench. Then, shuffle your deck. If your opponent's Active Pok\u00e9mon is a Pok\u00e9mon V, you may put up to 5 Basic Pok\u00e9mon onto your Bench in this way instead.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=warning, ), Attack( title="Cutting Wind", diff --git a/spirit/game/scripts/cards/SWSH12/SkuntankV_108.py b/spirit/game/scripts/cards/SWSH12/SkuntankV_108.py index c7279aa..203218c 100644 --- a/spirit/game/scripts/cards/SWSH12/SkuntankV_108.py +++ b/spirit/game/scripts/cards/SWSH12/SkuntankV_108.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack + + +async def pursuit_blast(ctx): + """30 to a Benched target, 120 instead if it retreated last turn (no W/R).""" + bench = ctx.opponent_bench() + if not bench: + return + target = await ctx.choose_pokemon(bench, "Choose 1 of your opponent's Benched Pokémon") + if target is None: + return + retreated = target.entity_id in ctx.session.turn_state.retreated_entities_last_turn + await ctx.deal_damage(120 if retreated else 30, target=target, apply_modifiers=False) card = PokemonCardDef( guid="030d3812-f599-5df8-a93e-a6156322b6bc", @@ -22,14 +35,14 @@ card = PokemonCardDef( title="Pursuit Blast", game_text="This attack does 30 damage to 1 of your opponent's Benched Pok\u00e9mon. If that Pok\u00e9mon retreated from the Active Spot during your opponent's last turn, this attack does 120 damage instead. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + effect=pursuit_blast, ), Attack( title="Shrieking Poison", game_text="Your opponent's Active Pok\u00e9mon is now Confused and Poisoned.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 2}, damage=90, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED, SpecialConditions.POISONED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/SkuntankV_180.py b/spirit/game/scripts/cards/SWSH12/SkuntankV_180.py index 6a64385..7cb34c8 100644 --- a/spirit/game/scripts/cards/SWSH12/SkuntankV_180.py +++ b/spirit/game/scripts/cards/SWSH12/SkuntankV_180.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack + + +async def pursuit_blast(ctx): + """30 to a Benched target, 120 instead if it retreated last turn (no W/R).""" + bench = ctx.opponent_bench() + if not bench: + return + target = await ctx.choose_pokemon(bench, "Choose 1 of your opponent's Benched Pokémon") + if target is None: + return + retreated = target.entity_id in ctx.session.turn_state.retreated_entities_last_turn + await ctx.deal_damage(120 if retreated else 30, target=target, apply_modifiers=False) card = PokemonCardDef( guid="1017ca1f-7579-5c13-9270-60feeafda2c4", @@ -22,14 +35,14 @@ card = PokemonCardDef( title="Pursuit Blast", game_text="This attack does 30 damage to 1 of your opponent's Benched Pok\u00e9mon. If that Pok\u00e9mon retreated from the Active Spot during your opponent's last turn, this attack does 120 damage instead. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + effect=pursuit_blast, ), Attack( title="Shrieking Poison", game_text="Your opponent's Active Pok\u00e9mon is now Confused and Poisoned.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 2}, damage=90, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED, SpecialConditions.POISONED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/SkuntankV_181.py b/spirit/game/scripts/cards/SWSH12/SkuntankV_181.py index c1a6315..9ae4b33 100644 --- a/spirit/game/scripts/cards/SWSH12/SkuntankV_181.py +++ b/spirit/game/scripts/cards/SWSH12/SkuntankV_181.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack + + +async def pursuit_blast(ctx): + """30 to a Benched target, 120 instead if it retreated last turn (no W/R).""" + bench = ctx.opponent_bench() + if not bench: + return + target = await ctx.choose_pokemon(bench, "Choose 1 of your opponent's Benched Pokémon") + if target is None: + return + retreated = target.entity_id in ctx.session.turn_state.retreated_entities_last_turn + await ctx.deal_damage(120 if retreated else 30, target=target, apply_modifiers=False) card = PokemonCardDef( guid="b528a4e5-1d04-5dc6-a5fc-877f8d62872c", @@ -22,14 +35,14 @@ card = PokemonCardDef( title="Pursuit Blast", game_text="This attack does 30 damage to 1 of your opponent's Benched Pok\u00e9mon. If that Pok\u00e9mon retreated from the Active Spot during your opponent's last turn, this attack does 120 damage instead. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + effect=pursuit_blast, ), Attack( title="Shrieking Poison", game_text="Your opponent's Active Pok\u00e9mon is now Confused and Poisoned.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 2}, damage=90, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED, SpecialConditions.POISONED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Slurpuff_84.py b/spirit/game/scripts/cards/SWSH12/Slurpuff_84.py index ca6c861..c636bff 100644 --- a/spirit/game/scripts/cards/SWSH12/Slurpuff_84.py +++ b/spirit/game/scripts/cards/SWSH12/Slurpuff_84.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import heal_attack card = PokemonCardDef( guid="a9750a39-7f05-5fd8-9642-767fa96c63fc", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Heal 30 damage from this Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 2}, damage=30, - effect=unimplemented, + effect=heal_attack(30), ), Attack( title="Magical Shot", diff --git a/spirit/game/scripts/cards/SWSH12/Smeargle_137.py b/spirit/game/scripts/cards/SWSH12/Smeargle_137.py index f32e68d..174ee99 100644 --- a/spirit/game/scripts/cards/SWSH12/Smeargle_137.py +++ b/spirit/game/scripts/cards/SWSH12/Smeargle_137.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import look_top_attach_energy +from spirit.game.card_effects.trainers import is_basic_energy_card + +colorful_palette = look_top_attach_energy( + 5, predicate=is_basic_energy_card, rest="shuffle", distribute=False, minimum=0 +) card = PokemonCardDef( guid="44f27339-c7d3-577a-9262-177c5a4cc904", @@ -22,7 +28,7 @@ card = PokemonCardDef( title="Colorful Palette", game_text="Look at the top 5 cards of your deck. You may attach any number of basic Energy cards you find there to 1 of your Pok\u00e9mon. Shuffle the other cards back into your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=colorful_palette, ), Attack( title="Ram", diff --git a/spirit/game/scripts/cards/SWSH12/Snorunt_41.py b/spirit/game/scripts/cards/SWSH12/Snorunt_41.py index fcdd15e..eb53dfa 100644 --- a/spirit/game/scripts/cards/SWSH12/Snorunt_41.py +++ b/spirit/game/scripts/cards/SWSH12/Snorunt_41.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import draw_attack + +collect = draw_attack(1) card = PokemonCardDef( guid="39bb4d6d-0249-5916-b48f-bfcc170a6fd9", @@ -22,7 +25,7 @@ card = PokemonCardDef( title="Collect", game_text="Draw a card.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=collect, ), Attack( title="Icy Snow", diff --git a/spirit/game/scripts/cards/SWSH12/Spinda_141.py b/spirit/game/scripts/cards/SWSH12/Spinda_141.py index 6b04064..711f558 100644 --- a/spirit/game/scripts/cards/SWSH12/Spinda_141.py +++ b/spirit/game/scripts/cards/SWSH12/Spinda_141.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import damage_all_opponents + + +async def _dizzying_spin_confuse(ctx): + """After the spread hit: confuse the opponent's Active.""" + await ctx.apply_special_condition(ctx.defender, SpecialConditions.CONFUSED) + card = PokemonCardDef( guid="537131fd-c9fa-5364-8d49-08ba3015a951", @@ -22,7 +29,7 @@ card = PokemonCardDef( title="Dizzying Spin", game_text="This attack does 10 damage to each of your opponent's Pok\u00e9mon. Your opponent's Active Pok\u00e9mon is now Confused. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=damage_all_opponents(10, also=_dizzying_spin_confuse), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Stonjourner_101.py b/spirit/game/scripts/cards/SWSH12/Stonjourner_101.py index 6c363c6..c7d54f6 100644 --- a/spirit/game/scripts/cards/SWSH12/Stonjourner_101.py +++ b/spirit/game/scripts/cards/SWSH12/Stonjourner_101.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if +from spirit.game.session.effects import is_supporter_card + + +def _no_supporter_in_discard(ctx) -> bool: + return not any(is_supporter_card(c) for c in ctx.discard_pile()) + card = PokemonCardDef( guid="90334fc1-7871-5630-8b70-5661f2027844", @@ -29,7 +36,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 2}, damage=60, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_no_supporter_in_discard, 130), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Stunfisk_55.py b/spirit/game/scripts/cards/SWSH12/Stunfisk_55.py index 7fb4ce4..eda63e8 100644 --- a/spirit/game/scripts/cards/SWSH12/Stunfisk_55.py +++ b/spirit/game/scripts/cards/SWSH12/Stunfisk_55.py @@ -1,5 +1,9 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import snipe_attack +from spirit.game.card_effects.trainers import has_supporter_in_discard + +mystery_bolt = snipe_attack(100, pool="any", count=1) card = PokemonCardDef( guid="4deeeaca-344b-51d8-b242-b7b72ce985a4", @@ -27,7 +31,8 @@ card = PokemonCardDef( title="Mystery Bolt", game_text="You can use this attack only if you have no Supporter cards in your discard pile. This attack does 100 damage to 1 of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + condition=lambda board, player_id, pokemon: not has_supporter_in_discard(board, player_id), + effect=mystery_bolt, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Sunflora_6.py b/spirit/game/scripts/cards/SWSH12/Sunflora_6.py index fd684f3..dbba952 100644 --- a/spirit/game/scripts/cards/SWSH12/Sunflora_6.py +++ b/spirit/game/scripts/cards/SWSH12/Sunflora_6.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import is_energy_card + + +async def _bright_beam(ctx): + picks = await ctx.discard_from_hand( + 3, minimum=0, predicate=is_energy_card, + prompt="Discard up to 3 Energy cards from your hand.", + ) + await ctx.deal_damage(10 + 70 * len(picks)) + card = PokemonCardDef( guid="f7d0f2ff-14ed-5a81-ab4b-1486d82c4964", @@ -25,7 +35,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=10, damage_operator="+", - effect=unimplemented, + effect=_bright_beam, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Swablu_142.py b/spirit/game/scripts/cards/SWSH12/Swablu_142.py index c2cb9bc..918cc0f 100644 --- a/spirit/game/scripts/cards/SWSH12/Swablu_142.py +++ b/spirit/game/scripts/cards/SWSH12/Swablu_142.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="10f617c5-5dc8-572f-9e3a-509abf0c85e3", @@ -25,7 +26,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 1}, damage=20, damage_operator="x", - effect=unimplemented, + effect=flip_damage(until_tails=True, per_heads=20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Talonflame_29.py b/spirit/game/scripts/cards/SWSH12/Talonflame_29.py index 49901df..31d2954 100644 --- a/spirit/game/scripts/cards/SWSH12/Talonflame_29.py +++ b/spirit/game/scripts/cards/SWSH12/Talonflame_29.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import snipe_attack, bonus_if, has_damage card = PokemonCardDef( guid="a0e2376a-a8e2-52c6-a55e-d82d630b2142", @@ -23,7 +24,7 @@ card = PokemonCardDef( title="Quick Dive", game_text="This attack does 50 damage to 1 of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.FIRE: 1}, - effect=unimplemented, + effect=snipe_attack(50, pool="any", count=1), ), Attack( title="Merciless Strike", @@ -31,7 +32,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 1}, damage=80, damage_operator="+", - effect=unimplemented, + effect=bonus_if(has_damage(), 80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Terrakion_97.py b/spirit/game/scripts/cards/SWSH12/Terrakion_97.py index 5950bbe..8986a7a 100644 --- a/spirit/game/scripts/cards/SWSH12/Terrakion_97.py +++ b/spirit/game/scripts/cards/SWSH12/Terrakion_97.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import protect_next_turn + + +def _cavern_tackle_condition(board, player_id, pokemon): + for p in board.pokemon_in_play(player_id): + for used_id, _archetype, used_title in board.turn_state.attacks_used_last_turn: + if used_id == p.entity_id and used_title == "Cavern Tackle": + return False + return True + card = PokemonCardDef( guid="b92d18c5-1b20-534e-95fc-738e6bfc4b8e", @@ -23,7 +33,8 @@ card = PokemonCardDef( game_text="During your opponent's next turn, prevent all damage from attacks done to this Pok\u00e9mon. If 1 of your Pok\u00e9mon used Cavern Tackle during your last turn, this attack can't be used.", cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + condition=_cavern_tackle_condition, + effect=protect_next_turn(prevent=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Togedemaru_127.py b/spirit/game/scripts/cards/SWSH12/Togedemaru_127.py index ae36e4a..6a08177 100644 --- a/spirit/game/scripts/cards/SWSH12/Togedemaru_127.py +++ b/spirit/game/scripts/cards/SWSH12/Togedemaru_127.py @@ -1,5 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.passives import Passive + + +class TogeDashPassive(Passive): + def modify_prizes_for_knockout(self, pokemon, ctx, count, carrier): + return 0 + + +async def toge_dash(ctx): + """Flip a coin. If heads, if this Pokémon is Knocked Out during your + opponent's next turn, they can't take any Prize cards for it.""" + await ctx.deal_damage() + heads = await ctx.flip_coins(1, "Toge Dash") + if heads[0]: + ctx.add_passive_through_opponents_turn(ctx.source, TogeDashPassive()) card = PokemonCardDef( guid="f74c1ce2-1da1-5af1-8b2f-4a18da88c2a0", @@ -24,7 +39,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, during your opponent's next turn, if this Pok\u00e9mon is Knocked Out, your opponent can't take any Prize cards for it.", cost={PokemonTypes.LIGHTNING: 1}, damage=10, - effect=unimplemented, + effect=toge_dash, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Torracat_31.py b/spirit/game/scripts/cards/SWSH12/Torracat_31.py index 39a09d8..566f404 100644 --- a/spirit/game/scripts/cards/SWSH12/Torracat_31.py +++ b/spirit/game/scripts/cards/SWSH12/Torracat_31.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import guts_survive_passive + + +async def gritty_claws(ctx): + """40 damage; this Pokemon survives a would-be KO next turn at 10 HP if it has full HP now.""" + await ctx.deal_damage() + ctx.add_passive_through_opponents_turn( + ctx.attacker, + guts_survive_passive(hp_floor=10, title="Gritty Claws", flip=False, require_full_hp=True), + ) + card = PokemonCardDef( guid="a5770fc4-fedb-5dd0-a636-31505b2bf992", @@ -24,7 +35,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, if this Pok\u00e9mon has full HP and would be Knocked Out by damage from an attack, it is not Knocked Out, and its remaining HP becomes 10.", cost={PokemonTypes.FIRE: 2}, damage=40, - effect=unimplemented, + effect=gritty_claws, ), Attack( title="Combustion", diff --git a/spirit/game/scripts/cards/SWSH12/Toxapex_115.py b/spirit/game/scripts/cards/SWSH12/Toxapex_115.py index 4ecf2c0..b18def7 100644 --- a/spirit/game/scripts/cards/SWSH12/Toxapex_115.py +++ b/spirit/game/scripts/cards/SWSH12/Toxapex_115.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import bonus_if, defender_has_condition card = PokemonCardDef( guid="9539d98d-3ded-5237-90a1-979ed1936e40", @@ -25,7 +26,7 @@ card = PokemonCardDef( cost={PokemonTypes.DARKNESS: 1}, damage=10, damage_operator="+", - effect=unimplemented, + effect=bonus_if(defender_has_condition(SpecialConditions.POISONED), 120), ), Attack( title="Spike Shot", diff --git a/spirit/game/scripts/cards/SWSH12/Toxicroak_110.py b/spirit/game/scripts/cards/SWSH12/Toxicroak_110.py index 7458850..9d01dc2 100644 --- a/spirit/game/scripts/cards/SWSH12/Toxicroak_110.py +++ b/spirit/game/scripts/cards/SWSH12/Toxicroak_110.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import TakesLessPassive + + +async def diving_uppercut(ctx): + """120. During your opponent's next turn, this Pokemon takes 50 more + damage from attacks (after applying Weakness and Resistance).""" + await ctx.deal_damage() + ctx.add_passive_through_opponents_turn(ctx.attacker, TakesLessPassive(-50)) + card = PokemonCardDef( guid="8bdd8b77-da42-5060-9fd8-f57b5e2f59b7", @@ -29,7 +38,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, this Pok\u00e9mon takes 50 more damage from attacks (after applying Weakness and Resistance).", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + effect=diving_uppercut, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/UnidentifiedFossil_165.py b/spirit/game/scripts/cards/SWSH12/UnidentifiedFossil_165.py index 323ed6f..3f9c496 100644 --- a/spirit/game/scripts/cards/SWSH12/UnidentifiedFossil_165.py +++ b/spirit/game/scripts/cards/SWSH12/UnidentifiedFossil_165.py @@ -1,7 +1,8 @@ -from spirit.game.data_utils import ItemCardDef +from spirit.game.data_utils import FossilItemCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import FossilBodyPassive, fossil_discard_ability -card = ItemCardDef( +card = FossilItemCardDef( guid="e9e06f61-c93b-5f12-8f53-969981c60f7e", key="SWSH12", name="com.direwolfdigital.cake.data.archetypes.trainer.UnidentifiedFossil.Name", @@ -10,5 +11,8 @@ card = ItemCardDef( subtypes=["Item"], collector_number=165, set_code="SWSH12", - rarity=Rarities.Uncommon + rarity=Rarities.Uncommon, + hp=60, + passive=FossilBodyPassive(), + abilities=[fossil_discard_ability()], ) diff --git a/spirit/game/scripts/cards/SWSH12/UnownVSTAR_199.py b/spirit/game/scripts/cards/SWSH12/UnownVSTAR_199.py index 145dbb6..424a83f 100644 --- a/spirit/game/scripts/cards/SWSH12/UnownVSTAR_199.py +++ b/spirit/game/scripts/cards/SWSH12/UnownVSTAR_199.py @@ -1,5 +1,38 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import ( + PokemonCardDef, Attack, Ability, ability_id_for, ABILITIES_BY_ID, +) +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.attacks_common import flip_damage +from spirit.game.session.passives import Passive + +_GUID = "d7df8307-aa3d-5a25-9099-4f9291d1ff75" + + +class _StarCipherPassive(Passive): + def modify_weakness(self, calc, carrier): + if calc.target.owning_player_id != carrier.owning_player_id: + calc.weak_types = [PokemonTypes.PSYCHIC.value] + + +_STAR_CIPHER_GRANTED = Ability( + title="Star Cipher", + game_text="The Weakness of each of your opponent's Pokémon in play is now Psychic. (The amount of Weakness doesn't change.)", + passive=_StarCipherPassive(), +) +_STAR_CIPHER_GRANTED.ability_id = ability_id_for(_GUID, 90) +ABILITIES_BY_ID[_STAR_CIPHER_GRANTED.ability_id] = _STAR_CIPHER_GRANTED + + +async def star_cipher(ctx): + """VSTAR Power: gain an Ability making opponent's Pokemon Weak to Psychic.""" + entries = list(ctx.attacker.get_attribute(AttrID.PIE_ABILITIES) or []) + if all(e.get("abilityID") != _STAR_CIPHER_GRANTED.ability_id + for e in entries if isinstance(e, dict)): + entries.append(_STAR_CIPHER_GRANTED.to_dict()) + await ctx.session._broadcast_entity_attribute( + ctx.attacker, AttrID.PIE_ABILITIES, entries + ) + card = PokemonCardDef( guid="d7df8307-aa3d-5a25-9099-4f9291d1ff75", @@ -26,13 +59,14 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 1}, damage=70, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=3, per_heads=70), ), Attack( title="Star Cipher", game_text="Until this Pok\u00e9mon leaves play, it gains an Ability that has the effect \"The Weakness of each of your opponent's Pok\u00e9mon in play is now Psychic. (The amount of Weakness doesn't change.)\" (You can't use more than 1 VSTAR Power in a game.)", cost={PokemonTypes.COLORLESS: 3}, - effect=unimplemented, + vstar=True, + effect=star_cipher, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/UnownVSTAR_66.py b/spirit/game/scripts/cards/SWSH12/UnownVSTAR_66.py index bc2c81d..616d411 100644 --- a/spirit/game/scripts/cards/SWSH12/UnownVSTAR_66.py +++ b/spirit/game/scripts/cards/SWSH12/UnownVSTAR_66.py @@ -1,5 +1,38 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import ( + PokemonCardDef, Attack, Ability, ability_id_for, ABILITIES_BY_ID, +) +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.attacks_common import flip_damage +from spirit.game.session.passives import Passive + +_GUID = "db5a6a5e-0350-58b3-955c-8dd6868d1ab3" + + +class _StarCipherPassive(Passive): + def modify_weakness(self, calc, carrier): + if calc.target.owning_player_id != carrier.owning_player_id: + calc.weak_types = [PokemonTypes.PSYCHIC.value] + + +_STAR_CIPHER_GRANTED = Ability( + title="Star Cipher", + game_text="The Weakness of each of your opponent's Pokémon in play is now Psychic. (The amount of Weakness doesn't change.)", + passive=_StarCipherPassive(), +) +_STAR_CIPHER_GRANTED.ability_id = ability_id_for(_GUID, 90) +ABILITIES_BY_ID[_STAR_CIPHER_GRANTED.ability_id] = _STAR_CIPHER_GRANTED + + +async def star_cipher(ctx): + """VSTAR Power: gain an Ability making opponent's Pokemon Weak to Psychic.""" + entries = list(ctx.attacker.get_attribute(AttrID.PIE_ABILITIES) or []) + if all(e.get("abilityID") != _STAR_CIPHER_GRANTED.ability_id + for e in entries if isinstance(e, dict)): + entries.append(_STAR_CIPHER_GRANTED.to_dict()) + await ctx.session._broadcast_entity_attribute( + ctx.attacker, AttrID.PIE_ABILITIES, entries + ) + card = PokemonCardDef( guid="db5a6a5e-0350-58b3-955c-8dd6868d1ab3", @@ -26,13 +59,14 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 1}, damage=70, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=3, per_heads=70), ), Attack( title="Star Cipher", game_text="Until this Pok\u00e9mon leaves play, it gains an Ability that has the effect \"The Weakness of each of your opponent's Pok\u00e9mon in play is now Psychic. (The amount of Weakness doesn't change.)\" (You can't use more than 1 VSTAR Power in a game.)", cost={PokemonTypes.COLORLESS: 3}, - effect=unimplemented, + vstar=True, + effect=star_cipher, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/UnownV_176.py b/spirit/game/scripts/cards/SWSH12/UnownV_176.py index 591d144..43ada6c 100644 --- a/spirit/game/scripts/cards/SWSH12/UnownV_176.py +++ b/spirit/game/scripts/cards/SWSH12/UnownV_176.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack, count_prizes_remaining + + +async def victory_symbol(ctx): + """No damage. If you have only 1 Prize card remaining, you win the game.""" + if count_prizes_remaining("mine")(ctx) == 1: + await ctx.win_game("Victory Symbol") card = PokemonCardDef( guid="d03cee67-a14e-52a4-a653-e862bf65533a", @@ -24,13 +31,13 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Confused.", cost={PokemonTypes.PSYCHIC: 1}, damage=30, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED), ), Attack( title="Victory Symbol", game_text="If you use this attack when you have only 1 Prize card remaining, you win this game.", cost={PokemonTypes.COLORLESS: 3}, - effect=unimplemented, + effect=victory_symbol, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/UnownV_177.py b/spirit/game/scripts/cards/SWSH12/UnownV_177.py index cae651f..ee44ed1 100644 --- a/spirit/game/scripts/cards/SWSH12/UnownV_177.py +++ b/spirit/game/scripts/cards/SWSH12/UnownV_177.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack, count_prizes_remaining + + +async def victory_symbol(ctx): + """No damage. If you have only 1 Prize card remaining, you win the game.""" + if count_prizes_remaining("mine")(ctx) == 1: + await ctx.win_game("Victory Symbol") card = PokemonCardDef( guid="9607adb2-2f4e-5e7c-90d8-e5d3b7f1f1ad", @@ -24,13 +31,13 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Confused.", cost={PokemonTypes.PSYCHIC: 1}, damage=30, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED), ), Attack( title="Victory Symbol", game_text="If you use this attack when you have only 1 Prize card remaining, you win this game.", cost={PokemonTypes.COLORLESS: 3}, - effect=unimplemented, + effect=victory_symbol, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/UnownV_65.py b/spirit/game/scripts/cards/SWSH12/UnownV_65.py index fc61ca8..218af3b 100644 --- a/spirit/game/scripts/cards/SWSH12/UnownV_65.py +++ b/spirit/game/scripts/cards/SWSH12/UnownV_65.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack, count_prizes_remaining + + +async def victory_symbol(ctx): + """No damage. If you have only 1 Prize card remaining, you win the game.""" + if count_prizes_remaining("mine")(ctx) == 1: + await ctx.win_game("Victory Symbol") card = PokemonCardDef( guid="a0f3b1c3-5168-5a50-807c-318e3dda08b6", @@ -24,13 +31,13 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Confused.", cost={PokemonTypes.PSYCHIC: 1}, damage=30, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED), ), Attack( title="Victory Symbol", game_text="If you use this attack when you have only 1 Prize card remaining, you win this game.", cost={PokemonTypes.COLORLESS: 3}, - effect=unimplemented, + effect=victory_symbol, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/UrsalunaV_102.py b/spirit/game/scripts/cards/SWSH12/UrsalunaV_102.py index e45fa26..93d2651 100644 --- a/spirit/game/scripts/cards/SWSH12/UrsalunaV_102.py +++ b/spirit/game/scripts/cards/SWSH12/UrsalunaV_102.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, damage_counters_on +from spirit.game.card_effects.passives_common import takes_less_passive card = PokemonCardDef( guid="3fc8d7a1-d601-5b59-9dac-3e8e196f790c", @@ -21,7 +23,7 @@ card = PokemonCardDef( Ability( title="Hard Coat", game_text="This Pok\u00e9mon takes 30 less damage from attacks (after applying Weakness and Resistance).", - effect=unimplemented, + passive=takes_less_passive(30), ), Attack( title="Peat Shoulder", @@ -29,7 +31,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 3}, damage=220, damage_operator="-", - effect=unimplemented, + effect=damage_per(damage_counters_on("self"), -10, base=220), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Venomoth_2.py b/spirit/game/scripts/cards/SWSH12/Venomoth_2.py index 82c31d7..e473031 100644 --- a/spirit/game/scripts/cards/SWSH12/Venomoth_2.py +++ b/spirit/game/scripts/cards/SWSH12/Venomoth_2.py @@ -1,5 +1,28 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions + +_CONDITION_CHOICES = [ + ("Asleep", SpecialConditions.ASLEEP), + ("Burned", SpecialConditions.BURNED), + ("Confused", SpecialConditions.CONFUSED), + ("Paralyzed", SpecialConditions.PARALYZED), + ("Poisoned", SpecialConditions.POISONED), +] + + +async def miracle_powder(ctx): + """30. Flip a coin. Heads: choose a Special Condition for the Defending Pokemon.""" + await ctx.deal_damage() + if not (await ctx.flip_coins(1, "Miracle Powder"))[0]: + return + target = ctx.defender + if target is None: + return + idx = await ctx.choose( + "Choose a Special Condition", [name for name, _ in _CONDITION_CHOICES], + ) + await ctx.apply_special_condition(target, _CONDITION_CHOICES[idx][1]) + card = PokemonCardDef( guid="eb790c34-26c0-5d0a-9f7e-9f7258c9c0e0", @@ -24,7 +47,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, choose a Special Condition. Your opponent's Active Pok\u00e9mon is now affected by that Special Condition.", cost={PokemonTypes.GRASS: 1}, damage=30, - effect=unimplemented, + effect=miracle_powder, ), Attack( title="Gust", diff --git a/spirit/game/scripts/cards/SWSH12/Victini_23.py b/spirit/game/scripts/cards/SWSH12/Victini_23.py index 7f12c09..69d0f13 100644 --- a/spirit/game/scripts/cards/SWSH12/Victini_23.py +++ b/spirit/game/scripts/cards/SWSH12/Victini_23.py @@ -1,5 +1,27 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import energy_provides_type +from spirit.game.card_effects.support_common import distribute_energy + + +async def assisting_flame(ctx): + """20 damage. Attach up to 2 Fire Energy cards from your discard pile to + your Pokemon, any way you like.""" + await ctx.deal_damage() + cards = [c for c in ctx.discard_pile() if energy_provides_type(c, PokemonTypes.FIRE.value)] + if not cards: + return + picks = await ctx.choose_cards( + cards, 2, minimum=0, + prompt="Choose up to 2 Fire Energy cards from your discard pile to attach.", + ) + if not picks: + return + candidates = ctx.my_pokemon_in_play() + if not candidates: + return + await distribute_energy(ctx, picks, candidates) + card = PokemonCardDef( guid="279096ac-b318-5148-a0b7-9912fbcd76ad", @@ -20,10 +42,10 @@ card = PokemonCardDef( abilities=[ Attack( title="Assisting Flame", - game_text="Attach up to 2 Fire Energy cards from your discard pile to your Pok\u00e9mon in any way you like.", + game_text="Attach up to 2 Fire Energy cards from your discard pile to your Pokémon in any way you like.", cost={PokemonTypes.FIRE: 1}, damage=20, - effect=unimplemented, + effect=assisting_flame, ), Attack( title="Flare", @@ -31,4 +53,4 @@ card = PokemonCardDef( damage=40, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH12/Virizion_14.py b/spirit/game/scripts/cards/SWSH12/Virizion_14.py index 6657b26..63db32a 100644 --- a/spirit/game/scripts/cards/SWSH12/Virizion_14.py +++ b/spirit/game/scripts/cards/SWSH12/Virizion_14.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, named_in_play +from spirit.game.card_effects.support_common import heal_attack card = PokemonCardDef( guid="88103eb6-5a2b-506c-bed7-15ec901166b4", @@ -24,14 +26,16 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=bonus_if( + named_in_play("Cobalion", "Terrakion", "Keldeo", require_all=True), 70 + ), ), Attack( title="Leaf Drain", game_text="Heal 20 damage from this Pok\u00e9mon.", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=50, - effect=unimplemented, + effect=heal_attack(20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Vulpix_17.py b/spirit/game/scripts/cards/SWSH12/Vulpix_17.py index 0306117..54bdc27 100644 --- a/spirit/game/scripts/cards/SWSH12/Vulpix_17.py +++ b/spirit/game/scripts/cards/SWSH12/Vulpix_17.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus card = PokemonCardDef( guid="71720dcb-7eb0-53b6-adfb-19056cb9bfc7", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 1}, damage=10, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Wailmer_37.py b/spirit/game/scripts/cards/SWSH12/Wailmer_37.py index 73a15dc..6a284ba 100644 --- a/spirit/game/scripts/cards/SWSH12/Wailmer_37.py +++ b/spirit/game/scripts/cards/SWSH12/Wailmer_37.py @@ -1,6 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def gulp_down(ctx): + """Heal from this Pokémon the same amount of damage you did to your + opponent's Active Pokémon.""" + dealt = await ctx.deal_damage() + if dealt > 0: + await ctx.heal(dealt, ctx.attacker) + card = PokemonCardDef( guid="bec6f3d7-b9a5-5c53-870d-ab9fbfd3e9ef", key="SWSH12", @@ -23,7 +31,7 @@ card = PokemonCardDef( game_text="Heal from this Pok\u00e9mon the same amount of damage you did to your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=gulp_down, ), Attack( title="Wave Splash", diff --git a/spirit/game/scripts/cards/SWSH12/Wailord_38.py b/spirit/game/scripts/cards/SWSH12/Wailord_38.py index c89f2ec..cfb4beb 100644 --- a/spirit/game/scripts/cards/SWSH12/Wailord_38.py +++ b/spirit/game/scripts/cards/SWSH12/Wailord_38.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if +from spirit.game.card_effects.passives_common import takes_less_passive +from spirit.game.session.effects import is_special_energy + + +def _has_special_energy(ctx): + return any(is_special_energy(c) for c in ctx.attached_energies(ctx.attacker)) card = PokemonCardDef( guid="0f9ce1b0-88d8-560f-b792-aa989c183fcb", @@ -22,7 +29,7 @@ card = PokemonCardDef( Ability( title="Jumbo-Sized", game_text="This Pok\u00e9mon takes 30 less damage from attacks (after applying Weakness and Resistance).", - effect=unimplemented, + passive=takes_less_passive(30), ), Attack( title="Special Wave", @@ -30,7 +37,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 3, PokemonTypes.COLORLESS: 1}, damage=120, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_has_special_energy, 120), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH12/Zeraora_56.py b/spirit/game/scripts/cards/SWSH12/Zeraora_56.py index c63cf37..29dc944 100644 --- a/spirit/game/scripts/cards/SWSH12/Zeraora_56.py +++ b/spirit/game/scripts/cards/SWSH12/Zeraora_56.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, active_is +from spirit.game.session.effects import is_evolution_pokemon card = PokemonCardDef( guid="67417d48-8976-5d62-bd34-b402c360ef14", @@ -24,7 +26,7 @@ card = PokemonCardDef( cost={PokemonTypes.LIGHTNING: 1}, damage=30, damage_operator="+", - effect=unimplemented, + effect=bonus_if(active_is(is_evolution_pokemon), 30), ), Attack( title="Mach Bolt", diff --git a/spirit/game/scripts/cards/SWSH12/Zygarde_134.py b/spirit/game/scripts/cards/SWSH12/Zygarde_134.py index cf4c557..c4eea78 100644 --- a/spirit/game/scripts/cards/SWSH12/Zygarde_134.py +++ b/spirit/game/scripts/cards/SWSH12/Zygarde_134.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import attach_from_discard +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.card_effects.passives_common import is_in_active_spot card = PokemonCardDef( guid="18232951-426c-5481-a3d1-9a4b9f349b5f", @@ -22,7 +25,11 @@ card = PokemonCardDef( game_text="Attach a basic Energy card from your discard pile to 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.GRASS: 1}, damage=20, - effect=unimplemented, + effect=attach_from_discard( + predicate=is_basic_energy_card, count=1, + target=lambda p: not is_in_active_spot(p), + prompt="Choose a Benched Pokémon to attach the Energy to.", + ), ), Attack( title="Speed Attack", diff --git a/spirit/game/scripts/cards/SWSH2/Aegislash_135.py b/spirit/game/scripts/cards/SWSH2/Aegislash_135.py index aa12cb4..8a9b231 100644 --- a/spirit/game/scripts/cards/SWSH2/Aegislash_135.py +++ b/spirit/game/scripts/cards/SWSH2/Aegislash_135.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import takes_less_passive card = PokemonCardDef( guid="078d1bb9-3aef-5095-990d-46022d273fed", @@ -23,7 +24,7 @@ card = PokemonCardDef( Ability( title="Big Shield", game_text="All of your Pok\u00e9mon take 30 less damage from your opponent's attacks (after applying Weakness and Resistance). You can't apply more than 1 Big Shield Ability at a time.", - effect=unimplemented, + passive=takes_less_passive(30, protects="team", stack_key="BigShield"), ), Attack( title="Power Edge", diff --git a/spirit/game/scripts/cards/SWSH2/Alcremie_87.py b/spirit/game/scripts/cards/SWSH2/Alcremie_87.py index 4595e21..4b4a3e9 100644 --- a/spirit/game/scripts/cards/SWSH2/Alcremie_87.py +++ b/spirit/game/scripts/cards/SWSH2/Alcremie_87.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.card_effects.support_common import distribute_energy, heal_attack + + +async def decorate(ctx): + """Attach any number of basic Energy cards from your hand to your Pokemon in any way you like.""" + energies = [c for c in ctx.hand() if is_basic_energy_card(c)] + if not energies: + return + picks = await ctx.choose_cards( + energies, len(energies), minimum=0, + prompt="Choose basic Energy cards to attach.", + ) + candidates = ctx.my_pokemon_in_play() + if picks and candidates: + await distribute_energy(ctx, picks, candidates) + card = PokemonCardDef( guid="2cbfa407-5d37-5aef-8bb6-305674c06fd3", @@ -23,14 +40,14 @@ card = PokemonCardDef( title="Decorate", game_text="Attach any number of basic Energy cards from your hand to your Pok\u00e9mon in any way you like.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=decorate, ), Attack( title="Draining Kiss", game_text="Heal 30 damage from this Pok\u00e9mon.", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=50, - effect=unimplemented, + effect=heal_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Appletun_23.py b/spirit/game/scripts/cards/SWSH2/Appletun_23.py index 395c97c..030aecb 100644 --- a/spirit/game/scripts/cards/SWSH2/Appletun_23.py +++ b/spirit/game/scripts/cards/SWSH2/Appletun_23.py @@ -1,5 +1,26 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import requires_in_play +from spirit.game.session.effects import is_basic_pokemon + + +async def delicious_aroma(ctx): + """You may flip a coin; on heads switch an opposing Benched Basic in.""" + if not await ctx.ask_yes_no( + "Flip a coin to switch 1 of your opponent's Benched Basic " + "Pokémon with their Active Pokémon?"): + return + heads = (await ctx.flip_coins(1, "Delicious Aroma"))[0] + if not heads: + return + candidates = [p for p in ctx.opponent_bench() if is_basic_pokemon(p)] + if not candidates: + return + target = await ctx.choose_pokemon( + candidates, "Choose the opponent's new Active Pokémon") + if target is not None: + await ctx.switch_active(ctx.opponent_id, target) + card = PokemonCardDef( guid="88ba1dc3-c4d2-58dc-b67d-a78d0c0eb106", @@ -22,7 +43,9 @@ card = PokemonCardDef( Ability( title="Delicious Aroma", game_text="Once during your turn, you may flip a coin. If heads, switch 1 of your opponent's Benched Basic Pok\u00e9mon with their Active Pok\u00e9mon.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=requires_in_play(is_basic_pokemon, side="opponent"), + effect=delicious_aroma, ), Attack( title="Solar Beam", diff --git a/spirit/game/scripts/cards/SWSH2/Applin_20.py b/spirit/game/scripts/cards/SWSH2/Applin_20.py index 443a123..0c6216d 100644 --- a/spirit/game/scripts/cards/SWSH2/Applin_20.py +++ b/spirit/game/scripts/cards/SWSH2/Applin_20.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="ef6c76b8-d17f-53cb-a137-112fce5de7ba", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=10, damage_operator="+", - effect=unimplemented, + effect=flip_damage(until_tails=True, bonus_per_heads=30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Applin_21.py b/spirit/game/scripts/cards/SWSH2/Applin_21.py index 3b78409..a0de7f4 100644 --- a/spirit/game/scripts/cards/SWSH2/Applin_21.py +++ b/spirit/game/scripts/cards/SWSH2/Applin_21.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import flip_protection card = PokemonCardDef( guid="0ee9bc5e-7eab-5569-8bf5-0436c84eaf41", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Withdraw", game_text="Flip a coin. If heads, during your opponent's next turn, prevent all damage done to this Pok\u00e9mon by attacks.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=flip_protection(prevent=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Arcanine_28.py b/spirit/game/scripts/cards/SWSH2/Arcanine_28.py index a45c9dd..b6cdd73 100644 --- a/spirit/game/scripts/cards/SWSH2/Arcanine_28.py +++ b/spirit/game/scripts/cards/SWSH2/Arcanine_28.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, def_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.passives import Passive, carrier_pokemon + + +class WarmingUpPassive(Passive): + def max_hp_bonus(self, pokemon, carrier): + if pokemon is not carrier_pokemon(carrier): + return 0 + for child in carrier.children: + definition = def_for(getattr(child, "archetype_id", None) or "") + if definition is not None and definition.display_name == "Burning Scarf": + return 100 + return 0 + card = PokemonCardDef( guid="5484aedf-88dd-5122-b7db-ed05a849e5e6", @@ -22,7 +35,7 @@ card = PokemonCardDef( Ability( title="Warming Up", game_text="If this Pok\u00e9mon has a Burning Scarf attached, it gets +100 HP.", - effect=unimplemented, + passive=WarmingUpPassive(), ), Attack( title="Fire Mane", diff --git a/spirit/game/scripts/cards/SWSH2/Barbaracle_104.py b/spirit/game/scripts/cards/SWSH2/Barbaracle_104.py index 30f24f0..ba462cc 100644 --- a/spirit/game/scripts/cards/SWSH2/Barbaracle_104.py +++ b/spirit/game/scripts/cards/SWSH2/Barbaracle_104.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if + + +async def rock_hurl(ctx): + """50; this attack's damage isn't affected by Resistance.""" + await ctx.deal_damage(ignore_resistance=True) + + +def _more_hand_cards(ctx) -> bool: + return ctx.hand_size() > ctx.hand_size(ctx.opponent_id) card = PokemonCardDef( guid="9665df54-ad69-5f4b-b27e-02e1c8ff3111", @@ -24,7 +34,7 @@ card = PokemonCardDef( game_text="This attack's damage isn't affected by Resistance.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 1}, damage=50, - effect=unimplemented, + effect=rock_hurl, ), Attack( title="Hand Press", @@ -32,7 +42,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 2}, damage=80, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_more_hand_cards, 80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Barraskewda_53.py b/spirit/game/scripts/cards/SWSH2/Barraskewda_53.py index 1092509..0e04ed9 100644 --- a/spirit/game/scripts/cards/SWSH2/Barraskewda_53.py +++ b/spirit/game/scripts/cards/SWSH2/Barraskewda_53.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import is_energy_card, energy_provides_type + + +def _is_water_energy_card(card): + return is_energy_card(card) and energy_provides_type(card, PokemonTypes.WATER) + + +async def spiral_jet(ctx): + """Discard 2 Water Energy cards from hand. If you don't, this attack does nothing.""" + discarded = await ctx.discard_from_hand( + 2, minimum=0, predicate=_is_water_energy_card, + prompt="Discard 2 Water Energy cards from your hand for Spiral Jet", + ) + if len(discarded) < 2: + return + await ctx.deal_damage() + card = PokemonCardDef( guid="b5aa2e29-b89c-5cd4-8765-a4e71b923d01", @@ -29,7 +46,7 @@ card = PokemonCardDef( game_text="Discard 2 Water Energy cards from your hand. If you don't, this attack does nothing.", cost={PokemonTypes.WATER: 1}, damage=130, - effect=unimplemented, + effect=spiral_jet, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH2/Bewear_150.py b/spirit/game/scripts/cards/SWSH2/Bewear_150.py index 2bcf93d..6c7d3fe 100644 --- a/spirit/game/scripts/cards/SWSH2/Bewear_150.py +++ b/spirit/game/scripts/cards/SWSH2/Bewear_150.py @@ -1,5 +1,27 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import mill_attack +from spirit.game.session.effects import full_stack + + +async def big_throw(ctx): + """Flip a coin; on heads, discard the opponent's Active and all attached + cards (a discard, not a Knock Out -- no Prize is taken for it).""" + heads = (await ctx.flip_coins(1, "Big Throw"))[0] + if not heads: + return + target = ctx.defender + if target is None or ctx.effects_blocked(target): + return + await ctx.discard_cards(full_stack(target)) + + async def _promote(): + if not await ctx.session._promote_new_active(ctx.opponent_id): + screen_name = ctx.session.players[ctx.opponent_id].screen_name + await ctx.session.end_game( + ctx.player_id, f"{screen_name} has no Pokémon left") + ctx.deferred_actions.append(_promote) + card = PokemonCardDef( guid="026d6885-9216-5891-83ce-9a93c8672e0f", @@ -24,13 +46,13 @@ card = PokemonCardDef( game_text="Discard the top card of your opponent's deck.", cost={PokemonTypes.COLORLESS: 3}, damage=90, - effect=unimplemented, + effect=mill_attack(1), ), Attack( title="Big Throw", game_text="Flip a coin. If heads, discard your opponent's Active Pok\u00e9mon and all attached cards.", cost={PokemonTypes.COLORLESS: 4}, - effect=unimplemented, + effect=big_throw, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/BigCharm_206.py b/spirit/game/scripts/cards/SWSH2/BigCharm_206.py index dc21425..2d22bff 100644 --- a/spirit/game/scripts/cards/SWSH2/BigCharm_206.py +++ b/spirit/game/scripts/cards/SWSH2/BigCharm_206.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented +from spirit.game.data_utils import PokemonToolCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.passives_common import hp_bonus_tool card = PokemonToolCardDef( guid="54c8f905-d509-52ca-b0b7-03fadfacd82d", @@ -11,5 +12,5 @@ card = PokemonToolCardDef( collector_number=206, set_code="SWSH2", rarity=Rarities.RareSecret, - effect=unimplemented + passive=hp_bonus_tool(30) ) diff --git a/spirit/game/scripts/cards/SWSH2/Binacle_103.py b/spirit/game/scripts/cards/SWSH2/Binacle_103.py index 2b85683..8ddbe88 100644 --- a/spirit/game/scripts/cards/SWSH2/Binacle_103.py +++ b/spirit/game/scripts/cards/SWSH2/Binacle_103.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="6e17240d-6977-5257-847e-9df2aacbbd52", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 2}, damage=50, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=2, per_heads=50), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH2/BoltundV_181.py b/spirit/game/scripts/cards/SWSH2/BoltundV_181.py index 61a6c9f..ac317bf 100644 --- a/spirit/game/scripts/cards/SWSH2/BoltundV_181.py +++ b/spirit/game/scripts/cards/SWSH2/BoltundV_181.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_attach_energy +from spirit.game.card_effects.attacks_common import damage_per, count_energy +from spirit.game.card_effects.pokemon import is_lightning_energy + + +async def electrify(ctx): + """Search up to 2 Lightning Energy and attach them to your Benched + Pokémon in any way you like.""" + bench_ids = {p.entity_id for p in ctx.my_bench()} + await search_attach_energy( + is_lightning_energy, count=2, + target_pred=lambda p: p.entity_id in bench_ids, + )(ctx) + card = PokemonCardDef( guid="ff729eb5-df9b-5589-a612-66475e5a2572", @@ -22,7 +36,7 @@ card = PokemonCardDef( title="Electrify", game_text="Search your deck for up to 2 Lightning Energy cards and attach them to your Benched Pok\u00e9mon in any way you like. Then, shuffle your deck.", cost={PokemonTypes.LIGHTNING: 1}, - effect=unimplemented, + effect=electrify, ), Attack( title="Bolt Storm", @@ -30,7 +44,9 @@ card = PokemonCardDef( cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 1}, damage=10, damage_operator="+", - effect=unimplemented, + effect=damage_per( + count_energy("mine", energy_type=PokemonTypes.LIGHTNING), 30, base=10 + ), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/BoltundV_67.py b/spirit/game/scripts/cards/SWSH2/BoltundV_67.py index 534b4a4..9e7794b 100644 --- a/spirit/game/scripts/cards/SWSH2/BoltundV_67.py +++ b/spirit/game/scripts/cards/SWSH2/BoltundV_67.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_attach_energy +from spirit.game.card_effects.attacks_common import damage_per, count_energy +from spirit.game.card_effects.pokemon import is_lightning_energy + + +async def electrify(ctx): + """Search up to 2 Lightning Energy and attach them to your Benched + Pokémon in any way you like.""" + bench_ids = {p.entity_id for p in ctx.my_bench()} + await search_attach_energy( + is_lightning_energy, count=2, + target_pred=lambda p: p.entity_id in bench_ids, + )(ctx) + card = PokemonCardDef( guid="9f009bbd-8ab9-5770-af46-ef9f63b03109", @@ -22,7 +36,7 @@ card = PokemonCardDef( title="Electrify", game_text="Search your deck for up to 2 Lightning Energy cards and attach them to your Benched Pok\u00e9mon in any way you like. Then, shuffle your deck.", cost={PokemonTypes.LIGHTNING: 1}, - effect=unimplemented, + effect=electrify, ), Attack( title="Bolt Storm", @@ -30,7 +44,9 @@ card = PokemonCardDef( cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 1}, damage=10, damage_operator="+", - effect=unimplemented, + effect=damage_per( + count_energy("mine", energy_type=PokemonTypes.LIGHTNING), 30, base=10 + ), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Bunnelby_146.py b/spirit/game/scripts/cards/SWSH2/Bunnelby_146.py index d2b4e28..dc0a563 100644 --- a/spirit/game/scripts/cards/SWSH2/Bunnelby_146.py +++ b/spirit/game/scripts/cards/SWSH2/Bunnelby_146.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import mill_attack card = PokemonCardDef( guid="92fe258c-74c7-5fa8-b618-73b944228073", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Burrow", game_text="Discard the top card of your opponent's deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=mill_attack(1), ), Attack( title="Headbutt Bounce", @@ -30,4 +31,4 @@ card = PokemonCardDef( damage=50, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH2/BurningScarf_155.py b/spirit/game/scripts/cards/SWSH2/BurningScarf_155.py index b17477a..9374e4f 100644 --- a/spirit/game/scripts/cards/SWSH2/BurningScarf_155.py +++ b/spirit/game/scripts/cards/SWSH2/BurningScarf_155.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import PokemonToolCardDef, Ability, Triggers +from spirit.game.attributes import Rarities, PokemonTypes, SpecialConditions, AttrID +from spirit.game.card_effects.passives_common import is_in_active_spot + + +async def _burning_scarf_trigger(ctx): + pokemon = ctx.source + types = pokemon.get_attribute(AttrID.POKEMON_TYPES) or [] + if PokemonTypes.FIRE.value not in types or not is_in_active_spot(pokemon): + return + await ctx.apply_special_condition(ctx.damaged_by, SpecialConditions.BURNED) + card = PokemonToolCardDef( guid="1c9666f9-be63-52b0-bd4e-ca7d86810e73", @@ -11,5 +21,12 @@ card = PokemonToolCardDef( collector_number=155, set_code="SWSH2", rarity=Rarities.Uncommon, - effect=unimplemented + granted_abilities=[ + Ability( + title="Burning Scarf", + game_text="If the Fire Pok\u00e9mon this card is attached to is in the Active Spot and is damaged by an opponent's attack (even if it is Knocked Out), the Attacking Pok\u00e9mon is now Burned.", + trigger=Triggers.ON_DAMAGED_BY_ATTACK, + effect=_burning_scarf_trigger, + ), + ], ) diff --git a/spirit/game/scripts/cards/SWSH2/Butterfree_3.py b/spirit/game/scripts/cards/SWSH2/Butterfree_3.py index 91cbca9..c44abc5 100644 --- a/spirit/game/scripts/cards/SWSH2/Butterfree_3.py +++ b/spirit/game/scripts/cards/SWSH2/Butterfree_3.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="14dbcb5e-4edb-5c9c-ad1b-05e0c6db1f78", @@ -24,7 +25,10 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Burned, Confused, and Poisoned.", cost={PokemonTypes.GRASS: 1}, damage=30, - effect=unimplemented, + effect=condition_attack( + SpecialConditions.BURNED, SpecialConditions.CONFUSED, + SpecialConditions.POISONED, + ), ), Attack( title="Cutting Wind", diff --git a/spirit/game/scripts/cards/SWSH2/CapaciousBucket_156.py b/spirit/game/scripts/cards/SWSH2/CapaciousBucket_156.py index 48ac5ad..1a6cf0c 100644 --- a/spirit/game/scripts/cards/SWSH2/CapaciousBucket_156.py +++ b/spirit/game/scripts/cards/SWSH2/CapaciousBucket_156.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import ItemCardDef +from spirit.game.attributes import Rarities, PokemonTypes, AttrID +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.card_effects.trainers import is_basic_energy_card + + +def _is_water_energy(card): + return is_basic_energy_card(card) and PokemonTypes.WATER.value in ( + card.get_attribute(AttrID.POKEMON_TYPES) or [] + ) + card = ItemCardDef( guid="16d1e917-da92-5a04-ba26-e703abff008e", @@ -11,5 +20,8 @@ card = ItemCardDef( collector_number=156, set_code="SWSH2", rarity=Rarities.Uncommon, - effect=unimplemented + effect=search_to_hand( + _is_water_energy, count=2, minimum=0, reveal=True, + prompt="Choose up to 2 Water Energy cards.", + ) ) diff --git a/spirit/game/scripts/cards/SWSH2/Caterpie_1.py b/spirit/game/scripts/cards/SWSH2/Caterpie_1.py index 473a0bf..e54367f 100644 --- a/spirit/game/scripts/cards/SWSH2/Caterpie_1.py +++ b/spirit/game/scripts/cards/SWSH2/Caterpie_1.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.passives import Passive, carrier_pokemon + + +class AdaptiveEvolutionPassive(Passive): + def may_evolve_early(self, pokemon, carrier): + return carrier_pokemon(carrier) is pokemon + card = PokemonCardDef( guid="9ed85e01-9150-54c7-b1ec-3ca4fbfe5420", @@ -21,7 +28,7 @@ card = PokemonCardDef( Ability( title="Adaptive Evolution", game_text="This Pok\u00e9mon can evolve during your first turn or the turn you play it.", - effect=unimplemented, + passive=AdaptiveEvolutionPassive(), ), Attack( title="Gnaw", diff --git a/spirit/game/scripts/cards/SWSH2/Chandelure_33.py b/spirit/game/scripts/cards/SWSH2/Chandelure_33.py index 0884e8c..e4f108f 100644 --- a/spirit/game/scripts/cards/SWSH2/Chandelure_33.py +++ b/spirit/game/scripts/cards/SWSH2/Chandelure_33.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.passives_common import no_weakness_passive +from spirit.game.models.board import BoardState + + +def _protective_glow_protects(target, carrier): + return ( + target.owning_player_id == carrier.owning_player_id + and bool(BoardState.attached_energies(target)) + ) + card = PokemonCardDef( guid="86bff073-c9a4-5dd1-97d0-d7dd2f5564c6", @@ -22,14 +33,14 @@ card = PokemonCardDef( Ability( title="Protective Glow", game_text="All of your Pok\u00e9mon that have Energy attached have no Weakness.", - effect=unimplemented, + passive=no_weakness_passive(_protective_glow_protects), ), Attack( title="Mirage Flare", game_text="Your opponent's Active Pok\u00e9mon is now Confused.", cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 1}, damage=110, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Charjabug_65.py b/spirit/game/scripts/cards/SWSH2/Charjabug_65.py index aecc88a..9a6733f 100644 --- a/spirit/game/scripts/cards/SWSH2/Charjabug_65.py +++ b/spirit/game/scripts/cards/SWSH2/Charjabug_65.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import is_lightning_energy +from spirit.game.card_effects.support_common import search_attach_energy card = PokemonCardDef( guid="c23ec7ff-8792-5d26-9e9b-ca6331ecd9d9", @@ -23,7 +25,10 @@ card = PokemonCardDef( title="Charge", game_text="Search your deck for up to 2 Lightning Energy cards and attach them to this Pok\u00e9mon. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_attach_energy( + predicate=is_lightning_energy, count=2, to_self=True, + prompt="Choose up to 2 Lightning Energy cards to attach.", + ), ), Attack( title="Lightning Ball", diff --git a/spirit/game/scripts/cards/SWSH2/Chatot_142.py b/spirit/game/scripts/cards/SWSH2/Chatot_142.py index 6b5e228..aeeb949 100644 --- a/spirit/game/scripts/cards/SWSH2/Chatot_142.py +++ b/spirit/game/scripts/cards/SWSH2/Chatot_142.py @@ -1,5 +1,23 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_supporter_card + + +async def lucky_match(ctx): + if not await ctx.ask_yes_no("Flip a coin for Lucky Match?"): + return + heads = (await ctx.flip_coins(1, "Lucky Match"))[0] + if not heads: + return + supporters = [c for c in ctx.discard_pile() if is_supporter_card(c)] + if not supporters: + return + picks = await ctx.choose_cards( + supporters, 1, minimum=1, + prompt="Choose a Supporter card to put into your hand.", + ) + await ctx.put_in_hand(picks, reveal=False) + card = PokemonCardDef( guid="a175066a-4e1b-5f72-9144-dea4a170053d", @@ -21,8 +39,9 @@ card = PokemonCardDef( abilities=[ Ability( title="Lucky Match", - game_text="When you play this Pok\u00e9mon from your hand onto your Bench during your turn, you may flip a coin. If heads, put a Supporter card from your discard pile into your hand.", - effect=unimplemented, + game_text="When you play this Pokémon from your hand onto your Bench during your turn, you may flip a coin. If heads, put a Supporter card from your discard pile into your hand.", + trigger=Triggers.ON_PLAY, + effect=lucky_match, ), Attack( title="Glide", @@ -30,4 +49,4 @@ card = PokemonCardDef( damage=30, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH2/CinderaceVMAX_194.py b/spirit/game/scripts/cards/SWSH2/CinderaceVMAX_194.py index 745476f..921ca85 100644 --- a/spirit/game/scripts/cards/SWSH2/CinderaceVMAX_194.py +++ b/spirit/game/scripts/cards/SWSH2/CinderaceVMAX_194.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack + + +async def counter(ctx): + """30, plus that much more damage if this Pokémon was damaged by an + attack during the opponent's last turn.""" + bonus = ctx.damage_taken_last_turn(ctx.attacker) + await ctx.deal_damage(30 + bonus) + card = PokemonCardDef( guid="48ea422b-bc96-5d15-9924-f73b0918dce4", @@ -25,14 +34,14 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 1}, damage=30, damage_operator="+", - effect=unimplemented, + effect=counter, ), Attack( title="Max Pyro Ball", game_text="Your opponent's Active Pok\u00e9mon is now Burned.", cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=170, - effect=unimplemented, + effect=condition_attack(SpecialConditions.BURNED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/CinderaceVMAX_36.py b/spirit/game/scripts/cards/SWSH2/CinderaceVMAX_36.py index 2c83035..4bcfb67 100644 --- a/spirit/game/scripts/cards/SWSH2/CinderaceVMAX_36.py +++ b/spirit/game/scripts/cards/SWSH2/CinderaceVMAX_36.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack + + +async def counter(ctx): + """30, plus that much more damage if this Pokémon was damaged by an + attack during the opponent's last turn.""" + bonus = ctx.damage_taken_last_turn(ctx.attacker) + await ctx.deal_damage(30 + bonus) + card = PokemonCardDef( guid="51c24cbc-a9ff-595a-982a-aff88886960b", @@ -25,14 +34,14 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 1}, damage=30, damage_operator="+", - effect=unimplemented, + effect=counter, ), Attack( title="Max Pyro Ball", game_text="Your opponent's Active Pok\u00e9mon is now Burned.", cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=170, - effect=unimplemented, + effect=condition_attack(SpecialConditions.BURNED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/CinderaceV_178.py b/spirit/game/scripts/cards/SWSH2/CinderaceV_178.py index 9f5465c..10c2a5c 100644 --- a/spirit/game/scripts/cards/SWSH2/CinderaceV_178.py +++ b/spirit/game/scripts/cards/SWSH2/CinderaceV_178.py @@ -1,5 +1,23 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.attributes import AttrID +from spirit.game.session.passives import Passive + + +class _FieldRunnerPassive(Passive): + """No Retreat Cost while a Stadium is in play.""" + + def modify_retreat_cost(self, cost, pokemon, carrier, board): + if pokemon is not carrier: + return cost + node = carrier + while node.parent is not None: + node = node.parent + for child in node.children: + if child.get_attribute(AttrID.NAME) == "activeStadium" and child.children: + return 0 + return cost + card = PokemonCardDef( guid="47f8f433-e678-5249-9378-924d1dd3764a", @@ -21,7 +39,7 @@ card = PokemonCardDef( Ability( title="Field Runner", game_text="If a Stadium is in play, this Pok\u00e9mon has no Retreat Cost.", - effect=unimplemented, + passive=_FieldRunnerPassive(), ), Attack( title="Crimson Legs", diff --git a/spirit/game/scripts/cards/SWSH2/CinderaceV_35.py b/spirit/game/scripts/cards/SWSH2/CinderaceV_35.py index f2b2cec..a97f4ef 100644 --- a/spirit/game/scripts/cards/SWSH2/CinderaceV_35.py +++ b/spirit/game/scripts/cards/SWSH2/CinderaceV_35.py @@ -1,5 +1,23 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.attributes import AttrID +from spirit.game.session.passives import Passive + + +class _FieldRunnerPassive(Passive): + """No Retreat Cost while a Stadium is in play.""" + + def modify_retreat_cost(self, cost, pokemon, carrier, board): + if pokemon is not carrier: + return cost + node = carrier + while node.parent is not None: + node = node.parent + for child in node.children: + if child.get_attribute(AttrID.NAME) == "activeStadium" and child.children: + return 0 + return cost + card = PokemonCardDef( guid="c33475c2-20c9-54c7-8ac8-7721bc54baac", @@ -21,7 +39,7 @@ card = PokemonCardDef( Ability( title="Field Runner", game_text="If a Stadium is in play, this Pok\u00e9mon has no Retreat Cost.", - effect=unimplemented, + passive=_FieldRunnerPassive(), ), Attack( title="Crimson Legs", diff --git a/spirit/game/scripts/cards/SWSH2/Clefable_75.py b/spirit/game/scripts/cards/SWSH2/Clefable_75.py index d95a4ca..52a06ad 100644 --- a/spirit/game/scripts/cards/SWSH2/Clefable_75.py +++ b/spirit/game/scripts/cards/SWSH2/Clefable_75.py @@ -1,6 +1,30 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def prankish(ctx): + opponent_active = ctx.opponent_active() + if opponent_active is None or ctx.effects_blocked(opponent_active): + return + energies = ctx.attached_energies(opponent_active) + if not energies: + return + if not await ctx.ask_yes_no( + "Put an Energy attached to your opponent's Active Pokémon on top of their deck?" + ): + return + energy = energies[0] + if len(energies) > 1: + picks = await ctx.choose_cards( + energies, 1, + prompt="Choose an Energy to put on top of your opponent's deck.", + ) + if not picks: + return + energy = picks[0] + await ctx.put_on_top_of_deck(energy) + + card = PokemonCardDef( guid="b8e5eaa2-4d4a-5d3b-a319-685fe1dc4497", key="SWSH2", @@ -22,7 +46,8 @@ card = PokemonCardDef( Ability( title="Prankish", game_text="When you play this Pok\u00e9mon from your hand to evolve 1 of your Pok\u00e9mon during your turn, you may put an Energy attached to your opponent's Active Pok\u00e9mon on top of their deck.", - effect=unimplemented, + trigger=Triggers.ON_EVOLVE, + effect=prankish, ), Attack( title="Moon Kick", diff --git a/spirit/game/scripts/cards/SWSH2/Clefairy_74.py b/spirit/game/scripts/cards/SWSH2/Clefairy_74.py index 1865d91..768ffcd 100644 --- a/spirit/game/scripts/cards/SWSH2/Clefairy_74.py +++ b/spirit/game/scripts/cards/SWSH2/Clefairy_74.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="d5921ee8-1e3b-5c85-b40f-dbe00b6105c4", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Shining Fingers", game_text="Your opponent's Active Pok\u00e9mon is now Asleep.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=condition_attack(SpecialConditions.ASLEEP), ), Attack( title="Magical Shot", diff --git a/spirit/game/scripts/cards/SWSH2/Coalossal_107.py b/spirit/game/scripts/cards/SWSH2/Coalossal_107.py index 5cd2fdf..ce538a2 100644 --- a/spirit/game/scripts/cards/SWSH2/Coalossal_107.py +++ b/spirit/game/scripts/cards/SWSH2/Coalossal_107.py @@ -1,5 +1,44 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.trainers import is_energy_card +from spirit.game.card_effects.support_common import requires_discard, distribute_energy + + +def _is_fire_energy_card(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_energy_card(card) and PokemonTypes.FIRE.value in types + + +def _is_fighting_energy_card(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_energy_card(card) and PokemonTypes.FIGHTING.value in types + + +async def tar_generator(ctx): + """Once per turn, you may attach a Fire Energy, a Fighting Energy, or + 1 of each from your discard pile to your Pokémon in any way you like.""" + if not await ctx.ask_yes_no( + "Attach a Fire Energy card, a Fighting Energy card, or 1 of each " + "from your discard pile to your Pokémon?" + ): + return + picks = [] + fire_cards = [c for c in ctx.discard_pile() if _is_fire_energy_card(c)] + if fire_cards: + picks.extend(await ctx.choose_cards( + fire_cards, 1, minimum=0, + prompt="Choose a Fire Energy card to attach (optional)", + )) + fighting_cards = [c for c in ctx.discard_pile() if _is_fighting_energy_card(c)] + if fighting_cards: + picks.extend(await ctx.choose_cards( + fighting_cards, 1, minimum=0, + prompt="Choose a Fighting Energy card to attach (optional)", + )) + if not picks: + return + await distribute_energy(ctx, picks, ctx.my_pokemon_in_play()) + card = PokemonCardDef( guid="f7c67054-1b19-5383-bb84-b84f0e2635b9", @@ -21,8 +60,12 @@ card = PokemonCardDef( abilities=[ Ability( title="Tar Generator", - game_text="Once during your turn, you may attach a Fire Energy card, a Fighting Energy card, or 1 of each from your discard pile to your Pok\u00e9mon in any way you like.", - effect=unimplemented, + game_text="Once during your turn, you may attach a Fire Energy card, a Fighting Energy card, or 1 of each from your discard pile to your Pokémon in any way you like.", + activation=Activations.ONCE_PER_TURN, + condition=requires_discard( + lambda c: _is_fire_energy_card(c) or _is_fighting_energy_card(c) + ), + effect=tar_generator, ), Attack( title="Flaming Avalanche", @@ -30,4 +73,4 @@ card = PokemonCardDef( damage=130, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH2/CopperajahVMAX_137.py b/spirit/game/scripts/cards/SWSH2/CopperajahVMAX_137.py index d938473..08f968a 100644 --- a/spirit/game/scripts/cards/SWSH2/CopperajahVMAX_137.py +++ b/spirit/game/scripts/cards/SWSH2/CopperajahVMAX_137.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, active_is +from spirit.game.session.effects import is_basic_pokemon card = PokemonCardDef( guid="d9828a6d-91e5-50c0-b993-ad0b0be22b26", @@ -26,7 +28,7 @@ card = PokemonCardDef( cost={PokemonTypes.METAL: 2, PokemonTypes.COLORLESS: 1}, damage=100, damage_operator="+", - effect=unimplemented, + effect=bonus_if(active_is(is_basic_pokemon), 100), ), Attack( title="G-Max Hammer", diff --git a/spirit/game/scripts/cards/SWSH2/CopperajahVMAX_199.py b/spirit/game/scripts/cards/SWSH2/CopperajahVMAX_199.py index b489d9c..d06ea76 100644 --- a/spirit/game/scripts/cards/SWSH2/CopperajahVMAX_199.py +++ b/spirit/game/scripts/cards/SWSH2/CopperajahVMAX_199.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, active_is +from spirit.game.session.effects import is_basic_pokemon card = PokemonCardDef( guid="78daab62-dde9-56f0-88dc-add9f0870d3a", @@ -26,7 +28,7 @@ card = PokemonCardDef( cost={PokemonTypes.METAL: 2, PokemonTypes.COLORLESS: 1}, damage=100, damage_operator="+", - effect=unimplemented, + effect=bonus_if(active_is(is_basic_pokemon), 100), ), Attack( title="G-Max Hammer", diff --git a/spirit/game/scripts/cards/SWSH2/CopperajahV_136.py b/spirit/game/scripts/cards/SWSH2/CopperajahV_136.py index 2e3b993..e99673c 100644 --- a/spirit/game/scripts/cards/SWSH2/CopperajahV_136.py +++ b/spirit/game/scripts/cards/SWSH2/CopperajahV_136.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import protect_next_turn card = PokemonCardDef( guid="185034a7-7022-5565-9333-d8289166f437", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, this Pok\u00e9mon takes 30 less damage from attacks (after applying Weakness and Resistance).", cost={PokemonTypes.METAL: 2, PokemonTypes.COLORLESS: 1}, damage=90, - effect=unimplemented, + effect=protect_next_turn(reduce=30), ), Attack( title="Wrack Down", diff --git a/spirit/game/scripts/cards/SWSH2/CopperajahV_187.py b/spirit/game/scripts/cards/SWSH2/CopperajahV_187.py index 361c25d..582d036 100644 --- a/spirit/game/scripts/cards/SWSH2/CopperajahV_187.py +++ b/spirit/game/scripts/cards/SWSH2/CopperajahV_187.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import protect_next_turn card = PokemonCardDef( guid="4f757a17-5030-540a-a571-6e2b7f2cfe88", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, this Pok\u00e9mon takes 30 less damage from attacks (after applying Weakness and Resistance).", cost={PokemonTypes.METAL: 2, PokemonTypes.COLORLESS: 1}, damage=90, - effect=unimplemented, + effect=protect_next_turn(reduce=30), ), Attack( title="Wrack Down", diff --git a/spirit/game/scripts/cards/SWSH2/Cramorant_51.py b/spirit/game/scripts/cards/SWSH2/Cramorant_51.py index b3cd5d4..0ac10c0 100644 --- a/spirit/game/scripts/cards/SWSH2/Cramorant_51.py +++ b/spirit/game/scripts/cards/SWSH2/Cramorant_51.py @@ -1,4 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import count_energy, damage_per +from spirit.game.card_effects.passives_common import flip_protection +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -24,7 +26,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, during your opponent's next turn, prevent all effects of attacks, including damage, done to this Pok\u00e9mon.", cost={PokemonTypes.WATER: 1}, damage=20, - effect=unimplemented, + effect=flip_protection(prevent=True, effects_too=True), ), Attack( title="Hydro Pump", @@ -32,7 +34,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=50, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("self", energy_type=PokemonTypes.WATER), 20, base=50), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/CursedShovel_157.py b/spirit/game/scripts/cards/SWSH2/CursedShovel_157.py index b33dac8..6178d33 100644 --- a/spirit/game/scripts/cards/SWSH2/CursedShovel_157.py +++ b/spirit/game/scripts/cards/SWSH2/CursedShovel_157.py @@ -1,6 +1,17 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented +from spirit.game.data_utils import PokemonToolCardDef, Ability, Triggers from spirit.game.attributes import Rarities + +async def _cursed_shovel_trigger(ctx): + pokemon = ctx.source + if ctx.damage_amount < ctx.pre_hit_hp: + return + attacker = ctx.damaged_by + if attacker is None or attacker.owning_player_id == pokemon.owning_player_id: + return + await ctx.discard_cards(ctx.deck_top(2, player_id=ctx.opponent_id)) + + card = PokemonToolCardDef( guid="ca85413c-5592-5d35-bc93-723c6d0cc1d9", key="SWSH2", @@ -11,5 +22,12 @@ card = PokemonToolCardDef( collector_number=157, set_code="SWSH2", rarity=Rarities.Uncommon, - effect=unimplemented + granted_abilities=[ + Ability( + title="Cursed Shovel", + game_text="If the Pok\u00e9mon this card is attached to is Knocked Out by damage from an opponent's attack, discard the top 2 cards of your opponent's deck.", + trigger=Triggers.ON_DAMAGED_BY_ATTACK, + effect=_cursed_shovel_trigger, + ), + ], ) diff --git a/spirit/game/scripts/cards/SWSH2/Dan_158.py b/spirit/game/scripts/cards/SWSH2/Dan_158.py index ba9eaa7..5dd4951 100644 --- a/spirit/game/scripts/cards/SWSH2/Dan_158.py +++ b/spirit/game/scripts/cards/SWSH2/Dan_158.py @@ -1,6 +1,12 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities + +async def dan(ctx): + """Draw 2 cards. (Rock-Paper-Scissors bonus draw is unscripted -- no RPS engine capability.)""" + await ctx.draw_cards(2) + + card = SupporterCardDef( guid="eef4c1b0-77f4-5523-a0c4-c1749b53d1a8", key="SWSH2", @@ -11,5 +17,5 @@ card = SupporterCardDef( collector_number=158, set_code="SWSH2", rarity=Rarities.Uncommon, - effect=unimplemented + effect=dan ) diff --git a/spirit/game/scripts/cards/SWSH2/Diggersby_147.py b/spirit/game/scripts/cards/SWSH2/Diggersby_147.py index e84b2ed..81e308e 100644 --- a/spirit/game/scripts/cards/SWSH2/Diggersby_147.py +++ b/spirit/game/scripts/cards/SWSH2/Diggersby_147.py @@ -1,6 +1,23 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def mining_rush(ctx): + """Discard up to 6 cards from the top of your deck; if you do, 30 damage + for each card discarded this way.""" + top = ctx.deck_top(6) + if not top: + return + picks = await ctx.choose_cards( + top, len(top), minimum=0, display_cards=top, + prompt="Choose up to 6 cards to discard from the top of your deck", + ) + if not picks: + return + await ctx.discard_cards(picks) + await ctx.deal_damage(30 * len(picks)) + + card = PokemonCardDef( guid="901c7740-3b1a-5b7e-a902-2aedf07ff052", key="SWSH2", @@ -25,7 +42,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=30, damage_operator="x", - effect=unimplemented, + effect=mining_rush, ), Attack( title="Headbutt Bounce", @@ -33,4 +50,4 @@ card = PokemonCardDef( damage=110, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH2/Doublade_134.py b/spirit/game/scripts/cards/SWSH2/Doublade_134.py index 3f755fa..2a25bc8 100644 --- a/spirit/game/scripts/cards/SWSH2/Doublade_134.py +++ b/spirit/game/scripts/cards/SWSH2/Doublade_134.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="70e09fd7-213c-59e7-b987-11e408acfb2e", @@ -31,7 +32,7 @@ card = PokemonCardDef( cost={PokemonTypes.METAL: 2, PokemonTypes.COLORLESS: 1}, damage=80, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=2, per_heads=80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/DragapultVMAX_197.py b/spirit/game/scripts/cards/SWSH2/DragapultVMAX_197.py index a0d5401..75fe6ae 100644 --- a/spirit/game/scripts/cards/SWSH2/DragapultVMAX_197.py +++ b/spirit/game/scripts/cards/SWSH2/DragapultVMAX_197.py @@ -1,6 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def shred(ctx): + """60. This attack's damage isn't affected by any effects on the + opponent's Active Pokémon.""" + await ctx.deal_damage(ignore_target_effects=True) + + +async def max_phantom(ctx): + """130. Put 5 damage counters on your opponent's Benched Pokémon in + any way you like.""" + await ctx.deal_damage() + bench = ctx.opponent_bench() + if bench: + await ctx.place_damage_counters(5, bench) + + card = PokemonCardDef( guid="5edb9814-ca2f-5889-99d4-25c86d6b373c", key="SWSH2", @@ -25,14 +41,14 @@ card = PokemonCardDef( game_text="This attack's damage isn't affected by any effects on your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.PSYCHIC: 1}, damage=60, - effect=unimplemented, + effect=shred, ), Attack( title="Max Phantom", game_text="Put 5 damage counters on your opponent's Benched Pok\u00e9mon in any way you like.", cost={PokemonTypes.PSYCHIC: 2}, damage=130, - effect=unimplemented, + effect=max_phantom, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/DragapultVMAX_93.py b/spirit/game/scripts/cards/SWSH2/DragapultVMAX_93.py index 79c9ff9..9f861e3 100644 --- a/spirit/game/scripts/cards/SWSH2/DragapultVMAX_93.py +++ b/spirit/game/scripts/cards/SWSH2/DragapultVMAX_93.py @@ -1,6 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def shred(ctx): + """60. This attack's damage isn't affected by any effects on the + opponent's Active Pokémon.""" + await ctx.deal_damage(ignore_target_effects=True) + + +async def max_phantom(ctx): + """130. Put 5 damage counters on your opponent's Benched Pokémon in + any way you like.""" + await ctx.deal_damage() + bench = ctx.opponent_bench() + if bench: + await ctx.place_damage_counters(5, bench) + + card = PokemonCardDef( guid="aeeb1fa5-4d95-544e-8856-83a548309bfd", key="SWSH2", @@ -25,14 +41,14 @@ card = PokemonCardDef( game_text="This attack's damage isn't affected by any effects on your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.PSYCHIC: 1}, damage=60, - effect=unimplemented, + effect=shred, ), Attack( title="Max Phantom", game_text="Put 5 damage counters on your opponent's Benched Pok\u00e9mon in any way you like.", cost={PokemonTypes.PSYCHIC: 2}, damage=130, - effect=unimplemented, + effect=max_phantom, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/DragapultV_183.py b/spirit/game/scripts/cards/SWSH2/DragapultV_183.py index 60df8cd..fc41660 100644 --- a/spirit/game/scripts/cards/SWSH2/DragapultV_183.py +++ b/spirit/game/scripts/cards/SWSH2/DragapultV_183.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if + + +def _entered_active_this_turn(ctx): + return ctx.entered_active_this_turn(ctx.attacker) + card = PokemonCardDef( guid="31afd2eb-00ff-56a6-a31a-58bda27e11b3", @@ -30,7 +36,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 2}, damage=60, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_entered_active_this_turn, 80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/DragapultV_92.py b/spirit/game/scripts/cards/SWSH2/DragapultV_92.py index ee260f7..f6f4fda 100644 --- a/spirit/game/scripts/cards/SWSH2/DragapultV_92.py +++ b/spirit/game/scripts/cards/SWSH2/DragapultV_92.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if + + +def _entered_active_this_turn(ctx): + return ctx.entered_active_this_turn(ctx.attacker) + card = PokemonCardDef( guid="98101893-57f5-5813-9984-cc2ebe838ffa", @@ -30,7 +36,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 2}, damage=60, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_entered_active_this_turn, 80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Dragapult_91.py b/spirit/game/scripts/cards/SWSH2/Dragapult_91.py index a1038db..f3cc674 100644 --- a/spirit/game/scripts/cards/SWSH2/Dragapult_91.py +++ b/spirit/game/scripts/cards/SWSH2/Dragapult_91.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import flip_prevent_damage_passive + + +async def phantom_force(ctx): + """120 damage; put 3 damage counters on the opponent's Benched Pokemon + in any way you like.""" + await ctx.deal_damage() + await ctx.place_damage_counters(3, candidates=ctx.opponent_bench()) + card = PokemonCardDef( guid="b48923cd-2056-598f-b21b-5b0136d13792", @@ -23,14 +32,14 @@ card = PokemonCardDef( Ability( title="Infiltrator", game_text="If any damage is done to this Pok\u00e9mon by attacks, flip a coin. If heads, prevent that damage.", - effect=unimplemented, + passive=flip_prevent_damage_passive("Infiltrator"), ), Attack( title="Phantom Force", game_text="Put 3 damage counters on your opponent's Benched Pok\u00e9mon in any way you like.", cost={PokemonTypes.PSYCHIC: 2}, damage=120, - effect=unimplemented, + effect=phantom_force, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Drakloak_90.py b/spirit/game/scripts/cards/SWSH2/Drakloak_90.py index 5788878..d571b2b 100644 --- a/spirit/game/scripts/cards/SWSH2/Drakloak_90.py +++ b/spirit/game/scripts/cards/SWSH2/Drakloak_90.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, def_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_pokemon_card +from spirit.game.card_effects.support_common import search_to_bench + + +def _is_dreepy(c): + if not is_pokemon_card(c): + return False + definition = def_for(c.archetype_id) + return definition is not None and definition.display_name == "Dreepy" + card = PokemonCardDef( guid="76929ced-5f1c-58b3-aa3b-8e38a11bc645", @@ -24,7 +34,7 @@ card = PokemonCardDef( title="Summon", game_text="Search your deck for a Dreepy and put it onto your Bench. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_to_bench(predicate=_is_dreepy), ), Attack( title="Bite", diff --git a/spirit/game/scripts/cards/SWSH2/Dreepy_89.py b/spirit/game/scripts/cards/SWSH2/Dreepy_89.py index ef964dc..0159b25 100644 --- a/spirit/game/scripts/cards/SWSH2/Dreepy_89.py +++ b/spirit/game/scripts/cards/SWSH2/Dreepy_89.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus card = PokemonCardDef( guid="9f342c97-65f3-5071-ab8a-23a9c056669d", @@ -25,7 +26,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 1}, damage=10, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/DubwoolV_153.py b/spirit/game/scripts/cards/SWSH2/DubwoolV_153.py index 92b9f34..d76ca6f 100644 --- a/spirit/game/scripts/cards/SWSH2/DubwoolV_153.py +++ b/spirit/game/scripts/cards/SWSH2/DubwoolV_153.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_prizes_taken +from spirit.game.card_effects.passives_common import takes_less_passive card = PokemonCardDef( guid="73d2df8d-aa04-534d-8635-f5882252efbb", @@ -21,7 +23,7 @@ card = PokemonCardDef( Ability( title="Soft Wool", game_text="This Pok\u00e9mon takes 30 less damage from attacks (after applying Weakness and Resistance).", - effect=unimplemented, + passive=takes_less_passive(30), ), Attack( title="Revenge Blast", @@ -29,7 +31,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=120, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_prizes_taken("opponent"), 30, base=120), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/DubwoolV_188.py b/spirit/game/scripts/cards/SWSH2/DubwoolV_188.py index 1338f6f..23f6ccb 100644 --- a/spirit/game/scripts/cards/SWSH2/DubwoolV_188.py +++ b/spirit/game/scripts/cards/SWSH2/DubwoolV_188.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_prizes_taken +from spirit.game.card_effects.passives_common import takes_less_passive card = PokemonCardDef( guid="8835e595-ff34-550e-907e-cadec8453bea", @@ -21,7 +23,7 @@ card = PokemonCardDef( Ability( title="Soft Wool", game_text="This Pok\u00e9mon takes 30 less damage from attacks (after applying Weakness and Resistance).", - effect=unimplemented, + passive=takes_less_passive(30), ), Attack( title="Revenge Blast", @@ -29,7 +31,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=120, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_prizes_taken("opponent"), 30, base=120), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Duraludon_138.py b/spirit/game/scripts/cards/SWSH2/Duraludon_138.py index 751f046..75554ef 100644 --- a/spirit/game/scripts/cards/SWSH2/Duraludon_138.py +++ b/spirit/game/scripts/cards/SWSH2/Duraludon_138.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import attach_from_discard +from spirit.game.card_effects.trainers import is_metal_energy_card card = PokemonCardDef( guid="c7413238-7dbe-514b-b9e8-6157c55e52ec", @@ -24,7 +26,10 @@ card = PokemonCardDef( game_text="Attach a Metal Energy card from your discard pile to 1 of your Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=attach_from_discard( + predicate=is_metal_energy_card, count=1, target="choice", + prompt="Choose a Metal Energy card to attach", + ), ), Attack( title="Power Beam", diff --git a/spirit/game/scripts/cards/SWSH2/Durant_132.py b/spirit/game/scripts/cards/SWSH2/Durant_132.py index 364d406..35538bb 100644 --- a/spirit/game/scripts/cards/SWSH2/Durant_132.py +++ b/spirit/game/scripts/cards/SWSH2/Durant_132.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_trainer_card +from spirit.game.card_effects.attacks_common import count_energy + + +async def gathering_food(ctx): + """For each Energy attached to this Pokemon, search the deck for a + Trainer card, reveal it, and put it into your hand. Then, shuffle.""" + n = count_energy("self")(ctx) + if n <= 0: + return + picks = await ctx.search_deck( + is_trainer_card, count=n, minimum=0, + prompt=f"Choose up to {n} Trainer card(s) to put into your hand.", + ) + await ctx.put_in_hand(picks, reveal=True) + await ctx.shuffle_deck() + card = PokemonCardDef( guid="6650b0bd-2604-5224-9cc2-0ec89e4a6b43", @@ -23,7 +40,7 @@ card = PokemonCardDef( title="Gathering Food", game_text="For each Energy attached to this Pok\u00e9mon, search your deck for a Trainer card, reveal it, and put it into your hand. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=gathering_food, ), Attack( title="Metal Claw", diff --git a/spirit/game/scripts/cards/SWSH2/EiscueV_55.py b/spirit/game/scripts/cards/SWSH2/EiscueV_55.py index 8100da8..becf231 100644 --- a/spirit/game/scripts/cards/SWSH2/EiscueV_55.py +++ b/spirit/game/scripts/cards/SWSH2/EiscueV_55.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import energy_provides_type +from spirit.game.card_effects.attacks_common import spread_damage + + +async def cold_absorption(ctx): + """Whenever you attach a Water Energy card from your hand to this Pokemon, heal 30 damage from it.""" + if ctx.attaching_player_id != ctx.player_id or ctx.energy_receiver is not ctx.source: + return + energy = ctx.attached_energy + if energy is None or not energy_provides_type(energy, PokemonTypes.WATER.value): + return + await ctx.heal(30, ctx.source) + card = PokemonCardDef( guid="4658ad41-9e37-593f-90ad-96ff2eb7d14d", @@ -21,14 +34,15 @@ card = PokemonCardDef( Ability( title="Cold Absorption", game_text="Whenever you attach a Water Energy card from your hand to this Pok\u00e9mon during your turn, heal 30 damage from it.", - effect=unimplemented, + trigger=Triggers.ON_ENERGY_ATTACHED, + effect=cold_absorption, ), Attack( title="Blizzard", game_text="This attack also does 10 damage to each of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + effect=spread_damage(10, side="opponent", also_base=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Eiscue_54.py b/spirit/game/scripts/cards/SWSH2/Eiscue_54.py index 90c524a..7adf5d9 100644 --- a/spirit/game/scripts/cards/SWSH2/Eiscue_54.py +++ b/spirit/game/scripts/cards/SWSH2/Eiscue_54.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import spread_damage +from spirit.game.session.passives import Passive, effective_max_hp + + +class IceFacePassive(Passive): + def modify_damage_taken(self, calc, carrier): + if not (calc.is_attack and calc.is_opposing): + return + if calc.target is not carrier: + return + if carrier.get_attribute(AttrID.HP, 0) < effective_max_hp(calc.board, carrier): + return + calc.amount = max(0, calc.amount - 60) card = PokemonCardDef( guid="d0dddfa3-a086-535a-ab9c-3156ef0e3098", @@ -21,14 +34,14 @@ card = PokemonCardDef( Ability( title="Ice Face", game_text="If this Pok\u00e9mon has full HP, it takes 60 less damage from your opponent's attacks (after applying Weakness and Resistance).", - effect=unimplemented, + passive=IceFacePassive(), ), Attack( title="Blizzard", game_text="This attack also does 10 damage to each of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=70, - effect=unimplemented, + effect=spread_damage(10, side="opponent", also_base=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/EldegossV_176.py b/spirit/game/scripts/cards/SWSH2/EldegossV_176.py index 85dd0b9..48a8e5e 100644 --- a/spirit/game/scripts/cards/SWSH2/EldegossV_176.py +++ b/spirit/game/scripts/cards/SWSH2/EldegossV_176.py @@ -1,5 +1,23 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import remove_self_from_play +from spirit.game.session.effects import is_supporter_card + + +async def happy_match(ctx): + """On play from hand onto Bench: you may put a Supporter card from your + discard pile into your hand.""" + supporters = [c for c in ctx.discard_pile() if is_supporter_card(c)] + if not supporters: + return + if not await ctx.ask_yes_no( + "Put a Supporter card from your discard pile into your hand?" + ): + return + picks = await ctx.choose_cards( + supporters, 1, prompt="Choose a Supporter card to put into your hand." + ) + await ctx.put_in_hand(picks, reveal=False) card = PokemonCardDef( guid="8ca9186d-2139-5842-aa69-bffe8434071c", @@ -21,14 +39,15 @@ card = PokemonCardDef( Ability( title="Happy Match", game_text="When you play this Pok\u00e9mon from your hand onto your Bench during your turn, you may put a Supporter card from your discard pile into your hand.", - effect=unimplemented, + trigger=Triggers.ON_PLAY, + effect=happy_match, ), Attack( title="Float Up", game_text="You may shuffle this Pok\u00e9mon and all attached cards into your deck.", cost={PokemonTypes.COLORLESS: 2}, damage=50, - effect=unimplemented, + effect=remove_self_from_play("deck", optional=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/EldegossV_19.py b/spirit/game/scripts/cards/SWSH2/EldegossV_19.py index b629244..9d306e3 100644 --- a/spirit/game/scripts/cards/SWSH2/EldegossV_19.py +++ b/spirit/game/scripts/cards/SWSH2/EldegossV_19.py @@ -1,5 +1,23 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import remove_self_from_play +from spirit.game.session.effects import is_supporter_card + + +async def happy_match(ctx): + """On play from hand onto Bench: you may put a Supporter card from your + discard pile into your hand.""" + supporters = [c for c in ctx.discard_pile() if is_supporter_card(c)] + if not supporters: + return + if not await ctx.ask_yes_no( + "Put a Supporter card from your discard pile into your hand?" + ): + return + picks = await ctx.choose_cards( + supporters, 1, prompt="Choose a Supporter card to put into your hand." + ) + await ctx.put_in_hand(picks, reveal=False) card = PokemonCardDef( guid="c4c876f7-b56f-50f8-ba32-cb7d9a72e330", @@ -21,14 +39,15 @@ card = PokemonCardDef( Ability( title="Happy Match", game_text="When you play this Pok\u00e9mon from your hand onto your Bench during your turn, you may put a Supporter card from your discard pile into your hand.", - effect=unimplemented, + trigger=Triggers.ON_PLAY, + effect=happy_match, ), Attack( title="Float Up", game_text="You may shuffle this Pok\u00e9mon and all attached cards into your deck.", cost={PokemonTypes.COLORLESS: 2}, damage=50, - effect=unimplemented, + effect=remove_self_from_play("deck", optional=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Electivire_59.py b/spirit/game/scripts/cards/SWSH2/Electivire_59.py index ae969d1..da29648 100644 --- a/spirit/game/scripts/cards/SWSH2/Electivire_59.py +++ b/spirit/game/scripts/cards/SWSH2/Electivire_59.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, condition_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.session.effects import is_special_energy + + +def _has_special_energy(ctx) -> bool: + return any(is_special_energy(e) for e in ctx.attached_energies(ctx.attacker)) card = PokemonCardDef( guid="4185dc51-a9dd-5940-8a61-d2990e6b6bf8", @@ -24,7 +30,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 1}, damage=50, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), Attack( title="Electrified Bolt", @@ -32,7 +38,7 @@ card = PokemonCardDef( cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 1}, damage=90, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_has_special_energy, 90), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Electrode_57.py b/spirit/game/scripts/cards/SWSH2/Electrode_57.py index 0c7ead9..e60a0cc 100644 --- a/spirit/game/scripts/cards/SWSH2/Electrode_57.py +++ b/spirit/game/scripts/cards/SWSH2/Electrode_57.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_hand, is_energy card = PokemonCardDef( guid="58be2930-fe7e-537d-8af0-b483ae2ffee6", @@ -23,7 +24,10 @@ card = PokemonCardDef( title="Orb Collector", game_text="Search your deck for up to 3 Energy cards, reveal them, and put them into your hand. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_to_hand( + is_energy, count=3, minimum=0, reveal=True, + prompt="Choose up to 3 Energy cards to put into your hand.", + ), ), Attack( title="Rolling Attack", diff --git a/spirit/game/scripts/cards/SWSH2/FalinksV_110.py b/spirit/game/scripts/cards/SWSH2/FalinksV_110.py index b073d32..983f78b 100644 --- a/spirit/game/scripts/cards/SWSH2/FalinksV_110.py +++ b/spirit/game/scripts/cards/SWSH2/FalinksV_110.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, def_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import takes_less_passive + + +def _protects_falinks(target, carrier): + if target.owning_player_id != carrier.owning_player_id: + return False + definition = def_for(target.archetype_id) + return bool(definition and "falinks" in (definition.display_name or "").lower()) + card = PokemonCardDef( guid="0c0ab75b-19c7-57e9-aa55-f7a01bd74d18", @@ -21,14 +30,14 @@ card = PokemonCardDef( Ability( title="Iron Defense Formation", game_text="All of your Pok\u00e9mon that have \"Falinks\" in their name take 20 less damage from your opponent's attacks (after applying Weakness and Resistance).", - effect=unimplemented, + passive=takes_less_passive(20, protects=_protects_falinks), ), Attack( title="Giga Impact", game_text="During your next turn, this Pok\u00e9mon can't attack.", cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=210, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/FalinksV_185.py b/spirit/game/scripts/cards/SWSH2/FalinksV_185.py index 876a23d..6a1230b 100644 --- a/spirit/game/scripts/cards/SWSH2/FalinksV_185.py +++ b/spirit/game/scripts/cards/SWSH2/FalinksV_185.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, def_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import takes_less_passive + + +def _protects_falinks(target, carrier): + if target.owning_player_id != carrier.owning_player_id: + return False + definition = def_for(target.archetype_id) + return bool(definition and "falinks" in (definition.display_name or "").lower()) + card = PokemonCardDef( guid="45ad3075-16d0-5034-9fe7-ca8a81419e83", @@ -21,14 +30,14 @@ card = PokemonCardDef( Ability( title="Iron Defense Formation", game_text="All of your Pok\u00e9mon that have \"Falinks\" in their name take 20 less damage from your opponent's attacks (after applying Weakness and Resistance).", - effect=unimplemented, + passive=takes_less_passive(20, protects=_protects_falinks), ), Attack( title="Giga Impact", game_text="During your next turn, this Pok\u00e9mon can't attack.", cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=210, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Falinks_109.py b/spirit/game/scripts/cards/SWSH2/Falinks_109.py index 843d525..9511083 100644 --- a/spirit/game/scripts/cards/SWSH2/Falinks_109.py +++ b/spirit/game/scripts/cards/SWSH2/Falinks_109.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, def_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_bench +from spirit.game.card_effects.support_common import search_to_bench, is_basic + + +def _falinks_named(p): + d = def_for(p.archetype_id) + return bool(d and d.display_name and "Falinks" in d.display_name) card = PokemonCardDef( guid="9e23a960-4d99-50a7-a248-1874244c2d31", @@ -22,7 +29,7 @@ card = PokemonCardDef( title="Call for Family", game_text="Search your deck for up to 2 Basic Pok\u00e9mon and put them onto your Bench. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_to_bench(is_basic, count=2), ), Attack( title="Team Attack", @@ -30,7 +37,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=30, damage_operator="x", - effect=unimplemented, + effect=damage_per(count_bench("mine", pred=_falinks_named), 30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Flapple_22.py b/spirit/game/scripts/cards/SWSH2/Flapple_22.py index 71599e0..7ea8573 100644 --- a/spirit/game/scripts/cards/SWSH2/Flapple_22.py +++ b/spirit/game/scripts/cards/SWSH2/Flapple_22.py @@ -1,5 +1,44 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import full_stack + + +async def apple_drop(ctx): + if not await ctx.ask_yes_no( + "Put 2 damage counters on 1 of your opponent's Pokémon?"): + return + candidates = ctx.opponent_pokemon_in_play() + if not candidates: + return + target = await ctx.choose_pokemon( + candidates, "Choose 1 of your opponent's Pokémon") + if target is None: + return + dealt = await ctx.deal_damage(20, target=target, as_counters=True) + if dealt <= 0: + return + pokemon = ctx.source + was_active = pokemon is ctx.my_active() + await ctx.shuffle_into_deck(full_stack(pokemon), ctx.player_id) + if was_active: + async def _promote(): + if not await ctx.session._promote_new_active(ctx.player_id): + screen_name = ctx.session.players[ctx.player_id].screen_name + await ctx.session.end_game( + ctx.opponent_id, f"{screen_name} has no Pokémon left") + ctx.deferred_actions.append(_promote) + + +async def acid_spray(ctx): + await ctx.deal_damage() + heads = (await ctx.flip_coins(1, "Acid Spray"))[0] + if not heads: + return + target = ctx.opponent_active() + if target is None or ctx.effects_blocked(target): + return + await ctx.discard_energy_from( + target, 1, prompt="Choose Energy to discard from the Defending Pokémon") card = PokemonCardDef( guid="da729e0f-309f-50ef-aafe-9c34630e6e10", @@ -22,14 +61,15 @@ card = PokemonCardDef( Ability( title="Apple Drop", game_text="Once during your turn, you may put 2 damage counters on 1 of your opponent's Pok\u00e9mon. If you placed any damage counters in this way, shuffle this Pok\u00e9mon and all attached cards into your deck.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + effect=apple_drop, ), Attack( title="Acid Spray", game_text="Flip a coin. If heads, discard an Energy from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 2}, damage=60, - effect=unimplemented, + effect=acid_spray, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Frosmoth_204.py b/spirit/game/scripts/cards/SWSH2/Frosmoth_204.py index 3d08731..26bf613 100644 --- a/spirit/game/scripts/cards/SWSH2/Frosmoth_204.py +++ b/spirit/game/scripts/cards/SWSH2/Frosmoth_204.py @@ -1,5 +1,33 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.session.effects import is_special_energy, is_water_pokemon +from spirit.game.card_effects.pokemon import is_energy_card + + +def _is_water_energy_card(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_energy_card(card) and not is_special_energy(card) and PokemonTypes.WATER.value in types + + +def _ice_dance_condition(board, player_id, pokemon): + hand = board.find_player_area(player_id, "hand") + if not hand or not any(_is_water_energy_card(c) for c in hand.children): + return False + bench = board.find_player_area(player_id, "bench") + return bool(bench) and any(is_water_pokemon(c) for c in bench.children) + + +async def ice_dance(ctx): + """As often as you like: attach a Water Energy card from hand to a Benched Water Pokemon.""" + energies = [c for c in ctx.hand() if _is_water_energy_card(c)] + picked = await ctx.choose_cards(energies, 1, minimum=1, prompt="Choose a Water Energy card to attach") + if not picked: + return + targets = [p for p in ctx.my_bench() if is_water_pokemon(p)] + target = await ctx.choose_pokemon(targets, "Choose the Benched Water Pokémon to attach it to") + if target is not None: + await ctx.attach_energy(picked[0], target) + card = PokemonCardDef( guid="1f6f4ce6-389d-592e-9abf-633f3cbb6aba", @@ -22,7 +50,9 @@ card = PokemonCardDef( Ability( title="Ice Dance", game_text="As often as you like during your turn, you may attach a Water Energy card from your hand to 1 of your Benched Water Pok\u00e9mon.", - effect=unimplemented, + activation=Activations.UNLIMITED, + condition=_ice_dance_condition, + effect=ice_dance, ), Attack( title="Aurora Beam", diff --git a/spirit/game/scripts/cards/SWSH2/FullHeal_159.py b/spirit/game/scripts/cards/SWSH2/FullHeal_159.py index 37d8c3c..502f4b4 100644 --- a/spirit/game/scripts/cards/SWSH2/FullHeal_159.py +++ b/spirit/game/scripts/cards/SWSH2/FullHeal_159.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.support_common import cure_conditions_effect card = ItemCardDef( guid="ae5dbb3f-2b7e-5ca0-9eae-d15cbce462bd", @@ -11,5 +12,5 @@ card = ItemCardDef( collector_number=159, set_code="SWSH2", rarity=Rarities.Uncommon, - effect=unimplemented + effect=cure_conditions_effect("active") ) diff --git a/spirit/game/scripts/cards/SWSH2/GalarMine_160.py b/spirit/game/scripts/cards/SWSH2/GalarMine_160.py index 7bca085..86a2ef6 100644 --- a/spirit/game/scripts/cards/SWSH2/GalarMine_160.py +++ b/spirit/game/scripts/cards/SWSH2/GalarMine_160.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import StadiumCardDef, unimplemented +from spirit.game.data_utils import StadiumCardDef from spirit.game.attributes import Rarities +from spirit.game.session.passives import Passive +from spirit.game.card_effects.passives_common import is_in_active_spot + + +class GalarMinePassive(Passive): + def modify_retreat_cost(self, cost, pokemon, carrier, board): + if is_in_active_spot(pokemon): + return cost + 2 + return cost + card = StadiumCardDef( guid="8f6486b5-b50d-51cc-9b8f-fc469e8a02b7", @@ -11,5 +21,5 @@ card = StadiumCardDef( collector_number=160, set_code="SWSH2", rarity=Rarities.Uncommon, - effect=unimplemented + passive=GalarMinePassive() ) diff --git a/spirit/game/scripts/cards/SWSH2/GalarianCorsola_78.py b/spirit/game/scripts/cards/SWSH2/GalarianCorsola_78.py index 7fc2ee0..69042c9 100644 --- a/spirit/game/scripts/cards/SWSH2/GalarianCorsola_78.py +++ b/spirit/game/scripts/cards/SWSH2/GalarianCorsola_78.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import place_counters card = PokemonCardDef( guid="4e6c4e23-e8d2-56e2-a4e8-1423b8c15a0c", @@ -23,7 +24,7 @@ card = PokemonCardDef( title="Cursed Drop", game_text="Put 3 damage counters on your opponent's Pok\u00e9mon in any way you like.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=place_counters(3, "choose_any_opponent"), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/GalarianCursola_79.py b/spirit/game/scripts/cards/SWSH2/GalarianCursola_79.py index 2028145..5454642 100644 --- a/spirit/game/scripts/cards/SWSH2/GalarianCursola_79.py +++ b/spirit/game/scripts/cards/SWSH2/GalarianCursola_79.py @@ -1,5 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import condition_attack + + +async def perish_body(ctx): + """If in the Active Spot and Knocked Out by damage from an opponent's + attack, flip a coin. If heads, the Attacking Pokemon is Knocked Out.""" + if not ctx.ko_from_attack: + return + active_area = ctx.board.find_player_area(ctx.player_id, "activePokemonArea") + if active_area is not None and active_area.children: + return # was Benched, not Active, when Knocked Out + results = await ctx.flip_coins(1, "Perish Body") + if results and results[0]: + await ctx.knock_out(ctx.ko_attacker) + card = PokemonCardDef( guid="7c46f16c-86b7-5ad4-8c5a-b9525c7c87c4", @@ -22,15 +37,16 @@ card = PokemonCardDef( abilities=[ Ability( title="Perish Body", - game_text="If this Pok\u00e9mon is in the Active Spot and is Knocked Out by damage from an opponent's attack, flip a coin. If heads, the Attacking Pok\u00e9mon is Knocked Out.", - effect=unimplemented, + game_text="If this Pokémon is in the Active Spot and is Knocked Out by damage from an opponent's attack, flip a coin. If heads, the Attacking Pokémon is Knocked Out.", + trigger=Triggers.ON_KNOCKED_OUT, + effect=perish_body, ), Attack( title="Corner", - game_text="During your opponent's next turn, the Defending Pok\u00e9mon can't retreat.", + game_text="During your opponent's next turn, the Defending Pokémon can't retreat.", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=60, - effect=unimplemented, + effect=condition_attack(no_retreat=True), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH2/GalarianDarmanitan_48.py b/spirit/game/scripts/cards/SWSH2/GalarianDarmanitan_48.py index 2e6a935..cf04ad1 100644 --- a/spirit/game/scripts/cards/SWSH2/GalarianDarmanitan_48.py +++ b/spirit/game/scripts/cards/SWSH2/GalarianDarmanitan_48.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import spread_damage card = PokemonCardDef( guid="336fea38-bce6-5ec2-ad53-2370a7663fe2", @@ -24,14 +25,14 @@ card = PokemonCardDef( game_text="This attack also does 10 damage to each of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=80, - effect=unimplemented, + effect=spread_damage(10, also_base=True), ), Attack( title="Crushing Headbutt", game_text="During your next turn, this Pok\u00e9mon can't use Crushing Headbutt.", cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 2}, damage=170, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/GalarianDarumaka_47.py b/spirit/game/scripts/cards/SWSH2/GalarianDarumaka_47.py index 9dba182..61a832a 100644 --- a/spirit/game/scripts/cards/SWSH2/GalarianDarumaka_47.py +++ b/spirit/game/scripts/cards/SWSH2/GalarianDarumaka_47.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="730727b9-ab87-50c2-9d94-5a24297fd44f", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/GalarianFarfetchd_94.py b/spirit/game/scripts/cards/SWSH2/GalarianFarfetchd_94.py index 3ec67fe..9585728 100644 --- a/spirit/game/scripts/cards/SWSH2/GalarianFarfetchd_94.py +++ b/spirit/game/scripts/cards/SWSH2/GalarianFarfetchd_94.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus card = PokemonCardDef( guid="7c5b7618-c545-5d1f-ac72-168753905545", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1}, damage=10, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(30), ), Attack( title="Pierce", diff --git a/spirit/game/scripts/cards/SWSH2/GalarianMeowth_126.py b/spirit/game/scripts/cards/SWSH2/GalarianMeowth_126.py index 823455d..3861f70 100644 --- a/spirit/game/scripts/cards/SWSH2/GalarianMeowth_126.py +++ b/spirit/game/scripts/cards/SWSH2/GalarianMeowth_126.py @@ -1,5 +1,23 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations, def_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import requires_hand + + +def _is_galarian_perrserker(card): + d = def_for(card.archetype_id) + return bool(d) and d.display_name == "Galarian Perrserker" + + +async def evolution_roar(ctx): + await ctx.discard_from_hand(2, prompt="Discard 2 cards to use Evolution Roar") + if await ctx.ask_yes_no("Search your deck for a Galarian Perrserker?"): + picks = await ctx.search_deck( + _is_galarian_perrserker, count=1, minimum=0, + prompt="Choose a Galarian Perrserker to put into your hand.", + ) + await ctx.put_in_hand(picks, reveal=True) + await ctx.shuffle_deck() + card = PokemonCardDef( guid="9c92919b-3dcf-55b8-866f-daa63bc46076", @@ -22,7 +40,9 @@ card = PokemonCardDef( Ability( title="Evolution Roar", game_text="You must discard 2 cards from your hand in order to use this Ability. Once during your turn, you may search your deck for a Galarian Perrserker, reveal it, and put it into your hand. Then, shuffle your deck.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=requires_hand(n=2), + effect=evolution_roar, ), Attack( title="Scratch", diff --git a/spirit/game/scripts/cards/SWSH2/GalarianMrMime_37.py b/spirit/game/scripts/cards/SWSH2/GalarianMrMime_37.py index 06994f5..945b602 100644 --- a/spirit/game/scripts/cards/SWSH2/GalarianMrMime_37.py +++ b/spirit/game/scripts/cards/SWSH2/GalarianMrMime_37.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack, flip_damage card = PokemonCardDef( guid="4b3fed0c-1fd8-5576-83ad-7ced9a5ef5b7", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Asleep.", cost={PokemonTypes.COLORLESS: 1}, damage=10, - effect=unimplemented, + effect=condition_attack(SpecialConditions.ASLEEP), ), Attack( title="Triple Spin", @@ -31,7 +32,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 1}, damage=20, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=3, per_heads=20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/GalarianMrRime_38.py b/spirit/game/scripts/cards/SWSH2/GalarianMrRime_38.py index 1b0e6d5..a47e899 100644 --- a/spirit/game/scripts/cards/SWSH2/GalarianMrRime_38.py +++ b/spirit/game/scripts/cards/SWSH2/GalarianMrRime_38.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage +from spirit.game.card_effects.passives_common import attack_effect_shield_passive +from spirit.game.card_effects.pokemon import is_energy_card + + +def _has_energy_attached(pokemon) -> bool: + return any(is_energy_card(c) for c in getattr(pokemon, "children", [])) + + +def _screen_cleaner_protects(target, carrier) -> bool: + return target.owning_player_id == carrier.owning_player_id \ + and _has_energy_attached(target) + card = PokemonCardDef( guid="8fb41ba0-f1ad-54ae-b56c-91d0ef89c4e5", @@ -21,8 +34,8 @@ card = PokemonCardDef( abilities=[ Ability( title="Screen Cleaner", - game_text="Prevent all effects of your opponent's attacks, except damage, done to all of your Pok\u00e9mon that have Energy attached. (Existing effects are not removed.)", - effect=unimplemented, + game_text="Prevent all effects of your opponent's attacks, except damage, done to all of your Pokémon that have Energy attached. (Existing effects are not removed.)", + passive=attack_effect_shield_passive(protects=_screen_cleaner_protects), ), Attack( title="Triple Spin", @@ -30,7 +43,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 1}, damage=50, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=3, per_heads=50), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH2/GalarianPerrserker_127.py b/spirit/game/scripts/cards/SWSH2/GalarianPerrserker_127.py index 1bf53a7..a73bde8 100644 --- a/spirit/game/scripts/cards/SWSH2/GalarianPerrserker_127.py +++ b/spirit/game/scripts/cards/SWSH2/GalarianPerrserker_127.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="8e655bb0-03c3-531f-8136-909742ca8011", @@ -31,7 +32,7 @@ card = PokemonCardDef( cost={PokemonTypes.METAL: 2, PokemonTypes.COLORLESS: 1}, damage=80, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=3, per_heads=80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/GalarianPerrserker_205.py b/spirit/game/scripts/cards/SWSH2/GalarianPerrserker_205.py index a16da8e..8c1077c 100644 --- a/spirit/game/scripts/cards/SWSH2/GalarianPerrserker_205.py +++ b/spirit/game/scripts/cards/SWSH2/GalarianPerrserker_205.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.passives_common import team_damage_boost_passive + + +def _is_metal_pokemon(pokemon) -> bool: + types = pokemon.get_attribute(AttrID.POKEMON_TYPES) or [] + return PokemonTypes.METAL.value in types + card = PokemonCardDef( guid="fd0ba61c-aa8a-55e0-b86f-dfb81bbe22d4", @@ -23,7 +30,7 @@ card = PokemonCardDef( Ability( title="Steely Spirit", game_text="Your Metal Pok\u00e9mon's attacks do 20 more damage to your opponent's Active Pok\u00e9mon (before applying Weakness and Resistance).", - effect=unimplemented, + passive=team_damage_boost_passive(20, attacker_pred=_is_metal_pokemon), ), Attack( title="Metal Claw", diff --git a/spirit/game/scripts/cards/SWSH2/GalarianRunerigus_102.py b/spirit/game/scripts/cards/SWSH2/GalarianRunerigus_102.py index b3d353c..a20cf7f 100644 --- a/spirit/game/scripts/cards/SWSH2/GalarianRunerigus_102.py +++ b/spirit/game/scripts/cards/SWSH2/GalarianRunerigus_102.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import ( + place_counters, damage_counters_on, recoil_attack, +) card = PokemonCardDef( guid="9f2f2067-61a3-526f-bc46-dc8cc38e2d1d", @@ -23,14 +26,16 @@ card = PokemonCardDef( title="Spreading Spite", game_text="For each damage counter on this Galarian Runerigus, put 2 damage counters on your opponent's Pok\u00e9mon in any way you like.", cost={PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + effect=place_counters( + lambda ctx: 2 * damage_counters_on("self")(ctx), "choose_any_opponent" + ), ), Attack( title="Mad Hammer", game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 2}, damage=120, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/GalarianSirfetchd_95.py b/spirit/game/scripts/cards/SWSH2/GalarianSirfetchd_95.py index dd4acbb..6eb4aea 100644 --- a/spirit/game/scripts/cards/SWSH2/GalarianSirfetchd_95.py +++ b/spirit/game/scripts/cards/SWSH2/GalarianSirfetchd_95.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.legal_actions import LOCK_UNTIL_LEAVES_ACTIVE + + +async def meteor_assault(ctx): + """180. Can't use Meteor Assault again until this Pokemon leaves Active.""" + await ctx.deal_damage() + ctx.session.turn_state.attack_locks[ + (ctx.attacker.entity_id, ctx.ability.ability_id) + ] = LOCK_UNTIL_LEAVES_ACTIVE + card = PokemonCardDef( guid="b11806af-f339-559e-886d-cfaf80104b14", @@ -29,7 +39,7 @@ card = PokemonCardDef( game_text="This Pok\u00e9mon can't use Meteor Assault again until it leaves the Active Spot.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 2}, damage=180, - effect=unimplemented, + effect=meteor_assault, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/GalarianWeezing_113.py b/spirit/game/scripts/cards/SWSH2/GalarianWeezing_113.py index 9014054..c58064d 100644 --- a/spirit/game/scripts/cards/SWSH2/GalarianWeezing_113.py +++ b/spirit/game/scripts/cards/SWSH2/GalarianWeezing_113.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.passives_common import ( + ability_lock_passive, + is_in_active_spot, + opposing_pokemon, +) + + +def _neutralizing_gas_target(pokemon, carrier): + return opposing_pokemon(pokemon, carrier) and is_in_active_spot(carrier) + card = PokemonCardDef( guid="bb68149c-b5d0-5b3c-ae45-0a56378f64cb", @@ -21,14 +32,14 @@ card = PokemonCardDef( abilities=[ Ability( title="Neutralizing Gas", - game_text="As long as this Pok\u00e9mon is in the Active Spot, your opponent's Pok\u00e9mon in play have no Abilities, except for Neutralizing Gas.", - effect=unimplemented, + game_text="As long as this Pokémon is in the Active Spot, your opponent's Pokémon in play have no Abilities, except for Neutralizing Gas.", + passive=ability_lock_passive(_neutralizing_gas_target), ), Attack( title="Severe Poison", - game_text="Your opponent's Active Pok\u00e9mon is now Poisoned. Put 4 damage counters instead of 1 on that Pok\u00e9mon during Pok\u00e9mon Checkup.", + game_text="Your opponent's Active Pokémon is now Poisoned. Put 4 damage counters instead of 1 on that Pokémon during Pokémon Checkup.", cost={PokemonTypes.DARKNESS: 1}, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED, counters=4), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH2/GalarianYamask_101.py b/spirit/game/scripts/cards/SWSH2/GalarianYamask_101.py index dd975cc..f3b4b5e 100644 --- a/spirit/game/scripts/cards/SWSH2/GalarianYamask_101.py +++ b/spirit/game/scripts/cards/SWSH2/GalarianYamask_101.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack card = PokemonCardDef( guid="b2c5e4bd-fc75-520c-abea-d0459d38a32e", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.COLORLESS: 2}, damage=50, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Garbodor_118.py b/spirit/game/scripts/cards/SWSH2/Garbodor_118.py index 35809cd..9496cf2 100644 --- a/spirit/game/scripts/cards/SWSH2/Garbodor_118.py +++ b/spirit/game/scripts/cards/SWSH2/Garbodor_118.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions + + +def _stadium_in_play_condition(board, player_id, pokemon): + area = board.find_global_area("activeStadium") + return bool(area and area.children) + + +async def poisonous_puddle(ctx): + """Once during your turn, if a Stadium is in play, you may make your + opponent's Active Pokemon Poisoned.""" + if await ctx.ask_yes_no("Make your opponent's Active Pokémon Poisoned?"): + await ctx.apply_special_condition(ctx.defender, SpecialConditions.POISONED) + card = PokemonCardDef( guid="692befb2-3abb-5fe5-9495-3fc7b3fd90ce", @@ -21,8 +34,10 @@ card = PokemonCardDef( abilities=[ Ability( title="Poisonous Puddle", - game_text="Once during your turn, if a Stadium is in play, you may make your opponent's Active Pok\u00e9mon Poisoned.", - effect=unimplemented, + game_text="Once during your turn, if a Stadium is in play, you may make your opponent's Active Pokémon Poisoned.", + activation=Activations.ONCE_PER_TURN, + condition=_stadium_in_play_condition, + effect=poisonous_puddle, ), Attack( title="Sludge Bomb", @@ -30,4 +45,4 @@ card = PokemonCardDef( damage=80, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH2/Greedent_152.py b/spirit/game/scripts/cards/SWSH2/Greedent_152.py index e257e90..f72d2cf 100644 --- a/spirit/game/scripts/cards/SWSH2/Greedent_152.py +++ b/spirit/game/scripts/cards/SWSH2/Greedent_152.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID, TrainerType +from spirit.game.card_effects.support_common import search_to_hand + +_TOOL_TYPES = (TrainerType.POKEMON_TOOL.value, TrainerType.POKEMON_TOOL_F.value) + + +def _is_tool_card(card): + return card.get_attribute(AttrID.TRAINER_TYPE) in _TOOL_TYPES + card = PokemonCardDef( guid="d771e6d9-7c9b-5807-9b5e-85d63c498dd1", @@ -22,7 +30,11 @@ card = PokemonCardDef( Ability( title="Greedy Tail", game_text="Once during your turn, you may search your deck for a Pok\u00e9mon Tool card, reveal it, and put it into your hand. Then, shuffle your deck.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + effect=search_to_hand( + _is_tool_card, count=1, minimum=0, reveal=True, + prompt="Choose a Pok\u00e9mon Tool card to reveal and put into your hand.", + ), ), Attack( title="Tail Smack", diff --git a/spirit/game/scripts/cards/SWSH2/Grimmsnarl_125.py b/spirit/game/scripts/cards/SWSH2/Grimmsnarl_125.py index c4b15a6..95fc9fb 100644 --- a/spirit/game/scripts/cards/SWSH2/Grimmsnarl_125.py +++ b/spirit/game/scripts/cards/SWSH2/Grimmsnarl_125.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy +from spirit.game.card_effects.passives_common import is_in_active_spot, opposing_active +from spirit.game.session.passives import Passive + + +class DarkOathPassive(Passive): + def modify_attack_cost(self, cost, pokemon, carrier, board): + if is_in_active_spot(carrier) and opposing_active(pokemon, carrier): + cost["Colorless"] = cost.get("Colorless", 0) + 1 + return cost + card = PokemonCardDef( guid="a0033ec6-9849-56b2-9fb9-c772b820d070", @@ -22,7 +33,7 @@ card = PokemonCardDef( Ability( title="Dark Oath", game_text="As long as this Pok\u00e9mon is in the Active Spot, your opponent's Active Pok\u00e9mon's attacks cost Colorless more.", - effect=unimplemented, + passive=DarkOathPassive(), ), Attack( title="Energy Press", @@ -30,7 +41,7 @@ card = PokemonCardDef( cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 2}, damage=100, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("defender"), 30, base=100), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Growlithe_27.py b/spirit/game/scripts/cards/SWSH2/Growlithe_27.py index 6db51b6..2d4c124 100644 --- a/spirit/game/scripts/cards/SWSH2/Growlithe_27.py +++ b/spirit/game/scripts/cards/SWSH2/Growlithe_27.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_or_nothing + + +async def _put_discard_card_in_hand(ctx): + discard = ctx.discard_pile() + if not discard: + return + picks = await ctx.choose_cards( + discard, 1, minimum=1, + prompt="Choose a card from your discard pile to put into your hand.", + ) + await ctx.put_in_hand(picks, reveal=False) card = PokemonCardDef( guid="32995129-62da-5065-b2c7-ca1ae5bb1e1b", @@ -22,7 +34,7 @@ card = PokemonCardDef( title="Odor Sleuth", game_text="Flip a coin. If heads, put a card from your discard pile into your hand.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=flip_or_nothing(then=_put_discard_card_in_hand), ), Attack( title="Fire Claws", diff --git a/spirit/game/scripts/cards/SWSH2/Gyarados_40.py b/spirit/game/scripts/cards/SWSH2/Gyarados_40.py index 20f44f2..fc46675 100644 --- a/spirit/game/scripts/cards/SWSH2/Gyarados_40.py +++ b/spirit/game/scripts/cards/SWSH2/Gyarados_40.py @@ -1,6 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def big_storm(ctx): + await ctx.deal_damage() + await ctx.discard_stadium() + + card = PokemonCardDef( guid="ba4d9911-4809-5ca5-82d8-83323dea0e89", key="SWSH2", @@ -29,7 +35,7 @@ card = PokemonCardDef( game_text="Discard any Stadium in play.", cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 3}, damage=200, - effect=unimplemented, + effect=big_storm, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Hatenna_83.py b/spirit/game/scripts/cards/SWSH2/Hatenna_83.py index 0b687d1..a3eb59f 100644 --- a/spirit/game/scripts/cards/SWSH2/Hatenna_83.py +++ b/spirit/game/scripts/cards/SWSH2/Hatenna_83.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.session.effects import is_pokemon_card card = PokemonCardDef( guid="83145193-6021-55e0-9d41-809b5610341e", @@ -23,7 +25,8 @@ card = PokemonCardDef( title="Find a Friend", game_text="Search your deck for a Pok\u00e9mon, reveal it, and put it into your hand. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_to_hand(is_pokemon_card, count=1, minimum=0, reveal=True, + prompt="Choose a Pokémon to put into your hand."), ), Attack( title="Psyshot", diff --git a/spirit/game/scripts/cards/SWSH2/Hatterene_85.py b/spirit/game/scripts/cards/SWSH2/Hatterene_85.py index 2c9694d..c06370b 100644 --- a/spirit/game/scripts/cards/SWSH2/Hatterene_85.py +++ b/spirit/game/scripts/cards/SWSH2/Hatterene_85.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import place_counters, count_discard +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_pokemon_card + + +async def mind_hat(ctx): + """Once per turn: each player discards a card from their hand (opponent first).""" + if not await ctx.ask_yes_no("Each player discards a card from their hand?"): + return + await ctx.discard_from_hand( + 1, minimum=0, player_id=ctx.opponent_id, + prompt="Discard a card from your hand.", + ) + await ctx.discard_from_hand( + 1, minimum=0, player_id=ctx.player_id, + prompt="Discard a card from your hand.", + ) + card = PokemonCardDef( guid="100c2dfb-3434-55cb-8d3d-6a4f165c0e58", @@ -23,13 +40,14 @@ card = PokemonCardDef( Ability( title="Mind Hat", game_text="Once during your turn, you may use this Ability. Each player discards a card from their hand. (Your opponent discards first.)", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + effect=mind_hat, ), Attack( title="Dripping Grudge", - game_text="Put 1 damage counter on your opponent's Active Pok\u00e9mon for each Pok\u00e9mon in your discard pile.", + game_text="Put 1 damage counter on your opponent's Active Pokémon for each Pokémon in your discard pile.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=place_counters(count_discard("mine", is_pokemon_card), "opponent_active"), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH2/Hattrem_84.py b/spirit/game/scripts/cards/SWSH2/Hattrem_84.py index 1e097e9..d88b464 100644 --- a/spirit/game/scripts/cards/SWSH2/Hattrem_84.py +++ b/spirit/game/scripts/cards/SWSH2/Hattrem_84.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import heal_attack +from spirit.game.card_effects.attacks_common import flip_bonus card = PokemonCardDef( guid="b71b318e-92a8-5a2c-8293-46f7899ca5bd", @@ -24,7 +26,7 @@ card = PokemonCardDef( title="Calm Mind", game_text="Heal 30 damage from this Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=heal_attack(30, target="self"), ), Attack( title="Brutal Swing", @@ -32,7 +34,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=30, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Hawlucha_148.py b/spirit/game/scripts/cards/SWSH2/Hawlucha_148.py index 1aab1d1..8de2375 100644 --- a/spirit/game/scripts/cards/SWSH2/Hawlucha_148.py +++ b/spirit/game/scripts/cards/SWSH2/Hawlucha_148.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import shuffle_hand_into_deck_draw card = PokemonCardDef( guid="4e59906b-1404-5891-b88d-83b56518a89c", @@ -23,7 +24,7 @@ card = PokemonCardDef( title="Windfall", game_text="Shuffle your hand into your deck. Then, draw 5 cards.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=shuffle_hand_into_deck_draw(5), ), Attack( title="Speed Attack", diff --git a/spirit/game/scripts/cards/SWSH2/Heatmor_34.py b/spirit/game/scripts/cards/SWSH2/Heatmor_34.py index 4b548a0..2b29cb3 100644 --- a/spirit/game/scripts/cards/SWSH2/Heatmor_34.py +++ b/spirit/game/scripts/cards/SWSH2/Heatmor_34.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack, self_energy_discard_attack card = PokemonCardDef( guid="50b28ad5-0de8-5aaa-92a3-4f5da75a315c", @@ -23,14 +24,14 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.COLORLESS: 1}, damage=20, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), Attack( title="Flamethrower", game_text="Discard an Energy from this Pok\u00e9mon.", cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + effect=self_energy_discard_attack(count=1), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Heliolisk_64.py b/spirit/game/scripts/cards/SWSH2/Heliolisk_64.py index aaa4a22..91d805c 100644 --- a/spirit/game/scripts/cards/SWSH2/Heliolisk_64.py +++ b/spirit/game/scripts/cards/SWSH2/Heliolisk_64.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack + + +async def eerie_impulse(ctx): + """Flip a coin. If heads, discard an Energy from 1 of your opponent's Pokémon.""" + heads = await ctx.flip_coins(1, "Eerie Impulse") + if not heads[0]: + return + candidates = [p for p in ctx.opponent_pokemon_in_play() if ctx.attached_energies(p)] + if not candidates: + return + target = await ctx.choose_pokemon( + candidates, "Choose 1 of your opponent's Pokémon to discard an Energy from" + ) + if target is not None and not ctx.effects_blocked(target): + await ctx.discard_energy_from(target, 1) + card = PokemonCardDef( guid="e0500056-9f3d-5a4f-b36c-a1a25ca198b6", @@ -23,14 +40,14 @@ card = PokemonCardDef( title="Eerie Impulse", game_text="Flip a coin. If heads, discard an Energy from 1 of your opponent's Pok\u00e9mon.", cost={PokemonTypes.LIGHTNING: 1}, - effect=unimplemented, + effect=eerie_impulse, ), Attack( title="Thunder", game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Helioptile_63.py b/spirit/game/scripts/cards/SWSH2/Helioptile_63.py index ff44634..64fdaa2 100644 --- a/spirit/game/scripts/cards/SWSH2/Helioptile_63.py +++ b/spirit/game/scripts/cards/SWSH2/Helioptile_63.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack card = PokemonCardDef( guid="ad303fe9-cbff-523c-8b7b-840320219b83", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="This Pok\u00e9mon also does 10 damage to itself.", cost={PokemonTypes.LIGHTNING: 1}, damage=30, - effect=unimplemented, + effect=recoil_attack(10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Heracross_6.py b/spirit/game/scripts/cards/SWSH2/Heracross_6.py index acc98cc..5ddb9cd 100644 --- a/spirit/game/scripts/cards/SWSH2/Heracross_6.py +++ b/spirit/game/scripts/cards/SWSH2/Heracross_6.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.support_common import gust_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Your opponent switches their Active Pok\u00e9mon with 1 of their Benched Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 2}, damage=30, - effect=unimplemented, + effect=gust_attack(opponent_chooses=True), ), Attack( title="Superpowered Horns", diff --git a/spirit/game/scripts/cards/SWSH2/Indeedee_88.py b/spirit/game/scripts/cards/SWSH2/Indeedee_88.py index 4960753..aba603c 100644 --- a/spirit/game/scripts/cards/SWSH2/Indeedee_88.py +++ b/spirit/game/scripts/cards/SWSH2/Indeedee_88.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.support_common import heal_targets card = PokemonCardDef( guid="57333162-5dc9-5e5c-b4e3-3f5292ab3e8a", @@ -21,16 +23,16 @@ card = PokemonCardDef( abilities=[ Attack( title="Replenish Time", - game_text="Heal 30 damage from each of your Pok\u00e9mon.", + game_text="Heal 30 damage from each of your Pokémon.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=heal_targets(30, "each_own"), ), Attack( title="Psybeam", - game_text="Your opponent's Active Pok\u00e9mon is now Confused.", + game_text="Your opponent's Active Pokémon is now Confused.", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH2/InteleonVMAX_195.py b/spirit/game/scripts/cards/SWSH2/InteleonVMAX_195.py index c0766b1..de581b5 100644 --- a/spirit/game/scripts/cards/SWSH2/InteleonVMAX_195.py +++ b/spirit/game/scripts/cards/SWSH2/InteleonVMAX_195.py @@ -1,5 +1,25 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import snipe_attack + + +async def hydro_snipe(ctx): + """60 damage, then you may put an Energy attached to the opponent's Active into their hand.""" + await ctx.deal_damage() + active = ctx.opponent_active() + if active is None or ctx.effects_blocked(active): + return + energies = ctx.attached_energies(active) + if not energies: + return + if not await ctx.ask_yes_no( + "Put an Energy attached to your opponent's Active Pokémon into their hand?"): + return + picked = await ctx.choose_cards( + energies, 1, minimum=1, prompt="Choose an Energy card to return to its owner's hand") + if picked: + await ctx.put_in_hand(picked, reveal=False) + card = PokemonCardDef( guid="a6ae21f4-2228-5e61-965b-0c7d9fe711c2", @@ -24,14 +44,14 @@ card = PokemonCardDef( game_text="You may put an Energy attached to your opponent's Active Pok\u00e9mon into their hand.", cost={PokemonTypes.WATER: 1}, damage=60, - effect=unimplemented, + effect=hydro_snipe, ), Attack( title="Max Bullet", game_text="This attack also does 60 damage to 1 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, damage=160, - effect=unimplemented, + effect=snipe_attack(60, also_base=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/InteleonVMAX_50.py b/spirit/game/scripts/cards/SWSH2/InteleonVMAX_50.py index ea7f021..d697ade 100644 --- a/spirit/game/scripts/cards/SWSH2/InteleonVMAX_50.py +++ b/spirit/game/scripts/cards/SWSH2/InteleonVMAX_50.py @@ -1,5 +1,25 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import snipe_attack + + +async def hydro_snipe(ctx): + """60 damage, then you may put an Energy attached to the opponent's Active into their hand.""" + await ctx.deal_damage() + active = ctx.opponent_active() + if active is None or ctx.effects_blocked(active): + return + energies = ctx.attached_energies(active) + if not energies: + return + if not await ctx.ask_yes_no( + "Put an Energy attached to your opponent's Active Pokémon into their hand?"): + return + picked = await ctx.choose_cards( + energies, 1, minimum=1, prompt="Choose an Energy card to return to its owner's hand") + if picked: + await ctx.put_in_hand(picked, reveal=False) + card = PokemonCardDef( guid="c7faaa98-038f-5499-b403-9014ad9db931", @@ -24,14 +44,14 @@ card = PokemonCardDef( game_text="You may put an Energy attached to your opponent's Active Pok\u00e9mon into their hand.", cost={PokemonTypes.WATER: 1}, damage=60, - effect=unimplemented, + effect=hydro_snipe, ), Attack( title="Max Bullet", game_text="This attack also does 60 damage to 1 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, damage=160, - effect=unimplemented, + effect=snipe_attack(60, also_base=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/InteleonV_180.py b/spirit/game/scripts/cards/SWSH2/InteleonV_180.py index 44732a3..10fd937 100644 --- a/spirit/game/scripts/cards/SWSH2/InteleonV_180.py +++ b/spirit/game/scripts/cards/SWSH2/InteleonV_180.py @@ -1,6 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import snipe_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def aqua_report(ctx): + """130 damage, then your opponent reveals their hand.""" + await ctx.deal_damage() + await ctx.reveal_hand(of_player=ctx.opponent_id) + + card = PokemonCardDef( guid="fbac666c-1eae-5815-b8f8-66976ee0d916", key="SWSH2", @@ -22,14 +30,14 @@ card = PokemonCardDef( title="Snipe Shot", game_text="This attack does 40 damage to 1 of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.WATER: 1}, - effect=unimplemented, + effect=snipe_attack(40, pool="any", count=1), ), Attack( title="Aqua Report", game_text="Your opponent reveals their hand.", cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, damage=130, - effect=unimplemented, + effect=aqua_report, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/InteleonV_49.py b/spirit/game/scripts/cards/SWSH2/InteleonV_49.py index a4b1daa..87a9393 100644 --- a/spirit/game/scripts/cards/SWSH2/InteleonV_49.py +++ b/spirit/game/scripts/cards/SWSH2/InteleonV_49.py @@ -1,6 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import snipe_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def aqua_report(ctx): + """130 damage, then your opponent reveals their hand.""" + await ctx.deal_damage() + await ctx.reveal_hand(of_player=ctx.opponent_id) + + card = PokemonCardDef( guid="d0bac268-9afc-539a-bb81-9147795e0cc5", key="SWSH2", @@ -22,14 +30,14 @@ card = PokemonCardDef( title="Snipe Shot", game_text="This attack does 40 damage to 1 of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.WATER: 1}, - effect=unimplemented, + effect=snipe_attack(40, pool="any", count=1), ), Attack( title="Aqua Report", game_text="Your opponent reveals their hand.", cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, damage=130, - effect=unimplemented, + effect=aqua_report, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Lampent_32.py b/spirit/game/scripts/cards/SWSH2/Lampent_32.py index 7427333..934d2b2 100644 --- a/spirit/game/scripts/cards/SWSH2/Lampent_32.py +++ b/spirit/game/scripts/cards/SWSH2/Lampent_32.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented, Triggers +from spirit.game.card_effects.pokemon import top_entry +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.card_effects.support_common import attach_from_discard + + +def _is_fire_energy(card): + # "a Fire Energy card" = a BASIC Fire Energy (Welder precedent) -- Heat + # Fire Energy carries POKEMON_TYPES=[Fire] but is Special. + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_basic_energy_card(card) and PokemonTypes.FIRE.value in types + + +reignite = attach_from_discard(predicate=_is_fire_energy, count=1, target="choice") card = PokemonCardDef( guid="129364d2-7c0c-53a9-b32c-56a94164de81", @@ -21,15 +34,16 @@ card = PokemonCardDef( abilities=[ Ability( title="Top Entry", + trigger=Triggers.ON_TURN_DRAWN, game_text="Once during your turn, if you drew this Pok\u00e9mon from your deck at the beginning of your turn and your Bench isn't full, before you put it into your hand, you may put it onto your Bench.", - effect=unimplemented, + effect=top_entry(), ), Attack( title="Reignite", game_text="Attach a Fire Energy card from your discard pile to 1 of your Pok\u00e9mon.", cost={PokemonTypes.FIRE: 1}, damage=20, - effect=unimplemented, + effect=reignite, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Litwick_31.py b/spirit/game/scripts/cards/SWSH2/Litwick_31.py index 5ed308b..987e0a7 100644 --- a/spirit/game/scripts/cards/SWSH2/Litwick_31.py +++ b/spirit/game/scripts/cards/SWSH2/Litwick_31.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="ff83bfc2-6f69-5fa0-adc8-10c8d91a5b59", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Scorch", game_text="Your opponent's Active Pok\u00e9mon is now Burned.", cost={PokemonTypes.FIRE: 1}, - effect=unimplemented, + effect=condition_attack(SpecialConditions.BURNED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Lombre_8.py b/spirit/game/scripts/cards/SWSH2/Lombre_8.py index 94d099f..6f497b1 100644 --- a/spirit/game/scripts/cards/SWSH2/Lombre_8.py +++ b/spirit/game/scripts/cards/SWSH2/Lombre_8.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented, Triggers +from spirit.game.card_effects.pokemon import top_entry from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="39aa80ee-ae64-5e5a-8766-141f6071aa13", @@ -21,8 +23,9 @@ card = PokemonCardDef( abilities=[ Ability( title="Top Entry", + trigger=Triggers.ON_TURN_DRAWN, game_text="Once during your turn, if you drew this Pok\u00e9mon from your deck at the beginning of your turn and your Bench isn't full, before you put it into your hand, you may put it onto your Bench.", - effect=unimplemented, + effect=top_entry(), ), Attack( title="Fury Swipes", @@ -30,7 +33,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 2}, damage=40, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=3, per_heads=40), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Lotad_7.py b/spirit/game/scripts/cards/SWSH2/Lotad_7.py index b4e27ae..b95458c 100644 --- a/spirit/game/scripts/cards/SWSH2/Lotad_7.py +++ b/spirit/game/scripts/cards/SWSH2/Lotad_7.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import heal_attack card = PokemonCardDef( guid="00b64d17-4a71-5497-8e86-e6e05789c2d9", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Heal 10 damage from this Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, damage=10, - effect=unimplemented, + effect=heal_attack(10, target="self"), ), Attack( title="Ram", diff --git a/spirit/game/scripts/cards/SWSH2/Ludicolo_9.py b/spirit/game/scripts/cards/SWSH2/Ludicolo_9.py index 7be7427..6a25c87 100644 --- a/spirit/game/scripts/cards/SWSH2/Ludicolo_9.py +++ b/spirit/game/scripts/cards/SWSH2/Ludicolo_9.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_prizes_taken +from spirit.game.card_effects.support_common import heal_attack card = PokemonCardDef( guid="d0bb8fe0-bc31-5dc4-b5d2-ac074c6f637d", @@ -25,14 +27,14 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=60, damage_operator="x", - effect=unimplemented, + effect=damage_per(count_prizes_taken("mine"), 60), ), Attack( title="Mega Drain", game_text="Heal 30 damage from this Pok\u00e9mon.", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 2}, damage=120, - effect=unimplemented, + effect=heal_attack(30, target="self"), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Luxio_61.py b/spirit/game/scripts/cards/SWSH2/Luxio_61.py index b47c464..1fc8f78 100644 --- a/spirit/game/scripts/cards/SWSH2/Luxio_61.py +++ b/spirit/game/scripts/cards/SWSH2/Luxio_61.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented, Triggers +from spirit.game.card_effects.pokemon import top_entry from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -21,8 +22,9 @@ card = PokemonCardDef( abilities=[ Ability( title="Top Entry", + trigger=Triggers.ON_TURN_DRAWN, game_text="Once during your turn, if you drew this Pok\u00e9mon from your deck at the beginning of your turn and your Bench isn't full, before you put it into your hand, you may put it onto your Bench.", - effect=unimplemented, + effect=top_entry(), ), Attack( title="Zap Kick", diff --git a/spirit/game/scripts/cards/SWSH2/Luxray_62.py b/spirit/game/scripts/cards/SWSH2/Luxray_62.py index 87955e4..435dacd 100644 --- a/spirit/game/scripts/cards/SWSH2/Luxray_62.py +++ b/spirit/game/scripts/cards/SWSH2/Luxray_62.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if + + +def _evolved_this_turn(ctx) -> bool: + state = ctx.session.turn_state + return state.entered_play_turn.get(ctx.attacker.entity_id) == state.turn_number + card = PokemonCardDef( guid="51e8cc99-599e-5f84-8f56-6faa4e14d357", @@ -25,7 +32,7 @@ card = PokemonCardDef( cost={PokemonTypes.LIGHTNING: 1}, damage=60, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_evolved_this_turn, 100), ), Attack( title="Head Bolt", diff --git a/spirit/game/scripts/cards/SWSH2/Magikarp_39.py b/spirit/game/scripts/cards/SWSH2/Magikarp_39.py index 721ea65..5682713 100644 --- a/spirit/game/scripts/cards/SWSH2/Magikarp_39.py +++ b/spirit/game/scripts/cards/SWSH2/Magikarp_39.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import switch_self_attack card = PokemonCardDef( guid="45ee4f57-eaad-57ab-800b-324d13758afa", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Leap Out", game_text="Switch this Pok\u00e9mon with 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=switch_self_attack(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Magmar_29.py b/spirit/game/scripts/cards/SWSH2/Magmar_29.py index a2af577..ba1a95d 100644 --- a/spirit/game/scripts/cards/SWSH2/Magmar_29.py +++ b/spirit/game/scripts/cards/SWSH2/Magmar_29.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="cafb63b0-53bd-5886-b8cc-563729b1b479", @@ -29,7 +30,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=flip_damage(bonus_per_heads=30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Magmortar_30.py b/spirit/game/scripts/cards/SWSH2/Magmortar_30.py index c47271c..e5b4036 100644 --- a/spirit/game/scripts/cards/SWSH2/Magmortar_30.py +++ b/spirit/game/scripts/cards/SWSH2/Magmortar_30.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.pokemon import is_energy_card + + +async def ground_burn(ctx): + """Each player discards the top card of their deck; +100 damage per + Energy card discarded that way.""" + discarded = list(ctx.deck_top(1, player_id=ctx.player_id)) + \ + list(ctx.deck_top(1, player_id=ctx.opponent_id)) + await ctx.discard_cards(discarded) + energy_count = sum(1 for c in discarded if is_energy_card(c)) + await ctx.deal_damage(80 + 100 * energy_count) + card = PokemonCardDef( guid="2ee86c9a-0c58-542d-8065-41fa646e8ab4", @@ -24,7 +37,7 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Burned.", cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=condition_attack(SpecialConditions.BURNED), ), Attack( title="Ground Burn", @@ -32,7 +45,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=80, damage_operator="+", - effect=unimplemented, + effect=ground_burn, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/MalamarVMAX_122.py b/spirit/game/scripts/cards/SWSH2/MalamarVMAX_122.py index f8c584d..2fdea5d 100644 --- a/spirit/game/scripts/cards/SWSH2/MalamarVMAX_122.py +++ b/spirit/game/scripts/cards/SWSH2/MalamarVMAX_122.py @@ -1,6 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def max_jammer(ctx): + """Opponent reveals their hand. Choose a card there and put it on the + bottom of their deck.""" + await ctx.deal_damage() + hand = ctx.hand(ctx.opponent_id) + if not hand: + return + picks = await ctx.choose_cards( + hand, 1, minimum=1, + prompt="Choose a card from your opponent's hand to put on the bottom of their deck.", + ) + for card in picks: + await ctx.put_on_bottom_of_deck(card) + + card = PokemonCardDef( guid="916d33b5-f534-5982-b05e-1e5087a7e0e7", key="SWSH2", @@ -24,7 +40,7 @@ card = PokemonCardDef( game_text="Your opponent reveals their hand. Choose a card you find there and put it on the bottom of their deck.", cost={PokemonTypes.DARKNESS: 2, PokemonTypes.COLORLESS: 1}, damage=180, - effect=unimplemented, + effect=max_jammer, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/MalamarVMAX_198.py b/spirit/game/scripts/cards/SWSH2/MalamarVMAX_198.py index 696e9d2..533c9a7 100644 --- a/spirit/game/scripts/cards/SWSH2/MalamarVMAX_198.py +++ b/spirit/game/scripts/cards/SWSH2/MalamarVMAX_198.py @@ -1,6 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def max_jammer(ctx): + """Opponent reveals their hand. Choose a card there and put it on the + bottom of their deck.""" + await ctx.deal_damage() + hand = ctx.hand(ctx.opponent_id) + if not hand: + return + picks = await ctx.choose_cards( + hand, 1, minimum=1, + prompt="Choose a card from your opponent's hand to put on the bottom of their deck.", + ) + for card in picks: + await ctx.put_on_bottom_of_deck(card) + + card = PokemonCardDef( guid="be2964ef-c47b-5363-959d-cbf5aaf1c665", key="SWSH2", @@ -24,7 +40,7 @@ card = PokemonCardDef( game_text="Your opponent reveals their hand. Choose a card you find there and put it on the bottom of their deck.", cost={PokemonTypes.DARKNESS: 2, PokemonTypes.COLORLESS: 1}, damage=180, - effect=unimplemented, + effect=max_jammer, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/MalamarV_121.py b/spirit/game/scripts/cards/SWSH2/MalamarV_121.py index b66059e..bfcb5de 100644 --- a/spirit/game/scripts/cards/SWSH2/MalamarV_121.py +++ b/spirit/game/scripts/cards/SWSH2/MalamarV_121.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.support_common import gust_attack card = PokemonCardDef( guid="908bcbb5-587d-5280-b8da-085aae18766f", @@ -22,14 +24,14 @@ card = PokemonCardDef( title="Drag Off", game_text="Switch 1 of your opponent's Benched Pok\u00e9mon with their Active Pok\u00e9mon. This attack does 30 damage to the new Active Pok\u00e9mon.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=gust_attack(damage_to_new_active=30), ), Attack( title="Brain Shake", game_text="Your opponent's Active Pok\u00e9mon is now Confused.", cost={PokemonTypes.DARKNESS: 2, PokemonTypes.COLORLESS: 1}, damage=130, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/MalamarV_186.py b/spirit/game/scripts/cards/SWSH2/MalamarV_186.py index 9615164..b605d06 100644 --- a/spirit/game/scripts/cards/SWSH2/MalamarV_186.py +++ b/spirit/game/scripts/cards/SWSH2/MalamarV_186.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.support_common import gust_attack card = PokemonCardDef( guid="c4fd160f-b8e6-58f5-8dc2-a12ee60523ee", @@ -22,14 +24,14 @@ card = PokemonCardDef( title="Drag Off", game_text="Switch 1 of your opponent's Benched Pok\u00e9mon with their Active Pok\u00e9mon. This attack does 30 damage to the new Active Pok\u00e9mon.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=gust_attack(damage_to_new_active=30), ), Attack( title="Brain Shake", game_text="Your opponent's Active Pok\u00e9mon is now Confused.", cost={PokemonTypes.DARKNESS: 2, PokemonTypes.COLORLESS: 1}, damage=130, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Mandibuzz_120.py b/spirit/game/scripts/cards/SWSH2/Mandibuzz_120.py index 462b1b7..28bb16c 100644 --- a/spirit/game/scripts/cards/SWSH2/Mandibuzz_120.py +++ b/spirit/game/scripts/cards/SWSH2/Mandibuzz_120.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.attacks_common import flip_damage + + +async def blindside(ctx): + """100 damage to 1 of the opponent's Pokemon with any damage counters on it; no W/R on the Bench.""" + candidates = [p for p in ctx.opponent_pokemon_in_play() + if p.get_attribute(AttrID.HP, 0) < ctx.max_hp(p)] + if not candidates: + return + target = await ctx.choose_pokemon( + candidates, "Choose 1 of your opponent's Pokémon that has damage counters on it") + if target is not None: + active = ctx.opponent_active() + await ctx.deal_damage(100, target=target, apply_modifiers=(target is active)) card = PokemonCardDef( guid="4a93665e-625f-5dac-a813-34831aea0c75", @@ -26,13 +40,13 @@ card = PokemonCardDef( cost={PokemonTypes.DARKNESS: 1}, damage=30, damage_operator="x", - effect=unimplemented, + effect=flip_damage(until_tails=True, per_heads=30), ), Attack( title="Blindside", game_text="This attack does 100 damage to 1 of your opponent's Pok\u00e9mon that has any damage counters on it. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.DARKNESS: 2}, - effect=unimplemented, + effect=blindside, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Masquerain_11.py b/spirit/game/scripts/cards/SWSH2/Masquerain_11.py index 2e90c69..935a0d0 100644 --- a/spirit/game/scripts/cards/SWSH2/Masquerain_11.py +++ b/spirit/game/scripts/cards/SWSH2/Masquerain_11.py @@ -1,6 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.support_common import switch_self_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def threatening_pattern(ctx): + await ctx.deal_damage() + if not ctx.effects_blocked(ctx.defender): + ctx.restrict_attachments(ctx.defender) + + card = PokemonCardDef( guid="4426d1d4-82e3-532c-8651-80381702db30", key="SWSH2", @@ -24,14 +32,14 @@ card = PokemonCardDef( game_text="During your opponent's next turn, Energy can't be attached from your opponent's hand to the Defending Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=threatening_pattern, ), Attack( title="U-turn", game_text="Switch this Pok\u00e9mon with 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 2}, damage=40, - effect=unimplemented, + effect=switch_self_attack(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Medicham_98.py b/spirit/game/scripts/cards/SWSH2/Medicham_98.py index 423bbf5..bbd4041 100644 --- a/spirit/game/scripts/cards/SWSH2/Medicham_98.py +++ b/spirit/game/scripts/cards/SWSH2/Medicham_98.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy + + +async def yoga_kick(ctx): + """40 damage, not affected by Weakness or Resistance.""" + await ctx.deal_damage(40, apply_modifiers=False) + card = PokemonCardDef( guid="922383e2-f4b3-5832-aa12-7f6b54f8f1aa", @@ -24,7 +31,7 @@ card = PokemonCardDef( game_text="This attack's damage isn't affected by Weakness or Resistance.", cost={PokemonTypes.FIGHTING: 1}, damage=40, - effect=unimplemented, + effect=yoga_kick, ), Attack( title="Psychic", @@ -32,7 +39,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=60, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("defender"), 20, base=60), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Meditite_97.py b/spirit/game/scripts/cards/SWSH2/Meditite_97.py index 289f6ed..757081f 100644 --- a/spirit/game/scripts/cards/SWSH2/Meditite_97.py +++ b/spirit/game/scripts/cards/SWSH2/Meditite_97.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="1028dfe5-c20b-52b5-beec-df08bcf2a767", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.COLORLESS: 1}, damage=10, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Metapod_2.py b/spirit/game/scripts/cards/SWSH2/Metapod_2.py index 76d7be1..34c9268 100644 --- a/spirit/game/scripts/cards/SWSH2/Metapod_2.py +++ b/spirit/game/scripts/cards/SWSH2/Metapod_2.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.passives import Passive, carrier_pokemon + + +class AdaptiveEvolutionPassive(Passive): + def may_evolve_early(self, pokemon, carrier): + return carrier_pokemon(carrier) is pokemon + card = PokemonCardDef( guid="446f070c-c06e-5359-9657-952ae55f4598", @@ -22,7 +29,7 @@ card = PokemonCardDef( Ability( title="Adaptive Evolution", game_text="This Pok\u00e9mon can evolve during your first turn or the turn you play it.", - effect=unimplemented, + passive=AdaptiveEvolutionPassive(), ), Attack( title="Ram", diff --git a/spirit/game/scripts/cards/SWSH2/Milcery_86.py b/spirit/game/scripts/cards/SWSH2/Milcery_86.py index 860ebac..ece21cc 100644 --- a/spirit/game/scripts/cards/SWSH2/Milcery_86.py +++ b/spirit/game/scripts/cards/SWSH2/Milcery_86.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import heal_targets card = PokemonCardDef( guid="6c76c5de-4e66-5f4a-9cf9-26e0a422dabd", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Aromatherapy", game_text="Heal 10 damage from each of your Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=heal_targets(10, "each_own"), ), Attack( title="Tackle", diff --git a/spirit/game/scripts/cards/SWSH2/MiloticV_179.py b/spirit/game/scripts/cards/SWSH2/MiloticV_179.py index 0353d8a..ce698ec 100644 --- a/spirit/game/scripts/cards/SWSH2/MiloticV_179.py +++ b/spirit/game/scripts/cards/SWSH2/MiloticV_179.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack, damage_per card = PokemonCardDef( guid="0f3371a0-3777-5d8a-b3e8-9bb8fbf01693", @@ -24,14 +25,18 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=10, damage_operator="+", - effect=unimplemented, + effect=damage_per( + lambda ctx: (ctx.opponent_active().get_attribute(AttrID.RETREAT_COST, 0) + if ctx.opponent_active() else 0), + 50, base=10, + ), ), Attack( title="Hypno Splash", game_text="Your opponent's Active Pok\u00e9mon is now Asleep.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 3}, damage=150, - effect=unimplemented, + effect=condition_attack(SpecialConditions.ASLEEP), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/MiloticV_43.py b/spirit/game/scripts/cards/SWSH2/MiloticV_43.py index b3e4076..6ef7a91 100644 --- a/spirit/game/scripts/cards/SWSH2/MiloticV_43.py +++ b/spirit/game/scripts/cards/SWSH2/MiloticV_43.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack, damage_per card = PokemonCardDef( guid="f37a4da2-72c4-58cc-b785-2c07ebd3ffe7", @@ -24,14 +25,18 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=10, damage_operator="+", - effect=unimplemented, + effect=damage_per( + lambda ctx: (ctx.opponent_active().get_attribute(AttrID.RETREAT_COST, 0) + if ctx.opponent_active() else 0), + 50, base=10, + ), ), Attack( title="Hypno Splash", game_text="Your opponent's Active Pok\u00e9mon is now Asleep.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 3}, damage=150, - effect=unimplemented, + effect=condition_attack(SpecialConditions.ASLEEP), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Morgrem_124.py b/spirit/game/scripts/cards/SWSH2/Morgrem_124.py index c8f7065..e44b76b 100644 --- a/spirit/game/scripts/cards/SWSH2/Morgrem_124.py +++ b/spirit/game/scripts/cards/SWSH2/Morgrem_124.py @@ -1,6 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def false_surrender(ctx): + """60. This attack's damage isn't affected by any effects on your + opponent's Active Pokemon.""" + await ctx.deal_damage(ignore_target_effects=True) + + card = PokemonCardDef( guid="701ea824-7e2f-54a5-93e9-89ea9f9bfbcf", key="SWSH2", @@ -26,10 +33,10 @@ card = PokemonCardDef( ), Attack( title="False Surrender", - game_text="This attack's damage isn't affected by any effects on your opponent's Active Pok\u00e9mon.", + game_text="This attack's damage isn't affected by any effects on your opponent's Active Pokémon.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 2}, damage=60, - effect=unimplemented, + effect=false_surrender, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH2/Morpeko_73.py b/spirit/game/scripts/cards/SWSH2/Morpeko_73.py index 5b07272..7fc2f08 100644 --- a/spirit/game/scripts/cards/SWSH2/Morpeko_73.py +++ b/spirit/game/scripts/cards/SWSH2/Morpeko_73.py @@ -1,5 +1,34 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import snipe_attack + + +def _attack_entries(pokemon): + return [e for e in (pokemon.get_attribute(AttrID.PIE_ABILITIES) or []) + if isinstance(e, dict) and e.get("abilityType") == "Attack" + and e.get("abilityID")] + + +async def torment(ctx): + """20. Choose 1 of your opponent's Active Pokemon's attacks. During your + opponent's next turn, that Pokemon can't use that attack.""" + await ctx.deal_damage() + defender = ctx.defender + if defender is None or ctx.effects_blocked(defender): + return + entries = _attack_entries(defender) + if not entries: + return + index = 0 + if len(entries) > 1: + index = await ctx.choose( + "Choose an attack that can't be used during your opponent's next turn", + [e["title"]["id"] for e in entries], + ) + entry = entries[index] + state = ctx.session.turn_state + state.attack_locks[(defender.entity_id, entry["abilityID"])] = state.turn_number + 1 + card = PokemonCardDef( guid="6cdb27ed-9e14-53ae-9a91-b1cc6f5d6e88", @@ -20,17 +49,17 @@ card = PokemonCardDef( abilities=[ Attack( title="Torment", - game_text="Choose 1 of your opponent's Active Pok\u00e9mon's attacks. During your opponent's next turn, that Pok\u00e9mon can't use that attack.", + game_text="Choose 1 of your opponent's Active Pokémon's attacks. During your opponent's next turn, that Pokémon can't use that attack.", cost={PokemonTypes.COLORLESS: 1}, damage=20, - effect=unimplemented, + effect=torment, ), Attack( title="Spark", - game_text="This attack also does 20 damage to 1 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", + game_text="This attack also does 20 damage to 1 of your opponent's Benched Pokémon. (Don't apply Weakness and Resistance for Benched Pokémon.)", cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 1}, damage=50, - effect=unimplemented, + effect=snipe_attack(20, pool="bench", count=1, also_base=True), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH2/Natu_76.py b/spirit/game/scripts/cards/SWSH2/Natu_76.py index e0fa267..a9dddb1 100644 --- a/spirit/game/scripts/cards/SWSH2/Natu_76.py +++ b/spirit/game/scripts/cards/SWSH2/Natu_76.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import draw_attack card = PokemonCardDef( guid="019f4daa-c9dd-5e08-8a34-5b52c978e64d", @@ -23,7 +24,7 @@ card = PokemonCardDef( title="Me First", game_text="Draw a card.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=draw_attack(1), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/NinetalesV_177.py b/spirit/game/scripts/cards/SWSH2/NinetalesV_177.py index 383cccb..d9a8327 100644 --- a/spirit/game/scripts/cards/SWSH2/NinetalesV_177.py +++ b/spirit/game/scripts/cards/SWSH2/NinetalesV_177.py @@ -1,5 +1,24 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, def_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import self_energy_discard_attack + + +async def nine_tailed_shapeshifter(ctx): + """Choose 1 of the opponent's Active Pokemon's attacks and use it.""" + defender = ctx.defender + if defender is None: + return + definition = def_for(defender.archetype_id) + attacks = [a for a in getattr(definition, "abilities", []) if isinstance(a, Attack)] + if not attacks: + return + candidates = [(defender, atk) for atk in attacks] + picked = await ctx.choose_attack_to_copy(candidates, "Choose an attack to copy") + if picked is None: + return + _, chosen = picked + await ctx.use_attack(chosen) + card = PokemonCardDef( guid="0be33e94-0136-536a-8d7f-64b883d11f7c", @@ -20,16 +39,16 @@ card = PokemonCardDef( abilities=[ Attack( title="Nine-Tailed Shapeshifter", - game_text="Choose 1 of your opponent's Active Pok\u00e9mon's attacks and use it as this attack.", + game_text="Choose 1 of your opponent's Active Pokémon's attacks and use it as this attack.", cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + effect=nine_tailed_shapeshifter, ), Attack( title="Flamethrower", - game_text="Discard an Energy from this Pok\u00e9mon.", + game_text="Discard an Energy from this Pokémon.", cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 3}, damage=180, - effect=unimplemented, + effect=self_energy_discard_attack(count=1), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH2/NinetalesV_26.py b/spirit/game/scripts/cards/SWSH2/NinetalesV_26.py index b8b6966..c5a9162 100644 --- a/spirit/game/scripts/cards/SWSH2/NinetalesV_26.py +++ b/spirit/game/scripts/cards/SWSH2/NinetalesV_26.py @@ -1,5 +1,24 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, def_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import self_energy_discard_attack + + +async def nine_tailed_shapeshifter(ctx): + """Choose 1 of the opponent's Active Pokemon's attacks and use it.""" + defender = ctx.defender + if defender is None: + return + definition = def_for(defender.archetype_id) + attacks = [a for a in getattr(definition, "abilities", []) if isinstance(a, Attack)] + if not attacks: + return + candidates = [(defender, atk) for atk in attacks] + picked = await ctx.choose_attack_to_copy(candidates, "Choose an attack to copy") + if picked is None: + return + _, chosen = picked + await ctx.use_attack(chosen) + card = PokemonCardDef( guid="bb2c0ebf-f849-5ed4-9f57-de5eb87cfa8c", @@ -20,16 +39,16 @@ card = PokemonCardDef( abilities=[ Attack( title="Nine-Tailed Shapeshifter", - game_text="Choose 1 of your opponent's Active Pok\u00e9mon's attacks and use it as this attack.", + game_text="Choose 1 of your opponent's Active Pokémon's attacks and use it as this attack.", cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + effect=nine_tailed_shapeshifter, ), Attack( title="Flamethrower", - game_text="Discard an Energy from this Pok\u00e9mon.", + game_text="Discard an Energy from this Pokémon.", cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 3}, damage=180, - effect=unimplemented, + effect=self_energy_discard_attack(count=1), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH2/Ninetales_25.py b/spirit/game/scripts/cards/SWSH2/Ninetales_25.py index 11adce0..a010f8b 100644 --- a/spirit/game/scripts/cards/SWSH2/Ninetales_25.py +++ b/spirit/game/scripts/cards/SWSH2/Ninetales_25.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import bonus_if, condition_attack + + +def _defender_has_condition(ctx) -> bool: + defender = ctx.defender + return bool(defender and defender.get_attribute(AttrID.SPECIAL_CONDITIONS)) + card = PokemonCardDef( guid="7a11ae33-16b6-5345-a421-7c8d72dd4a02", @@ -25,14 +32,14 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 1}, damage=30, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_defender_has_condition, 90, base=30), ), Attack( title="Flickering Flames", game_text="Your opponent's Active Pok\u00e9mon is now Asleep.", cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 2}, damage=90, - effect=unimplemented, + effect=condition_attack(SpecialConditions.ASLEEP), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Nugget_162.py b/spirit/game/scripts/cards/SWSH2/Nugget_162.py index 72c99a0..a63a6d0 100644 --- a/spirit/game/scripts/cards/SWSH2/Nugget_162.py +++ b/spirit/game/scripts/cards/SWSH2/Nugget_162.py @@ -1,6 +1,27 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef from spirit.game.attributes import Rarities + +def _turn_drawn(board, player_id, card): + """Playable only if drawn as this turn's draw AND before any other main + action ("before you put it into your hand" -- immediate play; landing in + hand first is the engine's accepted approximation).""" + state = getattr(board, "turn_state", None) + if state is None or card.entity_id not in state.turn_draw_entity_ids: + return False + return ( + not state.supporter_played and not state.energy_attached + and not state.retreated and not state.trainers_played + and not state.attacks_used and not state.used_abilities + and state.turn_number not in state.entered_play_turn.values() + ) + + +async def _nugget(ctx): + """Draw 3 cards.""" + await ctx.draw_cards(3) + + card = ItemCardDef( guid="6479deb7-99e6-551e-a271-8bef6b9a7802", key="SWSH2", @@ -11,5 +32,6 @@ card = ItemCardDef( collector_number=162, set_code="SWSH2", rarity=Rarities.Uncommon, - effect=unimplemented + condition=_turn_drawn, + effect=_nugget, ) diff --git a/spirit/game/scripts/cards/SWSH2/Oleana_163.py b/spirit/game/scripts/cards/SWSH2/Oleana_163.py index ac11abf..3cb6509 100644 --- a/spirit/game/scripts/cards/SWSH2/Oleana_163.py +++ b/spirit/game/scripts/cards/SWSH2/Oleana_163.py @@ -1,5 +1,28 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.support_common import requires_hand +from spirit.game.session.effects import is_trainer_card + + +async def oleana_effect(ctx): + """Discard 2 other cards, then your opponent reveals their hand and you + put a Trainer card found there on the bottom of their deck.""" + discarded = await ctx.discard_from_hand( + 2, prompt="Discard 2 cards to play Oleana", + ) + if len(discarded) < 2: + return + opp_hand = await ctx.reveal_hand(of_player=ctx.opponent_id) + trainers = [c for c in opp_hand if is_trainer_card(c)] + if not trainers: + return + picks = await ctx.choose_cards( + trainers, 1, minimum=1, display_cards=opp_hand, + prompt="Choose a Trainer card to put on the bottom of your opponent's deck", + ) + for card in picks: + await ctx.put_on_bottom_of_deck(card) + card = SupporterCardDef( guid="32dd6bc5-df42-5365-8c29-8157eb2495b9", @@ -11,5 +34,6 @@ card = SupporterCardDef( collector_number=163, set_code="SWSH2", rarity=Rarities.Uncommon, - effect=unimplemented + effect=oleana_effect, + condition=requires_hand(n=2), ) diff --git a/spirit/game/scripts/cards/SWSH2/Oleana_191.py b/spirit/game/scripts/cards/SWSH2/Oleana_191.py index a7ad06d..69fa39c 100644 --- a/spirit/game/scripts/cards/SWSH2/Oleana_191.py +++ b/spirit/game/scripts/cards/SWSH2/Oleana_191.py @@ -1,5 +1,28 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.support_common import requires_hand +from spirit.game.session.effects import is_trainer_card + + +async def oleana_effect(ctx): + """Discard 2 other cards, then your opponent reveals their hand and you + put a Trainer card found there on the bottom of their deck.""" + discarded = await ctx.discard_from_hand( + 2, prompt="Discard 2 cards to play Oleana", + ) + if len(discarded) < 2: + return + opp_hand = await ctx.reveal_hand(of_player=ctx.opponent_id) + trainers = [c for c in opp_hand if is_trainer_card(c)] + if not trainers: + return + picks = await ctx.choose_cards( + trainers, 1, minimum=1, display_cards=opp_hand, + prompt="Choose a Trainer card to put on the bottom of your opponent's deck", + ) + for card in picks: + await ctx.put_on_bottom_of_deck(card) + card = SupporterCardDef( guid="dec7eff6-436e-5b94-aebf-75e6bd9fb385", @@ -11,5 +34,6 @@ card = SupporterCardDef( collector_number=191, set_code="SWSH2", rarity=Rarities.RareUltra, - effect=unimplemented + effect=oleana_effect, + condition=requires_hand(n=2), ) diff --git a/spirit/game/scripts/cards/SWSH2/Oleana_202.py b/spirit/game/scripts/cards/SWSH2/Oleana_202.py index 48657df..7b0aa04 100644 --- a/spirit/game/scripts/cards/SWSH2/Oleana_202.py +++ b/spirit/game/scripts/cards/SWSH2/Oleana_202.py @@ -1,5 +1,28 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.support_common import requires_hand +from spirit.game.session.effects import is_trainer_card + + +async def oleana_effect(ctx): + """Discard 2 other cards, then your opponent reveals their hand and you + put a Trainer card found there on the bottom of their deck.""" + discarded = await ctx.discard_from_hand( + 2, prompt="Discard 2 cards to play Oleana", + ) + if len(discarded) < 2: + return + opp_hand = await ctx.reveal_hand(of_player=ctx.opponent_id) + trainers = [c for c in opp_hand if is_trainer_card(c)] + if not trainers: + return + picks = await ctx.choose_cards( + trainers, 1, minimum=1, display_cards=opp_hand, + prompt="Choose a Trainer card to put on the bottom of your opponent's deck", + ) + for card in picks: + await ctx.put_on_bottom_of_deck(card) + card = SupporterCardDef( guid="afebe747-65b2-5600-acae-30e507332bb1", @@ -11,5 +34,6 @@ card = SupporterCardDef( collector_number=202, set_code="SWSH2", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=oleana_effect, + condition=requires_hand(n=2), ) diff --git a/spirit/game/scripts/cards/SWSH2/Palossand_82.py b/spirit/game/scripts/cards/SWSH2/Palossand_82.py index c951da7..b2ed42b 100644 --- a/spirit/game/scripts/cards/SWSH2/Palossand_82.py +++ b/spirit/game/scripts/cards/SWSH2/Palossand_82.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, def_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import heal_attack + + +def _has_cursed_shovel(ctx): + for tool, pokemon in ctx.tools_in_play(): + if pokemon is ctx.attacker: + definition = def_for(tool.archetype_id) + if getattr(definition, "display_name", None) == "Cursed Shovel": + return True + return False + + +async def sand_sink(ctx): + count = 3 if _has_cursed_shovel(ctx) else 1 + await ctx.discard_cards(ctx.deck_top(count, player_id=ctx.opponent_id)) + card = PokemonCardDef( guid="8efb553b-cfee-5681-8e21-dfccb14ac5fe", @@ -24,14 +40,14 @@ card = PokemonCardDef( title="Sand Sink", game_text="Discard the top card of your opponent's deck. If this Pok\u00e9mon has a Cursed Shovel attached, discard 2 more cards from the top of your opponent's deck.", cost={PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + effect=sand_sink, ), Attack( title="Super Absorption", game_text="Heal 30 damage from this Pok\u00e9mon.", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 2}, damage=90, - effect=unimplemented, + effect=heal_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Palpitoad_45.py b/spirit/game/scripts/cards/SWSH2/Palpitoad_45.py index 0826ceb..96f73d5 100644 --- a/spirit/game/scripts/cards/SWSH2/Palpitoad_45.py +++ b/spirit/game/scripts/cards/SWSH2/Palpitoad_45.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="1c444c46-93c9-54e9-b54a-e0d9b5e030a9", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Confused.", cost={PokemonTypes.COLORLESS: 2}, damage=30, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Pelipper_42.py b/spirit/game/scripts/cards/SWSH2/Pelipper_42.py index 7877779..56063e7 100644 --- a/spirit/game/scripts/cards/SWSH2/Pelipper_42.py +++ b/spirit/game/scripts/cards/SWSH2/Pelipper_42.py @@ -1,6 +1,23 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def _power_cyclone(ctx): + await ctx.deal_damage() + bench = ctx.my_bench() + energies = ctx.attached_energies(ctx.attacker) + if not bench or not energies: + return + picked = await ctx.choose_cards(energies, 1, minimum=1, prompt="Choose an Energy to move") + if not picked: + return + target = await ctx.choose_pokemon( + bench, "Choose the Benched Pokémon to move the Energy to" + ) + if target is not None: + await ctx.move_energy(picked[0], target) + + card = PokemonCardDef( guid="d9640c69-9b04-5427-8102-3f999ef6c740", key="SWSH2", @@ -30,7 +47,7 @@ card = PokemonCardDef( game_text="Move an Energy from this Pok\u00e9mon to 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=110, - effect=unimplemented, + effect=_power_cyclone, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Phantump_14.py b/spirit/game/scripts/cards/SWSH2/Phantump_14.py index d2568ac..94e466d 100644 --- a/spirit/game/scripts/cards/SWSH2/Phantump_14.py +++ b/spirit/game/scripts/cards/SWSH2/Phantump_14.py @@ -1,5 +1,23 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_basic_pokemon +from spirit.game.session.constants import BENCH_CAPACITY + + +async def dark_guidance(ctx): + """Put a Basic Pokemon from your discard pile onto your Bench.""" + if len(ctx.my_bench()) >= BENCH_CAPACITY: + return + candidates = [c for c in ctx.discard_pile() if is_basic_pokemon(c)] + if not candidates: + return + picks = await ctx.choose_cards( + candidates, 1, minimum=1, + prompt="Choose a Basic Pokémon from your discard pile to put onto your Bench.", + ) + for card in picks: + await ctx.bench_pokemon(card) + card = PokemonCardDef( guid="2962aa46-1df3-55e3-91be-1b88f5f31e72", @@ -22,7 +40,7 @@ card = PokemonCardDef( title="Dark Guidance", game_text="Put a Basic Pok\u00e9mon from your discard pile onto your Bench.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=dark_guidance, ), Attack( title="Seed Bomb", diff --git a/spirit/game/scripts/cards/SWSH2/Pidove_143.py b/spirit/game/scripts/cards/SWSH2/Pidove_143.py index 580acee..6243c0d 100644 --- a/spirit/game/scripts/cards/SWSH2/Pidove_143.py +++ b/spirit/game/scripts/cards/SWSH2/Pidove_143.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_pokemon_card +from spirit.game.card_effects.support_common import search_to_hand + + +def _has_fighting_resistance(card): + return ( + is_pokemon_card(card) + and card.get_attribute(AttrID.RESISTANCE_TYPES) == PokemonTypes.FIGHTING.value + ) + card = PokemonCardDef( guid="ae918bae-d738-5937-bff0-493a68cd9f71", @@ -21,9 +31,10 @@ card = PokemonCardDef( abilities=[ Attack( title="Chirp", - game_text="Search your deck for up to 2 Pok\u00e9mon with Fighting Resistance, reveal them, and put them into your hand. Then, shuffle your deck.", + game_text="Search your deck for up to 2 Pokémon with Fighting Resistance, reveal them, and put them into your hand. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_to_hand(_has_fighting_resistance, count=2, minimum=0, reveal=True, + prompt="Choose up to 2 Pokémon with Fighting Resistance."), ), Attack( title="Razor Wing", @@ -31,4 +42,4 @@ card = PokemonCardDef( damage=20, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH2/PincurchinV_72.py b/spirit/game/scripts/cards/SWSH2/PincurchinV_72.py index 2bc2d86..c84cb49 100644 --- a/spirit/game/scripts/cards/SWSH2/PincurchinV_72.py +++ b/spirit/game/scripts/cards/SWSH2/PincurchinV_72.py @@ -1,5 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import in_active_spot + + +async def counterattack_kerzap(ctx): + """If Active and damaged by an attack: flip 3, 3 damage counters per heads on the attacker.""" + if not in_active_spot(ctx.board, ctx.player_id, ctx.source): + return + attacker = ctx.damaged_by + if attacker is None: + return + results = await ctx.flip_coins(3, ctx.ability.title) + heads = sum(results) + if heads: + await ctx.deal_damage(heads * 30, target=attacker, apply_modifiers=False, + as_counters=True) card = PokemonCardDef( guid="c9f867cb-26f1-5185-8507-ba06380a14f0", @@ -21,7 +36,8 @@ card = PokemonCardDef( Ability( title="Counterattack Kerzap", game_text="If this Pok\u00e9mon is in the Active Spot and is damaged by an opponent's attack (even if it is Knocked Out), flip 3 coins. For each heads, put 3 damage counters on the Attacking Pok\u00e9mon.", - effect=unimplemented, + trigger=Triggers.ON_DAMAGED_BY_ATTACK, + effect=counterattack_kerzap, ), Attack( title="Sparking Strike", diff --git a/spirit/game/scripts/cards/SWSH2/PokBall_164.py b/spirit/game/scripts/cards/SWSH2/PokBall_164.py index 348f0c4..b23dff7 100644 --- a/spirit/game/scripts/cards/SWSH2/PokBall_164.py +++ b/spirit/game/scripts/cards/SWSH2/PokBall_164.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef from spirit.game.attributes import Rarities +from spirit.game.session.effects import is_pokemon_card + + +async def poke_ball(ctx): + """Flip a coin. If heads, search the deck for a Pokemon, reveal it, and + put it into your hand. Then, shuffle your deck.""" + if (await ctx.flip_coins(1, "Poké Ball"))[0]: + picks = await ctx.search_deck( + is_pokemon_card, count=1, minimum=0, + prompt="Choose a Pokémon to put into your hand.", + ) + await ctx.put_in_hand(picks, reveal=True) + await ctx.shuffle_deck() + card = ItemCardDef( guid="13c60c15-aa55-5c83-a82c-c5e599bb1ec2", @@ -11,5 +25,5 @@ card = ItemCardDef( collector_number=164, set_code="SWSH2", rarity=Rarities.Uncommon, - effect=unimplemented + effect=poke_ball ) diff --git a/spirit/game/scripts/cards/SWSH2/Probopass_131.py b/spirit/game/scripts/cards/SWSH2/Probopass_131.py index 1047908..8067e51 100644 --- a/spirit/game/scripts/cards/SWSH2/Probopass_131.py +++ b/spirit/game/scripts/cards/SWSH2/Probopass_131.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID + + +async def gravitational_drop(ctx): + """10, +30 for each Colorless in the opponent's Active's Retreat Cost.""" + active = ctx.opponent_active() + retreat = int(active.get_attribute(AttrID.RETREAT_COST) or 0) if active else 0 + await ctx.deal_damage(10 + 30 * retreat) + card = PokemonCardDef( guid="c77ee73f-9c0d-55c6-b019-b898698df88f", @@ -26,7 +34,7 @@ card = PokemonCardDef( cost={PokemonTypes.METAL: 1}, damage=10, damage_operator="+", - effect=unimplemented, + effect=gravitational_drop, ), Attack( title="Heavy Impact", diff --git a/spirit/game/scripts/cards/SWSH2/RillaboomVMAX_18.py b/spirit/game/scripts/cards/SWSH2/RillaboomVMAX_18.py index 0dfa701..84b2fe5 100644 --- a/spirit/game/scripts/cards/SWSH2/RillaboomVMAX_18.py +++ b/spirit/game/scripts/cards/SWSH2/RillaboomVMAX_18.py @@ -1,6 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.pokemon import energy_provides_type +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def max_beating(ctx): + """Discard up to 3 Grass Energy from this Pokemon; +50 damage per card discarded.""" + picks = await ctx.discard_energy_from( + ctx.attacker, 3, minimum=0, + predicate=lambda c: energy_provides_type(c, PokemonTypes.GRASS.value), + prompt="Choose up to 3 Grass Energy to discard from this Pokémon.", + ) + await ctx.deal_damage(130 + 50 * len(picks)) + + card = PokemonCardDef( guid="f2d5f72b-3b42-5e7d-8876-d3c18c4f3605", key="SWSH2", @@ -30,7 +42,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 3, PokemonTypes.COLORLESS: 1}, damage=130, damage_operator="+", - effect=unimplemented, + effect=max_beating, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/RillaboomVMAX_193.py b/spirit/game/scripts/cards/SWSH2/RillaboomVMAX_193.py index b8947f0..49dffab 100644 --- a/spirit/game/scripts/cards/SWSH2/RillaboomVMAX_193.py +++ b/spirit/game/scripts/cards/SWSH2/RillaboomVMAX_193.py @@ -1,6 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.pokemon import energy_provides_type +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def max_beating(ctx): + """Discard up to 3 Grass Energy from this Pokemon; +50 damage per card discarded.""" + picks = await ctx.discard_energy_from( + ctx.attacker, 3, minimum=0, + predicate=lambda c: energy_provides_type(c, PokemonTypes.GRASS.value), + prompt="Choose up to 3 Grass Energy to discard from this Pokémon.", + ) + await ctx.deal_damage(130 + 50 * len(picks)) + + card = PokemonCardDef( guid="e4d729c4-b1b3-58b5-9b65-968cbbb1ac93", key="SWSH2", @@ -30,7 +42,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 3, PokemonTypes.COLORLESS: 1}, damage=130, damage_operator="+", - effect=unimplemented, + effect=max_beating, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/RillaboomV_17.py b/spirit/game/scripts/cards/SWSH2/RillaboomV_17.py index 613f073..54816ec 100644 --- a/spirit/game/scripts/cards/SWSH2/RillaboomV_17.py +++ b/spirit/game/scripts/cards/SWSH2/RillaboomV_17.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.session.effects import is_basic_pokemon +from spirit.game.card_effects.attacks_common import recoil_attack +from spirit.game.card_effects.support_common import search_to_bench + + +def _is_basic_grass_pokemon(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_basic_pokemon(card) and PokemonTypes.GRASS.value in types + card = PokemonCardDef( guid="c7b64717-c408-5076-b063-da0602af7dcb", @@ -22,14 +31,14 @@ card = PokemonCardDef( title="Forest Feast", game_text="Search your deck for up to 2 Basic Grass Pok\u00e9mon and put them onto your Bench. Then, shuffle your deck.", cost={PokemonTypes.GRASS: 1}, - effect=unimplemented, + effect=search_to_bench(predicate=_is_basic_grass_pokemon, count=2), ), Attack( title="Wood Hammer", game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.GRASS: 3, PokemonTypes.COLORLESS: 1}, damage=220, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/RillaboomV_175.py b/spirit/game/scripts/cards/SWSH2/RillaboomV_175.py index fe46750..2f59814 100644 --- a/spirit/game/scripts/cards/SWSH2/RillaboomV_175.py +++ b/spirit/game/scripts/cards/SWSH2/RillaboomV_175.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.session.effects import is_basic_pokemon +from spirit.game.card_effects.attacks_common import recoil_attack +from spirit.game.card_effects.support_common import search_to_bench + + +def _is_basic_grass_pokemon(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_basic_pokemon(card) and PokemonTypes.GRASS.value in types + card = PokemonCardDef( guid="ae6d1c5c-cdc5-5a71-943d-e576481d71c7", @@ -22,14 +31,14 @@ card = PokemonCardDef( title="Forest Feast", game_text="Search your deck for up to 2 Basic Grass Pok\u00e9mon and put them onto your Bench. Then, shuffle your deck.", cost={PokemonTypes.GRASS: 1}, - effect=unimplemented, + effect=search_to_bench(predicate=_is_basic_grass_pokemon, count=2), ), Attack( title="Wood Hammer", game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.GRASS: 3, PokemonTypes.COLORLESS: 1}, damage=220, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/SandacondaV_108.py b/spirit/game/scripts/cards/SWSH2/SandacondaV_108.py index ba94a86..faa8e1f 100644 --- a/spirit/game/scripts/cards/SWSH2/SandacondaV_108.py +++ b/spirit/game/scripts/cards/SWSH2/SandacondaV_108.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import self_energy_discard_attack +from spirit.game.card_effects.support_common import attach_from_discard +from spirit.game.card_effects.pokemon import is_energy_card + + +def _is_fighting_energy(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_energy_card(card) and PokemonTypes.FIGHTING.value in types + card = PokemonCardDef( guid="b70ef18d-cdbd-5c15-aeef-3b81fdbaa922", @@ -23,14 +32,16 @@ card = PokemonCardDef( game_text="Attach a Fighting Energy card from your discard pile to this Pok\u00e9mon.", cost={PokemonTypes.FIGHTING: 1}, damage=30, - effect=unimplemented, + effect=attach_from_discard( + predicate=_is_fighting_energy, count=1, target="self", + prompt="Choose a Fighting Energy card to attach"), ), Attack( title="Sand Breath", game_text="Discard 2 Energy from this Pok\u00e9mon.", cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=220, - effect=unimplemented, + effect=self_energy_discard_attack(count=2), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/SandacondaV_184.py b/spirit/game/scripts/cards/SWSH2/SandacondaV_184.py index 110c58f..ae07d50 100644 --- a/spirit/game/scripts/cards/SWSH2/SandacondaV_184.py +++ b/spirit/game/scripts/cards/SWSH2/SandacondaV_184.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import self_energy_discard_attack +from spirit.game.card_effects.support_common import attach_from_discard +from spirit.game.card_effects.pokemon import is_energy_card + + +def _is_fighting_energy(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_energy_card(card) and PokemonTypes.FIGHTING.value in types + card = PokemonCardDef( guid="90f22b55-270b-53f4-8f60-26020942ee66", @@ -23,14 +32,16 @@ card = PokemonCardDef( game_text="Attach a Fighting Energy card from your discard pile to this Pok\u00e9mon.", cost={PokemonTypes.FIGHTING: 1}, damage=30, - effect=unimplemented, + effect=attach_from_discard( + predicate=_is_fighting_energy, count=1, target="self", + prompt="Choose a Fighting Energy card to attach"), ), Attack( title="Sand Breath", game_text="Discard 2 Energy from this Pok\u00e9mon.", cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=220, - effect=unimplemented, + effect=self_energy_discard_attack(count=2), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Sandygast_81.py b/spirit/game/scripts/cards/SWSH2/Sandygast_81.py index 19f93d3..4d4f57e 100644 --- a/spirit/game/scripts/cards/SWSH2/Sandygast_81.py +++ b/spirit/game/scripts/cards/SWSH2/Sandygast_81.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import place_counters card = PokemonCardDef( guid="c7d4930b-3cf6-523b-bf3d-1d798f444bfb", @@ -23,7 +24,7 @@ card = PokemonCardDef( title="Sneaky Placement", game_text="Put 1 damage counter on 1 of your opponent's Pok\u00e9mon.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=place_counters(1, "choose_any_opponent"), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Scizor_128.py b/spirit/game/scripts/cards/SWSH2/Scizor_128.py index 9ec3c78..fb7c3b7 100644 --- a/spirit/game/scripts/cards/SWSH2/Scizor_128.py +++ b/spirit/game/scripts/cards/SWSH2/Scizor_128.py @@ -1,6 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import bonus_if +from spirit.game.card_effects.passives_common import protect_next_turn +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +def _evolved_this_turn(ctx): + state = ctx.session.turn_state + return state.entered_play_turn.get(ctx.attacker.entity_id) == state.turn_number + + card = PokemonCardDef( guid="356ccf4d-9480-5adc-94d5-126f9af1e98b", key="SWSH2", @@ -26,14 +34,14 @@ card = PokemonCardDef( cost={PokemonTypes.METAL: 1}, damage=30, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_evolved_this_turn, 90), ), Attack( title="Guard Claw", game_text="During your opponent's next turn, this Pok\u00e9mon takes 30 less damage from attacks (after applying Weakness and Resistance).", cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 2}, damage=90, - effect=unimplemented, + effect=protect_next_turn(reduce=30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/ScoopUpNet_207.py b/spirit/game/scripts/cards/SWSH2/ScoopUpNet_207.py index dfee6f7..d3a7834 100644 --- a/spirit/game/scripts/cards/SWSH2/ScoopUpNet_207.py +++ b/spirit/game/scripts/cards/SWSH2/ScoopUpNet_207.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import scoop_up_net card = ItemCardDef( guid="3046823f-6e39-5c6a-a447-692894877a01", @@ -11,5 +12,5 @@ card = ItemCardDef( collector_number=207, set_code="SWSH2", rarity=Rarities.RareSecret, - effect=unimplemented + effect=scoop_up_net ) diff --git a/spirit/game/scripts/cards/SWSH2/Scyther_4.py b/spirit/game/scripts/cards/SWSH2/Scyther_4.py index b186369..ab95f5c 100644 --- a/spirit/game/scripts/cards/SWSH2/Scyther_4.py +++ b/spirit/game/scripts/cards/SWSH2/Scyther_4.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.passives_common import boost_own_next_turn +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Swords Dance", game_text="During your next turn, this Pok\u00e9mon's Blinding Scythe attack does 70 more damage (before applying Weakness and Resistance).", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=boost_own_next_turn(70, attack_title="Blinding Scythe"), ), Attack( title="Blinding Scythe", diff --git a/spirit/game/scripts/cards/SWSH2/Seismitoad_46.py b/spirit/game/scripts/cards/SWSH2/Seismitoad_46.py index 66d3089..d3bbd5a 100644 --- a/spirit/game/scripts/cards/SWSH2/Seismitoad_46.py +++ b/spirit/game/scripts/cards/SWSH2/Seismitoad_46.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack, condition_bonus_attack card = PokemonCardDef( guid="28bbd7eb-498c-5bf1-899a-381dfaba3e88", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Confused.", cost={PokemonTypes.WATER: 1}, damage=30, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED), ), Attack( title="Resonance", @@ -32,7 +33,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 3}, damage=120, damage_operator="+", - effect=unimplemented, + effect=condition_bonus_attack(120, SpecialConditions.CONFUSED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Shinx_60.py b/spirit/game/scripts/cards/SWSH2/Shinx_60.py index d16434a..9a12ecc 100644 --- a/spirit/game/scripts/cards/SWSH2/Shinx_60.py +++ b/spirit/game/scripts/cards/SWSH2/Shinx_60.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, opponent_prizes_taken_at_least card = PokemonCardDef( guid="3a350e08-665c-52ec-b887-cc6a4f273ab6", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.LIGHTNING: 1}, damage=10, damage_operator="+", - effect=unimplemented, + effect=bonus_if(opponent_prizes_taken_at_least(3), 50), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Shuckle_5.py b/spirit/game/scripts/cards/SWSH2/Shuckle_5.py index 23fd824..bf26f21 100644 --- a/spirit/game/scripts/cards/SWSH2/Shuckle_5.py +++ b/spirit/game/scripts/cards/SWSH2/Shuckle_5.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.trainers import is_basic_energy_card + + +async def berry_picking(ctx): + energy = [c for c in ctx.discard_pile() if is_basic_energy_card(c)] + if not energy: + return + picks = await ctx.choose_cards( + energy, 5, minimum=1, + prompt="Choose up to 5 basic Energy cards to shuffle into your deck.", + ) + if picks: + await ctx.shuffle_into_deck(picks) card = PokemonCardDef( guid="a6bf23dd-c9fa-5867-bd81-3a1639267862", @@ -22,14 +36,14 @@ card = PokemonCardDef( title="Berry Picking", game_text="Shuffle up to 5 basic Energy cards from your discard pile into your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=berry_picking, ), Attack( title="Bind", game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 2}, damage=50, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Sigilyph_80.py b/spirit/game/scripts/cards/SWSH2/Sigilyph_80.py index ab84f0d..fd0bcc1 100644 --- a/spirit/game/scripts/cards/SWSH2/Sigilyph_80.py +++ b/spirit/game/scripts/cards/SWSH2/Sigilyph_80.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, damage_counters_on +from spirit.game.card_effects.pokemon import in_active_spot + + +async def counterattack(ctx): + """If Active and damaged by an attack (even if KO'd): 3 counters on the attacker.""" + if not in_active_spot(ctx.board, ctx.player_id, ctx.source): + return + attacker = ctx.damaged_by + if attacker is None: + return + await ctx.deal_damage(30, target=attacker, apply_modifiers=False, as_counters=True) + card = PokemonCardDef( guid="79afddac-1ed7-5e33-a8d9-4182d23d69f8", @@ -22,7 +35,8 @@ card = PokemonCardDef( Ability( title="Counterattack", game_text="If this Pok\u00e9mon is your Active Pok\u00e9mon and is damaged by an opponent's attack (even if this Pok\u00e9mon is Knocked Out), put 3 damage counters on the Attacking Pok\u00e9mon.", - effect=unimplemented, + trigger=Triggers.ON_DAMAGED_BY_ATTACK, + effect=counterattack, ), Attack( title="Psychic Assault", @@ -30,7 +44,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=30, damage_operator="+", - effect=unimplemented, + effect=damage_per(damage_counters_on("defender"), 10, base=30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Skuntank_115.py b/spirit/game/scripts/cards/SWSH2/Skuntank_115.py index 90c6a05..9015f32 100644 --- a/spirit/game/scripts/cards/SWSH2/Skuntank_115.py +++ b/spirit/game/scripts/cards/SWSH2/Skuntank_115.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="c73d66af-4579-53c9-9ab9-28d002625091", @@ -29,7 +30,7 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Poisoned. During your opponent's next turn, the Defending Pok\u00e9mon can't retreat.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 2}, damage=80, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED, no_retreat=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Skyla_166.py b/spirit/game/scripts/cards/SWSH2/Skyla_166.py index 0e375df..8d86bbf 100644 --- a/spirit/game/scripts/cards/SWSH2/Skyla_166.py +++ b/spirit/game/scripts/cards/SWSH2/Skyla_166.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.session.effects import is_trainer_card +from spirit.game.card_effects.support_common import search_to_hand card = SupporterCardDef( guid="0bd34f56-2959-5a7f-88aa-ba000a36e9b4", @@ -11,5 +13,5 @@ card = SupporterCardDef( collector_number=166, set_code="SWSH2", rarity=Rarities.Uncommon, - effect=unimplemented + effect=search_to_hand(is_trainer_card, count=1, reveal=True, prompt="Choose a Trainer card."), ) diff --git a/spirit/game/scripts/cards/SWSH2/Snorlax_141.py b/spirit/game/scripts/cards/SWSH2/Snorlax_141.py index 91f7ad9..e5a2d8f 100644 --- a/spirit/game/scripts/cards/SWSH2/Snorlax_141.py +++ b/spirit/game/scripts/cards/SWSH2/Snorlax_141.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.support_common import draw_attack card = PokemonCardDef( guid="d3593fb7-d92f-5421-932a-a0e6eef6b262", @@ -22,14 +24,14 @@ card = PokemonCardDef( title="Collect", game_text="Draw 2 cards.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=draw_attack(2), ), Attack( title="Collapse", game_text="This Pok\u00e9mon is now Asleep.", cost={PokemonTypes.COLORLESS: 3}, damage=120, - effect=unimplemented, + effect=condition_attack(self_conditions=(SpecialConditions.ASLEEP,)), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Spiritomb_116.py b/spirit/game/scripts/cards/SWSH2/Spiritomb_116.py index 78bbd21..8fb4737 100644 --- a/spirit/game/scripts/cards/SWSH2/Spiritomb_116.py +++ b/spirit/game/scripts/cards/SWSH2/Spiritomb_116.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import place_counters, count_discard +from spirit.game.session.effects import is_pokemon_card + + +async def splitting_spite(ctx): + """Discard the top card of each player's deck.""" + cards = ctx.deck_top(1, player_id=ctx.player_id) + \ + ctx.deck_top(1, player_id=ctx.opponent_id) + await ctx.discard_cards(cards) + card = PokemonCardDef( guid="0199bea8-9023-57e2-b9f0-3c6e303d8dac", @@ -22,13 +32,13 @@ card = PokemonCardDef( title="Splitting Spite", game_text="Discard the top card of each player's deck.", cost={PokemonTypes.DARKNESS: 1}, - effect=unimplemented, + effect=splitting_spite, ), Attack( title="Dripping Grudge", game_text="Put 1 damage counter on your opponent's Active Pok\u00e9mon for each Pok\u00e9mon in your discard pile.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=place_counters(count_discard("mine", is_pokemon_card), "opponent_active"), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Stonjourner_111.py b/spirit/game/scripts/cards/SWSH2/Stonjourner_111.py index 3651bf5..d60b286 100644 --- a/spirit/game/scripts/cards/SWSH2/Stonjourner_111.py +++ b/spirit/game/scripts/cards/SWSH2/Stonjourner_111.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, count_energy + +_ENERGY_ON_SELF = count_energy("self") + + +def _has_extra_energy(ctx) -> bool: + total_cost = sum(ctx.ability.cost.values()) + return _ENERGY_ON_SELF(ctx) > total_cost + card = PokemonCardDef( guid="1ac0697d-0170-5fc3-b035-8f31594901c1", @@ -29,7 +38,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 3}, damage=120, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_has_extra_energy, 60), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Stufful_149.py b/spirit/game/scripts/cards/SWSH2/Stufful_149.py index ee6012e..6714d80 100644 --- a/spirit/game/scripts/cards/SWSH2/Stufful_149.py +++ b/spirit/game/scripts/cards/SWSH2/Stufful_149.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus card = PokemonCardDef( guid="886993b3-a6f8-5165-8ac3-c44da14df122", @@ -29,7 +30,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=40, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(40), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Stunky_114.py b/spirit/game/scripts/cards/SWSH2/Stunky_114.py index 5e8fd9c..6348963 100644 --- a/spirit/game/scripts/cards/SWSH2/Stunky_114.py +++ b/spirit/game/scripts/cards/SWSH2/Stunky_114.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="417f6c80-7ec7-55ee-b6d3-04473944f2fc", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Poisoned.", cost={PokemonTypes.COLORLESS: 2}, damage=10, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Surskit_10.py b/spirit/game/scripts/cards/SWSH2/Surskit_10.py index c97f2f2..b1827a8 100644 --- a/spirit/game/scripts/cards/SWSH2/Surskit_10.py +++ b/spirit/game/scripts/cards/SWSH2/Surskit_10.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import flip_protection card = PokemonCardDef( guid="e97939dc-ef96-5b21-9239-5eacfd491182", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, during your opponent's next turn, prevent all effects of attacks, including damage, done to this Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, damage=10, - effect=unimplemented, + effect=flip_protection(prevent=True, effects_too=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/ToolScrapper_168.py b/spirit/game/scripts/cards/SWSH2/ToolScrapper_168.py index 63e1bfb..ed4e6a1 100644 --- a/spirit/game/scripts/cards/SWSH2/ToolScrapper_168.py +++ b/spirit/game/scripts/cards/SWSH2/ToolScrapper_168.py @@ -1,5 +1,38 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import ItemCardDef +from spirit.game.attributes import Rarities, AttrID, TrainerType + + +def _is_pokemon_tool_card(card): + return card.get_attribute(AttrID.TRAINER_TYPE) in ( + TrainerType.POKEMON_TOOL.value, TrainerType.POKEMON_TOOL_F.value, + ) + + +def _tools_in_play(board): + return [ + child + for pid in board.player_ids + for pokemon in board.pokemon_in_play(pid) + for child in pokemon.children + if _is_pokemon_tool_card(child) + ] + + +def tool_scrapper_condition(board, player_id): + return bool(_tools_in_play(board)) + + +async def tool_scrapper(ctx): + """Choose up to 2 Pokémon Tools attached to Pokémon (yours or your + opponent's) and discard them.""" + tools = _tools_in_play(ctx.board) + picks = await ctx.choose_cards( + tools, 2, minimum=1, + prompt="Choose up to 2 Pokémon Tools to discard.", + ) + if picks: + await ctx.discard_cards(picks) + card = ItemCardDef( guid="bb9c83d8-b506-5765-89bd-7ba4b177fb22", @@ -11,5 +44,6 @@ card = ItemCardDef( collector_number=168, set_code="SWSH2", rarity=Rarities.Uncommon, - effect=unimplemented + effect=tool_scrapper, + condition=tool_scrapper_condition, ) diff --git a/spirit/game/scripts/cards/SWSH2/ToolScrapper_208.py b/spirit/game/scripts/cards/SWSH2/ToolScrapper_208.py index 059a99b..5127540 100644 --- a/spirit/game/scripts/cards/SWSH2/ToolScrapper_208.py +++ b/spirit/game/scripts/cards/SWSH2/ToolScrapper_208.py @@ -1,5 +1,38 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import ItemCardDef +from spirit.game.attributes import Rarities, AttrID, TrainerType + + +def _is_pokemon_tool_card(card): + return card.get_attribute(AttrID.TRAINER_TYPE) in ( + TrainerType.POKEMON_TOOL.value, TrainerType.POKEMON_TOOL_F.value, + ) + + +def _tools_in_play(board): + return [ + child + for pid in board.player_ids + for pokemon in board.pokemon_in_play(pid) + for child in pokemon.children + if _is_pokemon_tool_card(child) + ] + + +def tool_scrapper_condition(board, player_id): + return bool(_tools_in_play(board)) + + +async def tool_scrapper(ctx): + """Choose up to 2 Pokémon Tools attached to Pokémon (yours or your + opponent's) and discard them.""" + tools = _tools_in_play(ctx.board) + picks = await ctx.choose_cards( + tools, 2, minimum=1, + prompt="Choose up to 2 Pokémon Tools to discard.", + ) + if picks: + await ctx.discard_cards(picks) + card = ItemCardDef( guid="13a0bea3-c12d-5744-bae6-77bd9496fc43", @@ -11,5 +44,6 @@ card = ItemCardDef( collector_number=208, set_code="SWSH2", rarity=Rarities.RareSecret, - effect=unimplemented + effect=tool_scrapper, + condition=tool_scrapper_condition, ) diff --git a/spirit/game/scripts/cards/SWSH2/Toxel_68.py b/spirit/game/scripts/cards/SWSH2/Toxel_68.py index b8be4a7..e8ac466 100644 --- a/spirit/game/scripts/cards/SWSH2/Toxel_68.py +++ b/spirit/game/scripts/cards/SWSH2/Toxel_68.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="71d0329c-5625-51b5-9408-f33ef971717b", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.LIGHTNING: 1}, damage=10, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/ToxtricityVMAX_196.py b/spirit/game/scripts/cards/SWSH2/ToxtricityVMAX_196.py index f4ad39f..5aa2f98 100644 --- a/spirit/game/scripts/cards/SWSH2/ToxtricityVMAX_196.py +++ b/spirit/game/scripts/cards/SWSH2/ToxtricityVMAX_196.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_bonus_attack card = PokemonCardDef( guid="2ca83b47-a097-5fc3-b5a8-ba501075a9f9", @@ -25,7 +26,7 @@ card = PokemonCardDef( cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 1}, damage=160, damage_operator="+", - effect=unimplemented, + effect=condition_bonus_attack(80, SpecialConditions.POISONED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/ToxtricityVMAX_71.py b/spirit/game/scripts/cards/SWSH2/ToxtricityVMAX_71.py index 6f02a7c..2189756 100644 --- a/spirit/game/scripts/cards/SWSH2/ToxtricityVMAX_71.py +++ b/spirit/game/scripts/cards/SWSH2/ToxtricityVMAX_71.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_bonus_attack card = PokemonCardDef( guid="8c646f71-da3c-5c18-892d-ca0b2adeff10", @@ -25,7 +26,7 @@ card = PokemonCardDef( cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 1}, damage=160, damage_operator="+", - effect=unimplemented, + effect=condition_bonus_attack(80, SpecialConditions.POISONED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/ToxtricityV_182.py b/spirit/game/scripts/cards/SWSH2/ToxtricityV_182.py index af37d6f..0f571a9 100644 --- a/spirit/game/scripts/cards/SWSH2/ToxtricityV_182.py +++ b/spirit/game/scripts/cards/SWSH2/ToxtricityV_182.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack, condition_bonus_attack card = PokemonCardDef( guid="0581fc36-1674-5b28-90f6-5dcbc2d95d70", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Poisoned.", cost={PokemonTypes.LIGHTNING: 1}, damage=20, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED), ), Attack( title="Electric Riot", @@ -31,7 +32,7 @@ card = PokemonCardDef( cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 1}, damage=90, damage_operator="+", - effect=unimplemented, + effect=condition_bonus_attack(90, SpecialConditions.POISONED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/ToxtricityV_70.py b/spirit/game/scripts/cards/SWSH2/ToxtricityV_70.py index 9071ab5..40865c9 100644 --- a/spirit/game/scripts/cards/SWSH2/ToxtricityV_70.py +++ b/spirit/game/scripts/cards/SWSH2/ToxtricityV_70.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack, condition_bonus_attack card = PokemonCardDef( guid="b51c95f2-25d5-59c9-8442-acfbe43e4428", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Poisoned.", cost={PokemonTypes.LIGHTNING: 1}, damage=20, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED), ), Attack( title="Electric Riot", @@ -31,7 +32,7 @@ card = PokemonCardDef( cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 1}, damage=90, damage_operator="+", - effect=unimplemented, + effect=condition_bonus_attack(90, SpecialConditions.POISONED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Toxtricity_69.py b/spirit/game/scripts/cards/SWSH2/Toxtricity_69.py index 8d94915..977a63b 100644 --- a/spirit/game/scripts/cards/SWSH2/Toxtricity_69.py +++ b/spirit/game/scripts/cards/SWSH2/Toxtricity_69.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import damage_all_opponents + + +async def _poison_shout_condition(ctx): + """After the spread hit: poison the opponent's Active.""" + await ctx.apply_special_condition(ctx.defender, SpecialConditions.POISONED) + card = PokemonCardDef( guid="eeee9e0e-5dd9-51df-b3df-a6b572868d92", @@ -23,7 +30,7 @@ card = PokemonCardDef( title="Poison Shout", game_text="This attack does 20 damage to each of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.) Your opponent's Active Pok\u00e9mon is now Poisoned.", cost={PokemonTypes.LIGHTNING: 1}, - effect=unimplemented, + effect=damage_all_opponents(20, also=_poison_shout_condition), ), Attack( title="Hammer In", diff --git a/spirit/game/scripts/cards/SWSH2/Trevenant_15.py b/spirit/game/scripts/cards/SWSH2/Trevenant_15.py index 2ac5b96..f434366 100644 --- a/spirit/game/scripts/cards/SWSH2/Trevenant_15.py +++ b/spirit/game/scripts/cards/SWSH2/Trevenant_15.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="37bfe7f4-52a9-50a0-adf1-31d54d945b53", @@ -29,7 +30,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, the Defending Pok\u00e9mon can't retreat.", cost={PokemonTypes.GRASS: 2, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + effect=condition_attack(no_retreat=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Trubbish_117.py b/spirit/game/scripts/cards/SWSH2/Trubbish_117.py index 6092d60..189ce1c 100644 --- a/spirit/game/scripts/cards/SWSH2/Trubbish_117.py +++ b/spirit/game/scripts/cards/SWSH2/Trubbish_117.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_bonus_attack card = PokemonCardDef( guid="284de593-38bc-5ee0-8c17-dda557402764", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=20, damage_operator="+", - effect=unimplemented, + effect=condition_bonus_attack(50, SpecialConditions.POISONED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/TurffieldStadium_170.py b/spirit/game/scripts/cards/SWSH2/TurffieldStadium_170.py index 496cda9..fbdc97c 100644 --- a/spirit/game/scripts/cards/SWSH2/TurffieldStadium_170.py +++ b/spirit/game/scripts/cards/SWSH2/TurffieldStadium_170.py @@ -1,5 +1,30 @@ -from spirit.game.data_utils import StadiumCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import StadiumCardDef, Ability, Activations +from spirit.game.attributes import Rarities, PokemonTypes, AttrID +from spirit.game.session.effects import is_evolution_pokemon + + +def _is_evolution_grass(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_evolution_pokemon(card) and PokemonTypes.GRASS.value in types + + +async def turffield_stadium(ctx): + """Once during each player's turn, that player may search their deck for + an Evolution Grass Pokemon, reveal it, and put it into their hand.""" + picks = await ctx.search_deck( + _is_evolution_grass, count=1, minimum=0, + prompt="Choose an Evolution Grass Pokémon to put into your hand.", + ) + await ctx.put_in_hand(picks, reveal=True) + await ctx.shuffle_deck() + + +TURFFIELD_STADIUM_ABILITY = Ability( + title="Turffield Stadium", + game_text="Once during each player's turn, that player may search their deck for an Evolution Grass Pokémon, reveal it, and put it into their hand. Then, that player shuffles their deck.", + activation=Activations.ONCE_PER_TURN, + effect=turffield_stadium, +) card = StadiumCardDef( guid="7051ba8e-d8e1-5d98-b779-5362afd27b4e", @@ -11,5 +36,5 @@ card = StadiumCardDef( collector_number=170, set_code="SWSH2", rarity=Rarities.Uncommon, - effect=unimplemented + ability=TURFFIELD_STADIUM_ABILITY, ) diff --git a/spirit/game/scripts/cards/SWSH2/Tympole_44.py b/spirit/game/scripts/cards/SWSH2/Tympole_44.py index 22b4738..7413f5d 100644 --- a/spirit/game/scripts/cards/SWSH2/Tympole_44.py +++ b/spirit/game/scripts/cards/SWSH2/Tympole_44.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="77138fc5-fab0-5077-8c0c-c0fec507ae06", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Spiral Attack", game_text="Your opponent's Active Pok\u00e9mon is now Confused.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Unfezant_145.py b/spirit/game/scripts/cards/SWSH2/Unfezant_145.py index e06fa3e..b3c2fc8 100644 --- a/spirit/game/scripts/cards/SWSH2/Unfezant_145.py +++ b/spirit/game/scripts/cards/SWSH2/Unfezant_145.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import self_energy_discard_attack +from spirit.game.card_effects.passives_common import debuff_defender_attacks card = PokemonCardDef( guid="93413a9a-678d-5b3d-ac6f-6365a29d61e6", @@ -25,14 +27,14 @@ card = PokemonCardDef( game_text="During your opponent's next turn, the Defending Pok\u00e9mon's attacks do 50 less damage (before applying Weakness and Resistance).", cost={PokemonTypes.COLORLESS: 2}, damage=50, - effect=unimplemented, + effect=debuff_defender_attacks(50), ), Attack( title="Air Slash", game_text="Discard an Energy from this Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 3}, damage=150, - effect=unimplemented, + effect=self_energy_discard_attack(count=1, before_damage=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Vikavolt_66.py b/spirit/game/scripts/cards/SWSH2/Vikavolt_66.py index 4fa80d6..19ca812 100644 --- a/spirit/game/scripts/cards/SWSH2/Vikavolt_66.py +++ b/spirit/game/scripts/cards/SWSH2/Vikavolt_66.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import count_energy, damage_per, recoil_attack card = PokemonCardDef( guid="afccfc67-85c0-5b37-971d-9c0957cb6f47", @@ -25,14 +26,14 @@ card = PokemonCardDef( cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 2}, damage=60, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("mine"), 20, base=60), ), Attack( title="Thunder Jolt Beam", game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 3}, damage=170, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Voltorb_56.py b/spirit/game/scripts/cards/SWSH2/Voltorb_56.py index ec04c25..3ba69cb 100644 --- a/spirit/game/scripts/cards/SWSH2/Voltorb_56.py +++ b/spirit/game/scripts/cards/SWSH2/Voltorb_56.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="a147ce77-9912-5f28-9197-9ab45852c74a", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 1}, damage=20, damage_operator="x", - effect=unimplemented, + effect=flip_damage(until_tails=True, per_heads=20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Vullaby_119.py b/spirit/game/scripts/cards/SWSH2/Vullaby_119.py index 3f85f42..f1e33d8 100644 --- a/spirit/game/scripts/cards/SWSH2/Vullaby_119.py +++ b/spirit/game/scripts/cards/SWSH2/Vullaby_119.py @@ -1,6 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def pluck(ctx): + """Before doing damage, discard all Pokémon Tools from the opponent's Active.""" + target = ctx.defender + if target is not None and not ctx.effects_blocked(target): + tools = [t for t, p in ctx.tools_in_play() if p is target] + if tools: + await ctx.discard_cards(tools) + await ctx.deal_damage() + card = PokemonCardDef( guid="79899b78-7873-5d38-868d-792f413790b3", key="SWSH2", @@ -24,7 +34,7 @@ card = PokemonCardDef( game_text="Before doing damage, discard all Pok\u00e9mon Tools from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, damage=10, - effect=unimplemented, + effect=pluck, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Vulpix_24.py b/spirit/game/scripts/cards/SWSH2/Vulpix_24.py index 320195d..082c685 100644 --- a/spirit/game/scripts/cards/SWSH2/Vulpix_24.py +++ b/spirit/game/scripts/cards/SWSH2/Vulpix_24.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="aa54b60a-28b9-5962-afff-144c2b980a17", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Confuse Ray", game_text="Your opponent's Active Pok\u00e9mon is now Confused.", cost={PokemonTypes.FIRE: 1}, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED), ), Attack( title="Smash Kick", diff --git a/spirit/game/scripts/cards/SWSH2/Whiscash_100.py b/spirit/game/scripts/cards/SWSH2/Whiscash_100.py index 8a9f3dc..bd39346 100644 --- a/spirit/game/scripts/cards/SWSH2/Whiscash_100.py +++ b/spirit/game/scripts/cards/SWSH2/Whiscash_100.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import spread_damage +from spirit.game.session.passives import Passive + + +class SubmergePassive(Passive): + def prevents_damage(self, calc, carrier): + return calc.is_attack and calc.target is carrier and not calc.to_active + card = PokemonCardDef( guid="3952862f-a9b0-5bfc-bb13-a23b124a8273", @@ -21,15 +29,15 @@ card = PokemonCardDef( abilities=[ Ability( title="Submerge", - game_text="As long as this Pok\u00e9mon is on your Bench, prevent all damage done to this Pok\u00e9mon by attacks (both yours and your opponent's).", - effect=unimplemented, + game_text="As long as this Pokémon is on your Bench, prevent all damage done to this Pokémon by attacks (both yours and your opponent's).", + passive=SubmergePassive(), ), Attack( title="Earthquake", - game_text="This attack also does 20 damage to each of your Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", + game_text="This attack also does 20 damage to each of your Benched Pokémon. (Don't apply Weakness and Resistance for Benched Pokémon.)", cost={PokemonTypes.FIGHTING: 2}, damage=140, - effect=unimplemented, + effect=spread_damage(20, side="mine", also_base=True), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH2/Wingull_41.py b/spirit/game/scripts/cards/SWSH2/Wingull_41.py index a66d4ae..2140e5f 100644 --- a/spirit/game/scripts/cards/SWSH2/Wingull_41.py +++ b/spirit/game/scripts/cards/SWSH2/Wingull_41.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import draw_attack card = PokemonCardDef( guid="393990a5-6379-51fb-b6a4-8835ec0611f3", @@ -23,7 +24,7 @@ card = PokemonCardDef( title="Collect", game_text="Draw a card.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=draw_attack(1), ), Attack( title="Wave Splash", diff --git a/spirit/game/scripts/cards/SWSH2/Xatu_77.py b/spirit/game/scripts/cards/SWSH2/Xatu_77.py index 27c81e6..744a13d 100644 --- a/spirit/game/scripts/cards/SWSH2/Xatu_77.py +++ b/spirit/game/scripts/cards/SWSH2/Xatu_77.py @@ -1,5 +1,29 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy + + +async def energy_warp(ctx): + """Move an Energy from 1 of your opponent's Benched Pokémon to their Active Pokémon.""" + opp_active = ctx.opponent_active() + candidates = [p for p in ctx.opponent_bench() if ctx.attached_energies(p)] + if opp_active is None or not candidates: + return + source = await ctx.choose_pokemon( + candidates, "Choose 1 of your opponent's Benched Pokémon" + ) + if source is None or ctx.effects_blocked(source): + return + energies = ctx.attached_energies(source) + energy = energies[0] + if len(energies) > 1: + picked = await ctx.choose_cards( + energies, 1, prompt="Choose an Energy to move to the Active Pokémon" + ) + if not picked: + return + energy = picked[0] + await ctx.move_energy(energy, opp_active) card = PokemonCardDef( guid="2f9e7dac-4782-5c02-9ee7-30e1bd8bc71f", @@ -24,7 +48,7 @@ card = PokemonCardDef( title="Energy Warp", game_text="Move an Energy from 1 of your opponent's Benched Pok\u00e9mon to their Active Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=energy_warp, ), Attack( title="Psychic", @@ -32,7 +56,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 1}, damage=10, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("defender"), 30, base=10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Zacian_139.py b/spirit/game/scripts/cards/SWSH2/Zacian_139.py index 51daa0e..1b80774 100644 --- a/spirit/game/scripts/cards/SWSH2/Zacian_139.py +++ b/spirit/game/scripts/cards/SWSH2/Zacian_139.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import attach_from_discard +from spirit.game.card_effects.trainers import is_metal_energy_card + + +async def smashing_edge(ctx): + """120 damage. Flip a coin. If tails, discard 2 Energy from this Pokémon.""" + await ctx.deal_damage() + heads = (await ctx.flip_coins(1, "Smashing Edge"))[0] + if not heads: + await ctx.discard_energy_from( + ctx.attacker, 2, prompt="Discard 2 Energy from this Pokémon") + card = PokemonCardDef( guid="5f77495a-791f-5508-8bcc-1f1917aed094", @@ -24,14 +36,14 @@ card = PokemonCardDef( game_text="Attach a Metal Energy card from your discard pile to this Pok\u00e9mon.", cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=attach_from_discard(predicate=is_metal_energy_card, count=1, target="self"), ), Attack( title="Smashing Edge", game_text="Flip a coin. If tails, discard 2 Energy from this Pok\u00e9mon.", cost={PokemonTypes.METAL: 2, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + effect=smashing_edge, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH2/Zamazenta_140.py b/spirit/game/scripts/cards/SWSH2/Zamazenta_140.py index 95d5c15..8269c29 100644 --- a/spirit/game/scripts/cards/SWSH2/Zamazenta_140.py +++ b/spirit/game/scripts/cards/SWSH2/Zamazenta_140.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import protect_next_turn +from spirit.game.card_effects.attacks_common import lock_all_attacks + + +async def power_rush(ctx): + """120 damage. Flip a coin. If tails, this Pokémon can't attack during your next turn.""" + await ctx.deal_damage() + heads = (await ctx.flip_coins(1, "Power Rush"))[0] + if not heads: + lock_all_attacks(ctx, ctx.attacker) + card = PokemonCardDef( guid="dd843f6c-6d71-5a85-a284-d063bebc644d", @@ -24,14 +35,14 @@ card = PokemonCardDef( game_text="During your opponent's next turn, this Pok\u00e9mon takes 20 less damage from attacks (after applying Weakness and Resistance).", cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=protect_next_turn(reduce=20), ), Attack( title="Power Rush", game_text="Flip a coin. If tails, this Pok\u00e9mon can't attack during your next turn.", cost={PokemonTypes.METAL: 2, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + effect=power_rush, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Aggron_123.py b/spirit/game/scripts/cards/SWSH3/Aggron_123.py index 612dc6b..db7045a 100644 --- a/spirit/game/scripts/cards/SWSH3/Aggron_123.py +++ b/spirit/game/scripts/cards/SWSH3/Aggron_123.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import guts_survive_passive card = PokemonCardDef( guid="799fd8ab-7654-5787-8b67-a736acd255db", @@ -23,7 +24,7 @@ card = PokemonCardDef( Ability( title="Sturdy", game_text="If this Pok\u00e9mon has full HP and would be Knocked Out by damage from an attack, it is not Knocked Out, and its remaining HP becomes 10.", - effect=unimplemented, + passive=guts_survive_passive(hp_floor=10, flip=False, require_full_hp=True), ), Attack( title="Gigaton Stomp", diff --git a/spirit/game/scripts/cards/SWSH3/Arctozolt_66.py b/spirit/game/scripts/cards/SWSH3/Arctozolt_66.py index c4956e6..f0661e2 100644 --- a/spirit/game/scripts/cards/SWSH3/Arctozolt_66.py +++ b/spirit/game/scripts/cards/SWSH3/Arctozolt_66.py @@ -1,6 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def biting_whirlpool(ctx): + """Whenever your opponent attaches an Energy card from their hand to 1 of + their Pokémon, put 2 damage counters on that Pokémon.""" + if ctx.attaching_player_id == ctx.player_id: + return + receiver = ctx.energy_receiver + if receiver is None: + return + await ctx.deal_damage(20, target=receiver, apply_modifiers=False, as_counters=True) + + card = PokemonCardDef( guid="30c81b90-865b-554b-b23e-c2c1d614da12", key="SWSH3", @@ -22,7 +34,8 @@ card = PokemonCardDef( Ability( title="Biting Whirlpool", game_text="Whenever your opponent attaches an Energy card from their hand to 1 of their Pok\u00e9mon, put 2 damage counters on that Pok\u00e9mon.", - effect=unimplemented, + trigger=Triggers.ON_ENERGY_ATTACHED, + effect=biting_whirlpool, ), Attack( title="Electro Ball", diff --git a/spirit/game/scripts/cards/SWSH3/Beartic_49.py b/spirit/game/scripts/cards/SWSH3/Beartic_49.py index 7d408a8..681531d 100644 --- a/spirit/game/scripts/cards/SWSH3/Beartic_49.py +++ b/spirit/game/scripts/cards/SWSH3/Beartic_49.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack card = PokemonCardDef( guid="2fd55ee0-cf3f-5516-97f3-bb23f246854f", @@ -29,7 +30,7 @@ card = PokemonCardDef( game_text="This Pok\u00e9mon also does 50 damage to itself.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=150, - effect=unimplemented, + effect=recoil_attack(50), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/BillowingSmoke_158.py b/spirit/game/scripts/cards/SWSH3/BillowingSmoke_158.py index e680620..414a13d 100644 --- a/spirit/game/scripts/cards/SWSH3/BillowingSmoke_158.py +++ b/spirit/game/scripts/cards/SWSH3/BillowingSmoke_158.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented +from spirit.game.data_utils import PokemonToolCardDef from spirit.game.attributes import Rarities +from spirit.game.session.passives import Passive, carrier_pokemon + + +class BillowingSmokePassive(Passive): + def prize_destination(self, pokemon, ctx, carrier): + if carrier_pokemon(carrier) is not pokemon: + return None + if not ctx.is_attack_effect(): + return None + attacker = getattr(ctx, "attacker", None) + if attacker is None or attacker.owning_player_id == pokemon.owning_player_id: + return None + return "discard" + card = PokemonToolCardDef( guid="2508ef44-0afb-5957-8126-b92f2a37fd1e", @@ -11,5 +25,5 @@ card = PokemonToolCardDef( collector_number=158, set_code="SWSH3", rarity=Rarities.Uncommon, - effect=unimplemented + passive=BillowingSmokePassive() ) diff --git a/spirit/game/scripts/cards/SWSH3/BirdKeeper_159.py b/spirit/game/scripts/cards/SWSH3/BirdKeeper_159.py index 8a5a700..ba033a1 100644 --- a/spirit/game/scripts/cards/SWSH3/BirdKeeper_159.py +++ b/spirit/game/scripts/cards/SWSH3/BirdKeeper_159.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import player_has_bench + + +async def bird_keeper(ctx): + """Switch your Active Pokemon with 1 of your Benched Pokemon. If you do, draw 3 cards.""" + target = await ctx.choose_pokemon(ctx.my_bench(), "Choose your new Active Pokémon") + if target is None: + return + await ctx.switch_active(ctx.player_id, target) + await ctx.draw_cards(3) + card = SupporterCardDef( guid="5112ab69-f4b8-5060-b0f9-3df08080dee7", @@ -11,5 +22,6 @@ card = SupporterCardDef( collector_number=159, set_code="SWSH3", rarity=Rarities.Uncommon, - effect=unimplemented + effect=bird_keeper, + condition=player_has_bench ) diff --git a/spirit/game/scripts/cards/SWSH3/Blaziken_24.py b/spirit/game/scripts/cards/SWSH3/Blaziken_24.py index c902dcb..c87ea07 100644 --- a/spirit/game/scripts/cards/SWSH3/Blaziken_24.py +++ b/spirit/game/scripts/cards/SWSH3/Blaziken_24.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import attach_from_discard +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.card_effects.passives_common import is_in_active_spot card = PokemonCardDef( guid="337f5722-020d-5675-ae97-021c8517e0f9", @@ -12,7 +15,10 @@ card = PokemonCardDef( set_code="SWSH3", rarity=Rarities.RareHolo, hp=170, - elements=[PokemonTypes.FIRE], + # Double Type: this Pokemon is Fire AND Fighting type as long as it's in + # play; baked directly into POKEMON_TYPES since Weakness is computed off + # the live attacker's type list. + elements=[PokemonTypes.FIRE, PokemonTypes.FIGHTING], stage=PokemonStage.STAGE2, retreat_cost=2, weakness_type=PokemonTypes.WATER, @@ -21,15 +27,18 @@ card = PokemonCardDef( abilities=[ Ability( title="Double Type", - game_text="As long as this Pok\u00e9mon is in play, it is Fire and Fighting type.", - effect=unimplemented, + game_text="As long as this Pokémon is in play, it is Fire and Fighting type.", ), Attack( title="Turbo Drive", - game_text="Attach a basic Energy card from your discard pile to 1 of your Benched Pok\u00e9mon.", + game_text="Attach a basic Energy card from your discard pile to 1 of your Benched Pokémon.", cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 2}, damage=130, - effect=unimplemented, + effect=attach_from_discard( + predicate=is_basic_energy_card, count=1, + target=lambda p: not is_in_active_spot(p), + prompt="Choose a basic Energy card to attach to 1 of your Benched Pokémon", + ), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH3/Bounsweet_14.py b/spirit/game/scripts/cards/SWSH3/Bounsweet_14.py index 37dfa0c..2aacb88 100644 --- a/spirit/game/scripts/cards/SWSH3/Bounsweet_14.py +++ b/spirit/game/scripts/cards/SWSH3/Bounsweet_14.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_attach_energy +from spirit.game.card_effects.trainers import is_grass_energy_card card = PokemonCardDef( guid="bd5fc7e6-e8f7-56e6-8922-2fa9fb09bf97", @@ -22,7 +24,7 @@ card = PokemonCardDef( title="Synthesis", game_text="Search your deck for a Grass Energy card and attach it to 1 of your Pok\u00e9mon. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_attach_energy(is_grass_energy_card, count=1), ), Attack( title="Flop", diff --git a/spirit/game/scripts/cards/SWSH3/Bunnelby_150.py b/spirit/game/scripts/cards/SWSH3/Bunnelby_150.py index 278ba8a..1b90a68 100644 --- a/spirit/game/scripts/cards/SWSH3/Bunnelby_150.py +++ b/spirit/game/scripts/cards/SWSH3/Bunnelby_150.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_discard, has_attack_titled card = PokemonCardDef( guid="ab5940cb-af67-5353-88f8-f498c7274f87", @@ -20,11 +21,11 @@ card = PokemonCardDef( abilities=[ Attack( title="Mad Party", - game_text="This attack does 20 damage for each Pok\u00e9mon in your discard pile that has the Mad Party attack.", + game_text="This attack does 20 damage for each Pokémon in your discard pile that has the Mad Party attack.", cost={PokemonTypes.COLORLESS: 2}, damage=20, damage_operator="x", - effect=unimplemented, + effect=damage_per(count_discard("mine", has_attack_titled("Mad Party")), 20), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH3/ButterfreeVMAX_190.py b/spirit/game/scripts/cards/SWSH3/ButterfreeVMAX_190.py index ecf9ddd..42e3df5 100644 --- a/spirit/game/scripts/cards/SWSH3/ButterfreeVMAX_190.py +++ b/spirit/game/scripts/cards/SWSH3/ButterfreeVMAX_190.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="3c25b95f-e98f-57ca-9541-a69903dc44c4", @@ -24,7 +25,9 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Confused and Poisoned.", cost={PokemonTypes.GRASS: 2, PokemonTypes.COLORLESS: 1}, damage=150, - effect=unimplemented, + effect=condition_attack( + SpecialConditions.CONFUSED, SpecialConditions.POISONED, + ), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/ButterfreeVMAX_2.py b/spirit/game/scripts/cards/SWSH3/ButterfreeVMAX_2.py index c42ffc7..9a203dc 100644 --- a/spirit/game/scripts/cards/SWSH3/ButterfreeVMAX_2.py +++ b/spirit/game/scripts/cards/SWSH3/ButterfreeVMAX_2.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="ce6168a1-cd56-50c1-a99a-3d49564b2616", @@ -24,7 +25,9 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Confused and Poisoned.", cost={PokemonTypes.GRASS: 2, PokemonTypes.COLORLESS: 1}, damage=150, - effect=unimplemented, + effect=condition_attack( + SpecialConditions.CONFUSED, SpecialConditions.POISONED, + ), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/ButterfreeV_1.py b/spirit/game/scripts/cards/SWSH3/ButterfreeV_1.py index e865d4a..8577786 100644 --- a/spirit/game/scripts/cards/SWSH3/ButterfreeV_1.py +++ b/spirit/game/scripts/cards/SWSH3/ButterfreeV_1.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="615c3c80-031e-5c8a-9b68-9a24e4501ea1", @@ -22,7 +23,9 @@ card = PokemonCardDef( title="Dizzying Poison", game_text="Your opponent's Active Pok\u00e9mon is now Confused and Poisoned.", cost={PokemonTypes.GRASS: 1}, - effect=unimplemented, + effect=condition_attack( + SpecialConditions.CONFUSED, SpecialConditions.POISONED, + ), ), Attack( title="Blasting Wind", diff --git a/spirit/game/scripts/cards/SWSH3/ButterfreeV_177.py b/spirit/game/scripts/cards/SWSH3/ButterfreeV_177.py index 2300a68..e047bd4 100644 --- a/spirit/game/scripts/cards/SWSH3/ButterfreeV_177.py +++ b/spirit/game/scripts/cards/SWSH3/ButterfreeV_177.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="ff69cefc-d09c-5ccb-8edd-a097212ca8d4", @@ -22,7 +23,9 @@ card = PokemonCardDef( title="Dizzying Poison", game_text="Your opponent's Active Pok\u00e9mon is now Confused and Poisoned.", cost={PokemonTypes.GRASS: 1}, - effect=unimplemented, + effect=condition_attack( + SpecialConditions.CONFUSED, SpecialConditions.POISONED, + ), ), Attack( title="Blasting Wind", diff --git a/spirit/game/scripts/cards/SWSH3/CapeofToughness_160.py b/spirit/game/scripts/cards/SWSH3/CapeofToughness_160.py index 1909495..1a9b45a 100644 --- a/spirit/game/scripts/cards/SWSH3/CapeofToughness_160.py +++ b/spirit/game/scripts/cards/SWSH3/CapeofToughness_160.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented +from spirit.game.data_utils import PokemonToolCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.passives_common import hp_bonus_tool +from spirit.game.card_effects.pokemon import is_pokemon_gx +from spirit.game.session.effects import is_basic_pokemon card = PokemonToolCardDef( guid="cfbda63d-3fb2-54c7-9c90-de6100006d9f", @@ -11,5 +14,7 @@ card = PokemonToolCardDef( collector_number=160, set_code="SWSH3", rarity=Rarities.Uncommon, - effect=unimplemented + passive=hp_bonus_tool( + 50, holder_pred=lambda p: is_basic_pokemon(p) and not is_pokemon_gx(p.archetype_id) + ), ) diff --git a/spirit/game/scripts/cards/SWSH3/Carnivine_5.py b/spirit/game/scripts/cards/SWSH3/Carnivine_5.py index 7d0a0ab..86264a3 100644 --- a/spirit/game/scripts/cards/SWSH3/Carnivine_5.py +++ b/spirit/game/scripts/cards/SWSH3/Carnivine_5.py @@ -1,6 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def _return(ctx): + await ctx.deal_damage() + if await ctx.ask_yes_no("Draw cards until you have 5 cards in your hand?"): + await ctx.draw_until(5) + + +async def _giga_drain(ctx): + dealt = await ctx.deal_damage() + if dealt > 0: + await ctx.heal(dealt) + + card = PokemonCardDef( guid="3fd18270-2a2c-56a3-a3a5-1d2278b4e135", key="SWSH3", @@ -23,14 +36,14 @@ card = PokemonCardDef( game_text="You may draw cards until you have 5 cards in your hand.", cost={PokemonTypes.COLORLESS: 1}, damage=20, - effect=unimplemented, + effect=_return, ), Attack( title="Giga Drain", game_text="Heal from this Pok\u00e9mon the same amount of damage you did to your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=40, - effect=unimplemented, + effect=_giga_drain, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/CentiskorchVMAX_191.py b/spirit/game/scripts/cards/SWSH3/CentiskorchVMAX_191.py index 694111c..d08ac70 100644 --- a/spirit/game/scripts/cards/SWSH3/CentiskorchVMAX_191.py +++ b/spirit/game/scripts/cards/SWSH3/CentiskorchVMAX_191.py @@ -1,5 +1,24 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import count_energy +from spirit.game.card_effects.pokemon import energy_provides_type + + +async def g_max_centiferno(ctx): + """40 more damage for each Fire Energy attached to this Pokémon. If you did any damage, you may attach a Fire Energy card from your discard pile to this Pokémon.""" + amount = 40 * count_energy("self", energy_type=PokemonTypes.FIRE)(ctx) + dealt = await ctx.deal_damage(amount) if amount > 0 else 0 + if dealt <= 0: + return + if not await ctx.ask_yes_no("Attach a Fire Energy card from your discard pile to this Pokémon?"): + return + energies = [c for c in ctx.discard_pile() if energy_provides_type(c, PokemonTypes.FIRE.value)] + picks = await ctx.choose_cards( + energies, 1, minimum=1, + prompt="Choose a Fire Energy card to attach to this Pokémon.") + for card in picks: + await ctx.attach_energy(card, ctx.attacker) + card = PokemonCardDef( guid="e67a2398-6c80-523d-a2ad-86279b03dc6e", @@ -25,7 +44,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=40, damage_operator="+", - effect=unimplemented, + effect=g_max_centiferno, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/CentiskorchVMAX_34.py b/spirit/game/scripts/cards/SWSH3/CentiskorchVMAX_34.py index fc5c381..568e9d9 100644 --- a/spirit/game/scripts/cards/SWSH3/CentiskorchVMAX_34.py +++ b/spirit/game/scripts/cards/SWSH3/CentiskorchVMAX_34.py @@ -1,5 +1,24 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import count_energy +from spirit.game.card_effects.pokemon import energy_provides_type + + +async def g_max_centiferno(ctx): + """40 more damage for each Fire Energy attached to this Pokémon. If you did any damage, you may attach a Fire Energy card from your discard pile to this Pokémon.""" + amount = 40 * count_energy("self", energy_type=PokemonTypes.FIRE)(ctx) + dealt = await ctx.deal_damage(amount) if amount > 0 else 0 + if dealt <= 0: + return + if not await ctx.ask_yes_no("Attach a Fire Energy card from your discard pile to this Pokémon?"): + return + energies = [c for c in ctx.discard_pile() if energy_provides_type(c, PokemonTypes.FIRE.value)] + picks = await ctx.choose_cards( + energies, 1, minimum=1, + prompt="Choose a Fire Energy card to attach to this Pokémon.") + for card in picks: + await ctx.attach_energy(card, ctx.attacker) + card = PokemonCardDef( guid="06fa105d-71b2-594c-a3b3-6406411a3320", @@ -25,7 +44,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=40, damage_operator="+", - effect=unimplemented, + effect=g_max_centiferno, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/CentiskorchV_179.py b/spirit/game/scripts/cards/SWSH3/CentiskorchV_179.py index d71d851..ca84100 100644 --- a/spirit/game/scripts/cards/SWSH3/CentiskorchV_179.py +++ b/spirit/game/scripts/cards/SWSH3/CentiskorchV_179.py @@ -1,6 +1,24 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def radiating_heat(ctx): + """20 damage. You may discard an Energy from this Pokémon; if you do, + discard an Energy from the opponent's Active Pokémon.""" + await ctx.deal_damage() + if not ctx.attached_energies(ctx.attacker): + return + if not await ctx.ask_yes_no("Discard an Energy from this Pokémon?"): + return + discarded = await ctx.discard_energy_from(ctx.attacker, 1) + if not discarded: + return + defender = ctx.defender + if defender is not None and ctx.attached_energies(defender) \ + and not ctx.effects_blocked(defender): + await ctx.discard_energy_from(defender, 1) + + card = PokemonCardDef( guid="8585f076-23fd-5257-b0c4-3193697b639b", key="SWSH3", @@ -20,10 +38,10 @@ card = PokemonCardDef( abilities=[ Attack( title="Radiating Heat", - game_text="You may discard an Energy from this Pok\u00e9mon. If you do, discard an Energy from your opponent's Active Pok\u00e9mon.", + game_text="You may discard an Energy from this Pokémon. If you do, discard an Energy from your opponent's Active Pokémon.", cost={PokemonTypes.FIRE: 1}, damage=20, - effect=unimplemented, + effect=radiating_heat, ), Attack( title="Burning Train", @@ -31,4 +49,4 @@ card = PokemonCardDef( damage=180, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH3/CentiskorchV_33.py b/spirit/game/scripts/cards/SWSH3/CentiskorchV_33.py index 368fb94..d9dec8f 100644 --- a/spirit/game/scripts/cards/SWSH3/CentiskorchV_33.py +++ b/spirit/game/scripts/cards/SWSH3/CentiskorchV_33.py @@ -1,6 +1,24 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def radiating_heat(ctx): + """20 damage. You may discard an Energy from this Pokémon; if you do, + discard an Energy from the opponent's Active Pokémon.""" + await ctx.deal_damage() + if not ctx.attached_energies(ctx.attacker): + return + if not await ctx.ask_yes_no("Discard an Energy from this Pokémon?"): + return + discarded = await ctx.discard_energy_from(ctx.attacker, 1) + if not discarded: + return + defender = ctx.defender + if defender is not None and ctx.attached_energies(defender) \ + and not ctx.effects_blocked(defender): + await ctx.discard_energy_from(defender, 1) + + card = PokemonCardDef( guid="90b18e7c-0895-5543-8cd6-7cad97bf8b65", key="SWSH3", @@ -20,10 +38,10 @@ card = PokemonCardDef( abilities=[ Attack( title="Radiating Heat", - game_text="You may discard an Energy from this Pok\u00e9mon. If you do, discard an Energy from your opponent's Active Pok\u00e9mon.", + game_text="You may discard an Energy from this Pokémon. If you do, discard an Energy from your opponent's Active Pokémon.", cost={PokemonTypes.FIRE: 1}, damage=20, - effect=unimplemented, + effect=radiating_heat, ), Attack( title="Burning Train", @@ -31,4 +49,4 @@ card = PokemonCardDef( damage=180, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH3/CharizardVMAX_20.py b/spirit/game/scripts/cards/SWSH3/CharizardVMAX_20.py index 771a6d4..97f0bec 100644 --- a/spirit/game/scripts/cards/SWSH3/CharizardVMAX_20.py +++ b/spirit/game/scripts/cards/SWSH3/CharizardVMAX_20.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import self_energy_discard_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -29,7 +30,7 @@ card = PokemonCardDef( game_text="Discard 2 Energy from this Pok\u00e9mon.", cost={PokemonTypes.FIRE: 3, PokemonTypes.COLORLESS: 2}, damage=300, - effect=unimplemented, + effect=self_energy_discard_attack(count=2), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/CharizardV_19.py b/spirit/game/scripts/cards/SWSH3/CharizardV_19.py index 83fddae..62a940a 100644 --- a/spirit/game/scripts/cards/SWSH3/CharizardV_19.py +++ b/spirit/game/scripts/cards/SWSH3/CharizardV_19.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import self_energy_discard_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -28,7 +29,7 @@ card = PokemonCardDef( game_text="Discard 2 Energy from this Pok\u00e9mon.", cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 2}, damage=220, - effect=unimplemented, + effect=self_energy_discard_attack(count=2), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Coalossal_198.py b/spirit/game/scripts/cards/SWSH3/Coalossal_198.py index 26cca38..8d4ccbf 100644 --- a/spirit/game/scripts/cards/SWSH3/Coalossal_198.py +++ b/spirit/game/scripts/cards/SWSH3/Coalossal_198.py @@ -1,5 +1,44 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.trainers import is_energy_card +from spirit.game.card_effects.support_common import requires_discard, distribute_energy + + +def _is_fire_energy_card(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_energy_card(card) and PokemonTypes.FIRE.value in types + + +def _is_fighting_energy_card(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_energy_card(card) and PokemonTypes.FIGHTING.value in types + + +async def tar_generator(ctx): + """Once per turn, you may attach a Fire Energy, a Fighting Energy, or + 1 of each from your discard pile to your Pokémon in any way you like.""" + if not await ctx.ask_yes_no( + "Attach a Fire Energy card, a Fighting Energy card, or 1 of each " + "from your discard pile to your Pokémon?" + ): + return + picks = [] + fire_cards = [c for c in ctx.discard_pile() if _is_fire_energy_card(c)] + if fire_cards: + picks.extend(await ctx.choose_cards( + fire_cards, 1, minimum=0, + prompt="Choose a Fire Energy card to attach (optional)", + )) + fighting_cards = [c for c in ctx.discard_pile() if _is_fighting_energy_card(c)] + if fighting_cards: + picks.extend(await ctx.choose_cards( + fighting_cards, 1, minimum=0, + prompt="Choose a Fighting Energy card to attach (optional)", + )) + if not picks: + return + await distribute_energy(ctx, picks, ctx.my_pokemon_in_play()) + card = PokemonCardDef( guid="d0c3b8c3-ddb7-5d04-881c-0dc286bddc2f", @@ -21,8 +60,12 @@ card = PokemonCardDef( abilities=[ Ability( title="Tar Generator", - game_text="Once during your turn, you may attach a Fire Energy card, a Fighting Energy card, or 1 of each from your discard pile to your Pok\u00e9mon in any way you like.", - effect=unimplemented, + game_text="Once during your turn, you may attach a Fire Energy card, a Fighting Energy card, or 1 of each from your discard pile to your Pokémon in any way you like.", + activation=Activations.ONCE_PER_TURN, + condition=requires_discard( + lambda c: _is_fire_energy_card(c) or _is_fighting_energy_card(c) + ), + effect=tar_generator, ), Attack( title="Flaming Avalanche", @@ -30,4 +73,4 @@ card = PokemonCardDef( damage=130, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH3/Combusken_23.py b/spirit/game/scripts/cards/SWSH3/Combusken_23.py index 91f2265..8afb5b1 100644 --- a/spirit/game/scripts/cards/SWSH3/Combusken_23.py +++ b/spirit/game/scripts/cards/SWSH3/Combusken_23.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="ac0cb219-0e20-50e5-836e-6703526ff604", @@ -29,7 +30,7 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Burned.", cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 2}, damage=40, - effect=unimplemented, + effect=condition_attack(SpecialConditions.BURNED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Copperajah_132.py b/spirit/game/scripts/cards/SWSH3/Copperajah_132.py index bfab3fb..fe4ff98 100644 --- a/spirit/game/scripts/cards/SWSH3/Copperajah_132.py +++ b/spirit/game/scripts/cards/SWSH3/Copperajah_132.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.attacks_common import bonus_if +from spirit.game.card_effects.passives_common import condition_immunity_passive + + +def _bench_damaged(ctx) -> bool: + return any( + p.get_attribute(AttrID.HP, 0) < ctx.max_hp(p) for p in ctx.my_bench() + ) + card = PokemonCardDef( guid="5e272b8d-b1dd-52f8-ae29-9cb4afc5952d", @@ -23,7 +32,7 @@ card = PokemonCardDef( Ability( title="Antibacterial Skin", game_text="This Pok\u00e9mon can't be affected by any Special Conditions.", - effect=unimplemented, + passive=condition_immunity_passive(), ), Attack( title="Vengeful Stomp", @@ -31,7 +40,7 @@ card = PokemonCardDef( cost={PokemonTypes.METAL: 2, PokemonTypes.COLORLESS: 2}, damage=120, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_bench_damaged, 120), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Corviknight_156.py b/spirit/game/scripts/cards/SWSH3/Corviknight_156.py index b6700bc..61adccb 100644 --- a/spirit/game/scripts/cards/SWSH3/Corviknight_156.py +++ b/spirit/game/scripts/cards/SWSH3/Corviknight_156.py @@ -1,5 +1,35 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers, def_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import full_stack + + +def _is_corviknight(pokemon) -> bool: + d = def_for(pokemon.archetype_id) + return bool(d) and d.display_name == "Corviknight" + + +async def flying_taxi(ctx): + """On evolve: you may put 1 of your Pokémon (except any Corviknight) and + all attached cards into your hand.""" + candidates = [p for p in ctx.my_pokemon_in_play() if not _is_corviknight(p)] + if not candidates: + return + if not await ctx.ask_yes_no( + "Put 1 of your Pokémon (except Corviknight) and all attached cards into your hand?"): + return + target = await ctx.choose_pokemon( + candidates, "Choose a Pokémon (not Corviknight) to put into your hand") + if target is None: + return + is_active = target is ctx.my_active() + await ctx.put_in_hand(full_stack(target), reveal=False) + if is_active: + async def _promote(): + if not await ctx.session._promote_new_active(ctx.player_id): + screen_name = ctx.session.players[ctx.player_id].screen_name + await ctx.session.end_game(ctx.opponent_id, f"{screen_name} has no Pokémon left") + ctx.deferred_actions.append(_promote) + card = PokemonCardDef( guid="3878f503-8254-5362-8157-3818792e1f60", @@ -23,7 +53,8 @@ card = PokemonCardDef( Ability( title="Flying Taxi", game_text="When you play this Pok\u00e9mon from your hand to evolve 1 of your Pok\u00e9mon during your turn, you may put 1 of your Pok\u00e9mon, except any Corviknight, and all attached cards into your hand.", - effect=unimplemented, + trigger=Triggers.ON_EVOLVE, + effect=flying_taxi, ), Attack( title="Blasting Wind", diff --git a/spirit/game/scripts/cards/SWSH3/Corvisquire_155.py b/spirit/game/scripts/cards/SWSH3/Corvisquire_155.py index 6fdd979..e3f271b 100644 --- a/spirit/game/scripts/cards/SWSH3/Corvisquire_155.py +++ b/spirit/game/scripts/cards/SWSH3/Corvisquire_155.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import flip_damage +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -31,7 +32,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=40, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=3, per_heads=40), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/CrobatV_104.py b/spirit/game/scripts/cards/SWSH3/CrobatV_104.py index e07b502..58c8645 100644 --- a/spirit/game/scripts/cards/SWSH3/CrobatV_104.py +++ b/spirit/game/scripts/cards/SWSH3/CrobatV_104.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.pokemon import dark_asset card = PokemonCardDef( guid="a36c157c-a1c3-5c8b-81b8-43a80c8fa627", @@ -21,14 +23,16 @@ card = PokemonCardDef( Ability( title="Dark Asset", game_text="When you play this Pok\u00e9mon from your hand onto your Bench during your turn, you may draw cards until you have 6 cards in your hand. You can't use more than 1 Dark Asset Ability each turn.", - effect=unimplemented, + trigger=Triggers.ON_PLAY, + shared_once_per_turn="Dark Asset", + effect=dark_asset, ), Attack( title="Venomous Fang", game_text="Your opponent's Active Pok\u00e9mon is now Poisoned.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 1}, damage=70, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/CrobatV_182.py b/spirit/game/scripts/cards/SWSH3/CrobatV_182.py index fdd4b2f..b5e7851 100644 --- a/spirit/game/scripts/cards/SWSH3/CrobatV_182.py +++ b/spirit/game/scripts/cards/SWSH3/CrobatV_182.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.pokemon import dark_asset card = PokemonCardDef( guid="a9034837-b287-56e7-a621-c2db77d7c62e", @@ -21,14 +23,16 @@ card = PokemonCardDef( Ability( title="Dark Asset", game_text="When you play this Pok\u00e9mon from your hand onto your Bench during your turn, you may draw cards until you have 6 cards in your hand. You can't use more than 1 Dark Asset Ability each turn.", - effect=unimplemented, + trigger=Triggers.ON_PLAY, + shared_once_per_turn="Dark Asset", + effect=dark_asset, ), Attack( title="Venomous Fang", game_text="Your opponent's Active Pok\u00e9mon is now Poisoned.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 1}, damage=70, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Darkrai_105.py b/spirit/game/scripts/cards/SWSH3/Darkrai_105.py index e3193f0..2b16a88 100644 --- a/spirit/game/scripts/cards/SWSH3/Darkrai_105.py +++ b/spirit/game/scripts/cards/SWSH3/Darkrai_105.py @@ -1,5 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy +from spirit.game.card_effects.pokemon import energy_provides_type +from spirit.game.session.passives import Passive + + +class DarknessGuardPassive(Passive): + def modify_damage_taken(self, calc, carrier): + if not (calc.is_attack and calc.is_opposing and calc.target is carrier): + return + if any( + energy_provides_type(c, PokemonTypes.DARKNESS.value) + for c in carrier.children + ): + calc.amount = max(0, calc.amount - 20) + card = PokemonCardDef( guid="ff8b5d90-1361-5b57-9ebb-6a33f87e61ec", @@ -21,7 +36,7 @@ card = PokemonCardDef( Ability( title="Darkness Guard", game_text="If this Pok\u00e9mon has any Darkness Energy attached, it takes 20 less damage from attacks (after applying Weakness and Resistance).", - effect=unimplemented, + passive=DarknessGuardPassive(), ), Attack( title="Vortex of Darkness", @@ -29,7 +44,9 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=60, damage_operator="+", - effect=unimplemented, + effect=damage_per( + count_energy("self", energy_type=PokemonTypes.DARKNESS), 20, base=60 + ), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Decidueye_13.py b/spirit/game/scripts/cards/SWSH3/Decidueye_13.py index 1599502..2853bfa 100644 --- a/spirit/game/scripts/cards/SWSH3/Decidueye_13.py +++ b/spirit/game/scripts/cards/SWSH3/Decidueye_13.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, is_pokemon_v from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import prevent_damage_when +from spirit.game.card_effects.pokemon import is_pokemon_gx +from spirit.game.card_effects.attacks_common import snipe_attack + + +def _deep_forest_camo_pred(calc, carrier): + if calc.target is not carrier or calc.attacker is None: + return False + atk_id = calc.attacker.archetype_id + return is_pokemon_v(atk_id) or is_pokemon_gx(atk_id) + card = PokemonCardDef( guid="7024b910-8cfd-5096-bee9-77c505f96e64", @@ -22,14 +33,14 @@ card = PokemonCardDef( Ability( title="Deep Forest Camo", game_text="Prevent all damage done to this Pok\u00e9mon by attacks from your opponent's Pok\u00e9mon V and Pok\u00e9mon-GX.", - effect=unimplemented, + passive=prevent_damage_when(_deep_forest_camo_pred), ), Attack( title="Splitting Arrow", game_text="This attack also does 20 damage to 2 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=90, - effect=unimplemented, + effect=snipe_attack(20, pool="bench", count=2, also_base=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Dedenne_78.py b/spirit/game/scripts/cards/SWSH3/Dedenne_78.py index b4b3e60..d42948b 100644 --- a/spirit/game/scripts/cards/SWSH3/Dedenne_78.py +++ b/spirit/game/scripts/cards/SWSH3/Dedenne_78.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_discard, has_attack_titled card = PokemonCardDef( guid="46bd2775-9dbc-54d5-830b-a164e1543b7c", @@ -20,11 +21,11 @@ card = PokemonCardDef( abilities=[ Attack( title="Mad Party", - game_text="This attack does 20 damage for each Pok\u00e9mon in your discard pile that has the Mad Party attack.", + game_text="This attack does 20 damage for each Pokémon in your discard pile that has the Mad Party attack.", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 2}, damage=20, damage_operator="x", - effect=unimplemented, + effect=damage_per(count_discard("mine", has_attack_titled("Mad Party")), 20), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH3/Delcatty_142.py b/spirit/game/scripts/cards/SWSH3/Delcatty_142.py index aa742cf..709be7b 100644 --- a/spirit/game/scripts/cards/SWSH3/Delcatty_142.py +++ b/spirit/game/scripts/cards/SWSH3/Delcatty_142.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.support_common import gust_then + + +async def _confuse_new_active(ctx, new_active): + await ctx.apply_special_condition(new_active, SpecialConditions.CONFUSED) + card = PokemonCardDef( guid="7c2e83bc-2457-5a81-9e92-232a7f4866b4", @@ -23,7 +29,7 @@ card = PokemonCardDef( title="Captivating Tail", game_text="Switch 1 of your opponent's Benched Pok\u00e9mon with their Active Pok\u00e9mon. The new Active Pok\u00e9mon is now Confused.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=gust_then(_confuse_new_active), ), Attack( title="Moon Impact", diff --git a/spirit/game/scripts/cards/SWSH3/Dracovish_53.py b/spirit/game/scripts/cards/SWSH3/Dracovish_53.py index 6d1bb76..a669156 100644 --- a/spirit/game/scripts/cards/SWSH3/Dracovish_53.py +++ b/spirit/game/scripts/cards/SWSH3/Dracovish_53.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.passives import Passive +from spirit.game.card_effects.passives_common import is_in_active_spot + + +class PrimalLawPassive(Passive): + def blocks_evolution(self, player_id, target, carrier): + return player_id != carrier.owning_player_id and is_in_active_spot(carrier) + card = PokemonCardDef( guid="ca58be7f-9ced-5aad-93ec-fcce58b975b8", @@ -22,7 +30,7 @@ card = PokemonCardDef( Ability( title="Primal Law", game_text="As long as this Pok\u00e9mon is in the Active Spot, your opponent can't play any Pok\u00e9mon from their hand to evolve their Pok\u00e9mon.", - effect=unimplemented, + passive=PrimalLawPassive(), ), Attack( title="Hammer In", diff --git a/spirit/game/scripts/cards/SWSH3/Dracozolt_65.py b/spirit/game/scripts/cards/SWSH3/Dracozolt_65.py index f00fd39..ba56243 100644 --- a/spirit/game/scripts/cards/SWSH3/Dracozolt_65.py +++ b/spirit/game/scripts/cards/SWSH3/Dracozolt_65.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import boost_own_next_turn +from spirit.game.card_effects.attacks_common import lock_all_attacks + + +async def giga_impact(ctx): + """200 damage; this Pokemon can't attack during your next turn.""" + await ctx.deal_damage() + lock_all_attacks(ctx, ctx.attacker) + card = PokemonCardDef( guid="67b6b840-7de3-508d-a4b2-742011564715", @@ -24,14 +33,14 @@ card = PokemonCardDef( game_text="During your next turn, this Pok\u00e9mon's Amping Up attack does 90 more damage (before applying Weakness and Resistance).", cost={PokemonTypes.LIGHTNING: 1}, damage=30, - effect=unimplemented, + effect=boost_own_next_turn(90, attack_title="Amping Up"), ), Attack( title="Giga Impact", game_text="During your next turn, this Pok\u00e9mon can't attack.", cost={PokemonTypes.COLORLESS: 4}, damage=200, - effect=unimplemented, + effect=giga_impact, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Dugtrio_85.py b/spirit/game/scripts/cards/SWSH3/Dugtrio_85.py index fed4dbe..2b33002 100644 --- a/spirit/game/scripts/cards/SWSH3/Dugtrio_85.py +++ b/spirit/game/scripts/cards/SWSH3/Dugtrio_85.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import flip_protection card = PokemonCardDef( guid="91ff4ed5-3317-50ae-84c8-6cd150346b04", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, during your opponent's next turn, prevent all damage from and effects of attacks done to this Pok\u00e9mon.", cost={PokemonTypes.FIGHTING: 1}, damage=30, - effect=unimplemented, + effect=flip_protection(prevent=True, effects_too=True), ), Attack( title="Mud Bomb", diff --git a/spirit/game/scripts/cards/SWSH3/Electrike_58.py b/spirit/game/scripts/cards/SWSH3/Electrike_58.py index fa03f0f..22a2142 100644 --- a/spirit/game/scripts/cards/SWSH3/Electrike_58.py +++ b/spirit/game/scripts/cards/SWSH3/Electrike_58.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import self_energy_discard_attack card = PokemonCardDef( guid="e6129c46-2227-5fcd-beda-1024effd4404", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Discard all Energy from this Pok\u00e9mon.", cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 1}, damage=50, - effect=unimplemented, + effect=self_energy_discard_attack(all_energy=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Escavalier_124.py b/spirit/game/scripts/cards/SWSH3/Escavalier_124.py index 0e39bac..c65cc29 100644 --- a/spirit/game/scripts/cards/SWSH3/Escavalier_124.py +++ b/spirit/game/scripts/cards/SWSH3/Escavalier_124.py @@ -1,6 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +_FURY_CUTTER_BONUS = {1: 20, 2: 70, 3: 140} + + +async def fury_cutter(ctx): + results = await ctx.flip_coins(3, "Fury Cutter") + heads = sum(1 for r in results if r) + bonus = _FURY_CUTTER_BONUS.get(heads, 0) + await ctx.deal_damage(ctx.ability.damage + bonus) + card = PokemonCardDef( guid="04d1750c-3ff7-58b0-bff6-08eb5fde6516", key="SWSH3", @@ -26,7 +35,7 @@ card = PokemonCardDef( cost={PokemonTypes.METAL: 1}, damage=10, damage_operator="+", - effect=unimplemented, + effect=fury_cutter, ), Attack( title="Seashell Attack", diff --git a/spirit/game/scripts/cards/SWSH3/EternatusVMAX_117.py b/spirit/game/scripts/cards/SWSH3/EternatusVMAX_117.py index ce940e6..feba508 100644 --- a/spirit/game/scripts/cards/SWSH3/EternatusVMAX_117.py +++ b/spirit/game/scripts/cards/SWSH3/EternatusVMAX_117.py @@ -1,5 +1,35 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_in_play +from spirit.game.card_effects.trainers import is_darkness_pokemon +from spirit.game.models.board import PokemonEntity +from spirit.game.session.passives import Passive + + +def _all_darkness_in_play(carrier) -> bool: + player_entity = carrier.parent.parent if carrier.parent else None + if player_entity is None: + return False + mons = [ + c for area in player_entity.children + if area.get_attribute(AttrID.NAME) in ("bench", "activePokemonArea") + for c in area.children if isinstance(c, PokemonEntity) + ] + return bool(mons) and all(is_darkness_pokemon(m) for m in mons) + + +class EternalZonePassive(Passive): + """While every one of the owner's in-play Pokemon is Darkness type, Bench + capacity is 8 (auto-enforced back down to 5 when it stops applying). The + accompanying "can't put non-Darkness Pokemon into play" restriction has no + engine hook for Pokemon-from-hand plays yet (play_locked only gates + Energy/Trainer offers) and is not enforced here.""" + + def bench_capacity(self, player_id, carrier): + if player_id != carrier.owning_player_id: + return None + return 8 if _all_darkness_in_play(carrier) else None + card = PokemonCardDef( guid="555f9284-d28c-56a0-b3f9-915d78cfbcb6", @@ -22,7 +52,7 @@ card = PokemonCardDef( Ability( title="Eternal Zone", game_text="If all of your Pok\u00e9mon in play are Darkness type, you can have up to 8 Pok\u00e9mon on your Bench, and you can't put non-Darkness Pok\u00e9mon into play. (If this Ability stops working, discard Pok\u00e9mon from your Bench until you have 5.)", - effect=unimplemented, + passive=EternalZonePassive(), ), Attack( title="Dread End", @@ -30,7 +60,7 @@ card = PokemonCardDef( cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 1}, damage=30, damage_operator="x", - effect=unimplemented, + effect=damage_per(count_in_play("mine", is_darkness_pokemon), 30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/EternatusVMAX_192.py b/spirit/game/scripts/cards/SWSH3/EternatusVMAX_192.py index 537c8f0..c237ce6 100644 --- a/spirit/game/scripts/cards/SWSH3/EternatusVMAX_192.py +++ b/spirit/game/scripts/cards/SWSH3/EternatusVMAX_192.py @@ -1,5 +1,35 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_in_play +from spirit.game.card_effects.trainers import is_darkness_pokemon +from spirit.game.models.board import PokemonEntity +from spirit.game.session.passives import Passive + + +def _all_darkness_in_play(carrier) -> bool: + player_entity = carrier.parent.parent if carrier.parent else None + if player_entity is None: + return False + mons = [ + c for area in player_entity.children + if area.get_attribute(AttrID.NAME) in ("bench", "activePokemonArea") + for c in area.children if isinstance(c, PokemonEntity) + ] + return bool(mons) and all(is_darkness_pokemon(m) for m in mons) + + +class EternalZonePassive(Passive): + """While every one of the owner's in-play Pokemon is Darkness type, Bench + capacity is 8 (auto-enforced back down to 5 when it stops applying). The + accompanying "can't put non-Darkness Pokemon into play" restriction has no + engine hook for Pokemon-from-hand plays yet (play_locked only gates + Energy/Trainer offers) and is not enforced here.""" + + def bench_capacity(self, player_id, carrier): + if player_id != carrier.owning_player_id: + return None + return 8 if _all_darkness_in_play(carrier) else None + card = PokemonCardDef( guid="f730977d-1065-587d-96a1-209b28d6cedc", @@ -22,7 +52,7 @@ card = PokemonCardDef( Ability( title="Eternal Zone", game_text="If all of your Pok\u00e9mon in play are Darkness type, you can have up to 8 Pok\u00e9mon on your Bench, and you can't put non-Darkness Pok\u00e9mon into play. (If this Ability stops working, discard Pok\u00e9mon from your Bench until you have 5.)", - effect=unimplemented, + passive=EternalZonePassive(), ), Attack( title="Dread End", @@ -30,7 +60,7 @@ card = PokemonCardDef( cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 1}, damage=30, damage_operator="x", - effect=unimplemented, + effect=damage_per(count_in_play("mine", is_darkness_pokemon), 30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/EternatusV_116.py b/spirit/game/scripts/cards/SWSH3/EternatusV_116.py index f32a19d..098a291 100644 --- a/spirit/game/scripts/cards/SWSH3/EternatusV_116.py +++ b/spirit/game/scripts/cards/SWSH3/EternatusV_116.py @@ -1,5 +1,34 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, defender_is_vmax +from spirit.game.card_effects.trainers import is_energy_card + + +def _is_darkness_energy(card) -> bool: + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_energy_card(card) and PokemonTypes.DARKNESS.value in types + + +async def power_accelerator(ctx): + """30 damage. You may attach a Darkness Energy from hand to 1 Benched Pokémon.""" + await ctx.deal_damage(30) + energies = [c for c in ctx.hand() if _is_darkness_energy(c)] + if not energies or not ctx.my_bench(): + return + if not await ctx.ask_yes_no( + "Attach a Darkness Energy card from your hand to 1 of your Benched Pokémon?" + ): + return + picked = await ctx.choose_cards( + energies, 1, minimum=1, prompt="Choose a Darkness Energy card to attach" + ) + if not picked: + return + target = await ctx.choose_pokemon( + ctx.my_bench(), "Choose the Benched Pokémon to attach it to" + ) + if target is not None: + await ctx.attach_energy(picked[0], target) card = PokemonCardDef( guid="77e88db9-bb74-530a-a104-bcd6d3a3a87d", @@ -23,7 +52,7 @@ card = PokemonCardDef( game_text="You may attach a Darkness Energy card from your hand to 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=power_accelerator, ), Attack( title="Dynamax Cannon", @@ -31,7 +60,7 @@ card = PokemonCardDef( cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 3}, damage=120, damage_operator="+", - effect=unimplemented, + effect=bonus_if(defender_is_vmax, 120), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/FamiliarBell_161.py b/spirit/game/scripts/cards/SWSH3/FamiliarBell_161.py index 2d68042..f919974 100644 --- a/spirit/game/scripts/cards/SWSH3/FamiliarBell_161.py +++ b/spirit/game/scripts/cards/SWSH3/FamiliarBell_161.py @@ -1,6 +1,35 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.card_effects.support_common import requires_discard +from spirit.game.data_utils import ItemCardDef, def_for +from spirit.game.session.effects import is_pokemon_card from spirit.game.attributes import Rarities + +async def familiar_bell(ctx): + """Search your deck for a Pokemon with the same name as a Pokemon in your + discard pile, reveal it, and put it into your hand. Then, shuffle.""" + discard_names = { + def_for(c.archetype_id).display_name + for c in ctx.discard_pile() + if is_pokemon_card(c) and def_for(c.archetype_id) is not None + } + discard_names.discard(None) + if not discard_names: + return + + def matches(card): + if not is_pokemon_card(card): + return False + definition = def_for(card.archetype_id) + return definition is not None and definition.display_name in discard_names + + picks = await ctx.search_deck( + matches, count=1, minimum=0, + prompt="Choose a Pokémon with the same name as one in your discard pile.", + ) + await ctx.put_in_hand(picks, reveal=True) + await ctx.shuffle_deck() + + card = ItemCardDef( guid="70864958-656e-5a6e-91c3-25614cc5bf30", key="SWSH3", @@ -11,5 +40,6 @@ card = ItemCardDef( collector_number=161, set_code="SWSH3", rarity=Rarities.Uncommon, - effect=unimplemented + condition=requires_discard(is_pokemon_card), + effect=familiar_bell, ) diff --git a/spirit/game/scripts/cards/SWSH3/Feebas_38.py b/spirit/game/scripts/cards/SWSH3/Feebas_38.py index 85656ed..5137b2e 100644 --- a/spirit/game/scripts/cards/SWSH3/Feebas_38.py +++ b/spirit/game/scripts/cards/SWSH3/Feebas_38.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import heal_attack card = PokemonCardDef( guid="620ffc5e-0301-525e-9cca-dda3588caad3", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Nap", game_text="Heal 20 damage from this Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=heal_attack(20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Fletchinder_31.py b/spirit/game/scripts/cards/SWSH3/Fletchinder_31.py index 6142a02..2000d6c 100644 --- a/spirit/game/scripts/cards/SWSH3/Fletchinder_31.py +++ b/spirit/game/scripts/cards/SWSH3/Fletchinder_31.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="c541a0e7-c7c9-5fc7-a6ac-9d043cbdc492", @@ -25,7 +26,7 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Burned.", cost={PokemonTypes.COLORLESS: 1}, damage=20, - effect=unimplemented, + effect=condition_attack(SpecialConditions.BURNED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Flygon_91.py b/spirit/game/scripts/cards/SWSH3/Flygon_91.py index 6923442..e5e5c6a 100644 --- a/spirit/game/scripts/cards/SWSH3/Flygon_91.py +++ b/spirit/game/scripts/cards/SWSH3/Flygon_91.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import ( + no_retreat_passive, is_in_active_spot, opposing_active, apply_protection, +) + + +async def desert_geyser(ctx): + """If your opponent has a Stadium in play, discard it. If you discarded + a Stadium in this way, during your opponent's next turn, prevent all + damage from and effects of attacks done to this Pokemon.""" + await ctx.deal_damage() + stadium = ctx.stadium_in_play() + if stadium is not None and stadium.owning_player_id == ctx.opponent_id: + discarded = await ctx.discard_stadium() + if discarded is not None: + await apply_protection(ctx, target=ctx.attacker, prevent=True, effects_too=True) + card = PokemonCardDef( guid="79e558c4-d9f2-5639-a2bb-bd4bc241d998", @@ -22,14 +38,16 @@ card = PokemonCardDef( Ability( title="Labyrinth of Sand", game_text="As long as this Pok\u00e9mon is in the Active Spot, your opponent's Active Pok\u00e9mon can't retreat.", - effect=unimplemented, + passive=no_retreat_passive( + lambda p, c: opposing_active(p, c) and is_in_active_spot(c) + ), ), Attack( title="Desert Geyser", game_text="If your opponent has a Stadium in play, discard it. If you discarded a Stadium in this way, during your opponent's next turn, prevent all damage from and effects of attacks done to this Pok\u00e9mon.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 2}, damage=130, - effect=unimplemented, + effect=desert_geyser, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Furret_136.py b/spirit/game/scripts/cards/SWSH3/Furret_136.py index 202b8cb..bae1fa8 100644 --- a/spirit/game/scripts/cards/SWSH3/Furret_136.py +++ b/spirit/game/scripts/cards/SWSH3/Furret_136.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_or_nothing +from spirit.game.card_effects.support_common import draw_attack card = PokemonCardDef( guid="fef11338-5207-549f-b604-befb3c76f2c7", @@ -23,14 +25,14 @@ card = PokemonCardDef( title="Feelin' Fine", game_text="Draw 3 cards.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=draw_attack(3), ), Attack( title="Tail Smash", game_text="Flip a coin. If tails, this attack does nothing.", cost={PokemonTypes.COLORLESS: 1}, damage=90, - effect=unimplemented, + effect=flip_or_nothing(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/GalarianDarmanitan_28.py b/spirit/game/scripts/cards/SWSH3/GalarianDarmanitan_28.py index ee54197..efaa8fb 100644 --- a/spirit/game/scripts/cards/SWSH3/GalarianDarmanitan_28.py +++ b/spirit/game/scripts/cards/SWSH3/GalarianDarmanitan_28.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import energy_provides_type + + +async def frozen_heat(ctx): + water_energy = [e for e in ctx.attached_energies(ctx.attacker) + if energy_provides_type(e, PokemonTypes.WATER.value)] + bonus = 0 + if water_energy and await ctx.ask_yes_no( + "Discard all Water Energy from this Pokémon?"): + await ctx.discard_energy_from( + ctx.attacker, 99, + predicate=lambda c: energy_provides_type(c, PokemonTypes.WATER.value), + ) + bonus = 60 + await ctx.deal_damage(110 + bonus) + card = PokemonCardDef( guid="7c757cda-c44f-5d82-871d-5a2df8fb6b04", @@ -30,7 +46,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, damage=110, damage_operator="+", - effect=unimplemented, + effect=frozen_heat, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/GalarianDarmanitan_44.py b/spirit/game/scripts/cards/SWSH3/GalarianDarmanitan_44.py index fc7262f..6643ecb 100644 --- a/spirit/game/scripts/cards/SWSH3/GalarianDarmanitan_44.py +++ b/spirit/game/scripts/cards/SWSH3/GalarianDarmanitan_44.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import spread_damage card = PokemonCardDef( guid="5f1aa1ff-0343-5236-955f-c6ba461d7f7a", @@ -24,14 +25,14 @@ card = PokemonCardDef( game_text="This attack also does 10 damage to each of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=80, - effect=unimplemented, + effect=spread_damage(10, also_base=True), ), Attack( title="Crushing Headbutt", game_text="During your next turn, this Pok\u00e9mon can't use Crushing Headbutt.", cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 2}, damage=170, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/GalarianMrMime_35.py b/spirit/game/scripts/cards/SWSH3/GalarianMrMime_35.py index cd24c17..26a54bf 100644 --- a/spirit/game/scripts/cards/SWSH3/GalarianMrMime_35.py +++ b/spirit/game/scripts/cards/SWSH3/GalarianMrMime_35.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import protect_next_turn card = PokemonCardDef( guid="7a1a2254-2003-51d7-b6a1-1d4f7b40faeb", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Reflect", game_text="During your opponent's next turn, this Pok\u00e9mon takes 30 less damage from attacks (after applying Weakness and Resistance).", cost={PokemonTypes.WATER: 1}, - effect=unimplemented, + effect=protect_next_turn(reduce=30), ), Attack( title="Icy Snow", diff --git a/spirit/game/scripts/cards/SWSH3/GalarianMrRime_36.py b/spirit/game/scripts/cards/SWSH3/GalarianMrRime_36.py index 0d73bcc..6497264 100644 --- a/spirit/game/scripts/cards/SWSH3/GalarianMrRime_36.py +++ b/spirit/game/scripts/cards/SWSH3/GalarianMrRime_36.py @@ -1,5 +1,54 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_discard, has_attack_titled +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, GameSequence + + +def _shuffle_dance_condition(board, player_id, pokemon): + opponent = next((p for p in board.player_ids if p != player_id), None) + if opponent is None: + return False + prizes = board.find_player_area(opponent, "prizePile") + deck = board.find_player_area(opponent, "deck") + return bool(prizes and prizes.children and deck and deck.children) + + +async def shuffle_dance(ctx): + """Switch 1 of the opponent's face-down Prizes with the top card of their + deck; both cards stay face down (no intros to either viewer).""" + session = ctx.session + prize_area = ctx.board.find_player_area(ctx.opponent_id, "prizePile") + deck = ctx.board.find_player_area(ctx.opponent_id, "deck") + if not prize_area or not prize_area.children or not deck or not deck.children: + return + await ctx.flush_choreography() + prize_ids = [c.entity_id for c in prize_area.children] + picked = await session._prompt_prize_pick( + ctx.player_id, prize_ids, 1, + prompt="Choose 1 of your opponent's Prize cards to switch with the top card of their deck.", + prize_area=prize_area, + ) + prize_card = ctx.board.get_entity(picked[0]) if picked else None + if prize_card is None: + return + slot = prize_card.board_slot + if slot is None: + slot = list(prize_area.children).index(prize_card) + top_card = deck.children[-1] + moves = [] + if ctx.board.move_card(prize_card.entity_id, deck.entity_id): + moves.append(session._entity_moved_msg( + prize_card.entity_id, deck.entity_id, len(deck.children) - 1)) + if ctx.board.move_card(top_card.entity_id, prize_area.entity_id, slot): + moves.append(session._entity_moved_msg( + top_card.entity_id, prize_area.entity_id, slot)) + if moves: + # WithOpenPrizeCards tears the pick fan down (d.t cancels the explore + # command); the slot is refilled in the same bracket so gaps are unchanged. + await session.send_game_sequence( + list(session.players.values()), + GameSequence.WITH_OPEN_PRIZE_CARDS, moves) + ctx.visual_targets = [prize_area.entity_id, deck.entity_id] + card = PokemonCardDef( guid="f6bc1751-21f2-5e0e-bf61-04c023ebab4d", @@ -22,7 +71,9 @@ card = PokemonCardDef( Ability( title="Shuffle Dance", game_text="Once during your turn, you may switch 1 of your opponent's face-down Prize cards with the top card of their deck. (The cards stay face down.)", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=_shuffle_dance_condition, + effect=shuffle_dance, ), Attack( title="Mad Party", @@ -30,7 +81,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=20, damage_operator="x", - effect=unimplemented, + effect=damage_per(count_discard("mine", has_attack_titled("Mad Party")), 20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/GalarianSirfetchd_98.py b/spirit/game/scripts/cards/SWSH3/GalarianSirfetchd_98.py index c3da108..65ebd5a 100644 --- a/spirit/game/scripts/cards/SWSH3/GalarianSirfetchd_98.py +++ b/spirit/game/scripts/cards/SWSH3/GalarianSirfetchd_98.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.legal_actions import LOCK_UNTIL_LEAVES_ACTIVE + + +async def meteor_assault(ctx): + """180. Can't use Meteor Assault again until this Pokemon leaves Active.""" + await ctx.deal_damage() + ctx.session.turn_state.attack_locks[ + (ctx.attacker.entity_id, ctx.ability.ability_id) + ] = LOCK_UNTIL_LEAVES_ACTIVE + card = PokemonCardDef( guid="03a18e8d-0e6f-50db-a4cc-2bad0044ca7f", @@ -29,7 +39,7 @@ card = PokemonCardDef( game_text="This Pok\u00e9mon can't use Meteor Assault again until it leaves the Active Spot.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 2}, damage=180, - effect=unimplemented, + effect=meteor_assault, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/GalarianSlowbroV_99.py b/spirit/game/scripts/cards/SWSH3/GalarianSlowbroV_99.py index f9b5e59..deacfa4 100644 --- a/spirit/game/scripts/cards/SWSH3/GalarianSlowbroV_99.py +++ b/spirit/game/scripts/cards/SWSH3/GalarianSlowbroV_99.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.pokemon import in_active_spot + + +async def rapid_fire_poison(ctx): + """Once during your turn, if this Pokemon is in the Active Spot, you may + make your opponent's Active Pokemon Poisoned.""" + if await ctx.ask_yes_no("Make your opponent's Active Pokémon Poisoned?"): + await ctx.apply_special_condition(ctx.defender, SpecialConditions.POISONED) + card = PokemonCardDef( guid="6b18af02-c73b-5603-a12e-195280311ac7", @@ -20,15 +30,17 @@ card = PokemonCardDef( abilities=[ Ability( title="Rapid-Fire Poison", - game_text="Once during your turn, if this Pok\u00e9mon is in the Active Spot, you may make your opponent's Active Pok\u00e9mon Poisoned.", - effect=unimplemented, + game_text="Once during your turn, if this Pokémon is in the Active Spot, you may make your opponent's Active Pokémon Poisoned.", + activation=Activations.ONCE_PER_TURN, + condition=in_active_spot, + effect=rapid_fire_poison, ), Attack( title="Tripping Shot", - game_text="During your opponent's next turn, the Defending Pok\u00e9mon can't retreat.", + game_text="During your opponent's next turn, the Defending Pokémon can't retreat.", cost={PokemonTypes.DARKNESS: 2, PokemonTypes.COLORLESS: 1}, damage=130, - effect=unimplemented, + effect=condition_attack(no_retreat=True), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH3/GalarianStunfiskV_128.py b/spirit/game/scripts/cards/SWSH3/GalarianStunfiskV_128.py index 094d4de..bfef8bc 100644 --- a/spirit/game/scripts/cards/SWSH3/GalarianStunfiskV_128.py +++ b/spirit/game/scripts/cards/SWSH3/GalarianStunfiskV_128.py @@ -1,5 +1,56 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import ( + PokemonCardDef, Attack, Ability, Triggers, ability_id_for, ABILITIES_BY_ID, +) +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import energy_provides_type +from spirit.game.session.passives import Passive + +_GUID = "51e663e4-700f-5619-a1b0-ffbe09ca6c2f" + + +class MetalSkinPassive(Passive): + """This Pokemon gets +20 max HP for each Metal Energy attached to it.""" + + def max_hp_bonus(self, pokemon, carrier): + if pokemon is not carrier: + return 0 + count = sum(1 for c in carrier.children + if energy_provides_type(c, PokemonTypes.METAL.value)) + return 20 * count + + +async def _trapping_bite_trigger(ctx): + armed_until = getattr(ctx.source, "_trapping_bite_until", None) + if armed_until is None or ctx.session.turn_state.turn_number > armed_until: + return + attacker = ctx.damaged_by + if attacker is None: + return + await ctx.deal_damage(120, target=attacker, apply_modifiers=False, as_counters=True) + + +TRAPPING_BITE_GRANTED = Ability( + title="Trapping Bite", + trigger=Triggers.ON_DAMAGED_BY_ATTACK, + effect=_trapping_bite_trigger, +) +TRAPPING_BITE_GRANTED.ability_id = ability_id_for(_GUID, 90) +ABILITIES_BY_ID[TRAPPING_BITE_GRANTED.ability_id] = TRAPPING_BITE_GRANTED + + +async def trapping_bite(ctx): + """60. During the opponent's next turn, if this Pokemon is damaged by an + attack (even if Knocked Out), put 12 damage counters on the attacker.""" + await ctx.deal_damage() + ctx.attacker._trapping_bite_until = ctx.session.turn_state.turn_number + 1 + entries = list(ctx.attacker.get_attribute(AttrID.PIE_ABILITIES) or []) + if all(e.get("abilityID") != TRAPPING_BITE_GRANTED.ability_id + for e in entries if isinstance(e, dict)): + entries.append(TRAPPING_BITE_GRANTED.to_dict()) + await ctx.session._broadcast_entity_attribute( + ctx.attacker, AttrID.PIE_ABILITIES, entries + ) + card = PokemonCardDef( guid="51e663e4-700f-5619-a1b0-ffbe09ca6c2f", @@ -22,14 +73,14 @@ card = PokemonCardDef( Ability( title="Metal Skin", game_text="This Pok\u00e9mon gets +20 HP for each Metal Energy attached to it.", - effect=unimplemented, + passive=MetalSkinPassive(), ), Attack( title="Trapping Bite", game_text="During your opponent's next turn, if this Pok\u00e9mon is damaged by an attack (even if it is Knocked Out), put 12 damage counters on the Attacking Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 2}, damage=60, - effect=unimplemented, + effect=trapping_bite, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/GalarianStunfiskV_184.py b/spirit/game/scripts/cards/SWSH3/GalarianStunfiskV_184.py index bf54c72..8e60e80 100644 --- a/spirit/game/scripts/cards/SWSH3/GalarianStunfiskV_184.py +++ b/spirit/game/scripts/cards/SWSH3/GalarianStunfiskV_184.py @@ -1,5 +1,56 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import ( + PokemonCardDef, Attack, Ability, Triggers, ability_id_for, ABILITIES_BY_ID, +) +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import energy_provides_type +from spirit.game.session.passives import Passive + +_GUID = "00eef7ad-810c-535d-844a-3930b9c53ba1" + + +class MetalSkinPassive(Passive): + """This Pokemon gets +20 max HP for each Metal Energy attached to it.""" + + def max_hp_bonus(self, pokemon, carrier): + if pokemon is not carrier: + return 0 + count = sum(1 for c in carrier.children + if energy_provides_type(c, PokemonTypes.METAL.value)) + return 20 * count + + +async def _trapping_bite_trigger(ctx): + armed_until = getattr(ctx.source, "_trapping_bite_until", None) + if armed_until is None or ctx.session.turn_state.turn_number > armed_until: + return + attacker = ctx.damaged_by + if attacker is None: + return + await ctx.deal_damage(120, target=attacker, apply_modifiers=False, as_counters=True) + + +TRAPPING_BITE_GRANTED = Ability( + title="Trapping Bite", + trigger=Triggers.ON_DAMAGED_BY_ATTACK, + effect=_trapping_bite_trigger, +) +TRAPPING_BITE_GRANTED.ability_id = ability_id_for(_GUID, 90) +ABILITIES_BY_ID[TRAPPING_BITE_GRANTED.ability_id] = TRAPPING_BITE_GRANTED + + +async def trapping_bite(ctx): + """60. During the opponent's next turn, if this Pokemon is damaged by an + attack (even if Knocked Out), put 12 damage counters on the attacker.""" + await ctx.deal_damage() + ctx.attacker._trapping_bite_until = ctx.session.turn_state.turn_number + 1 + entries = list(ctx.attacker.get_attribute(AttrID.PIE_ABILITIES) or []) + if all(e.get("abilityID") != TRAPPING_BITE_GRANTED.ability_id + for e in entries if isinstance(e, dict)): + entries.append(TRAPPING_BITE_GRANTED.to_dict()) + await ctx.session._broadcast_entity_attribute( + ctx.attacker, AttrID.PIE_ABILITIES, entries + ) + card = PokemonCardDef( guid="00eef7ad-810c-535d-844a-3930b9c53ba1", @@ -22,14 +73,14 @@ card = PokemonCardDef( Ability( title="Metal Skin", game_text="This Pok\u00e9mon gets +20 HP for each Metal Energy attached to it.", - effect=unimplemented, + passive=MetalSkinPassive(), ), Attack( title="Trapping Bite", game_text="During your opponent's next turn, if this Pok\u00e9mon is damaged by an attack (even if it is Knocked Out), put 12 damage counters on the Attacking Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 2}, damage=60, - effect=unimplemented, + effect=trapping_bite, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/GlimwoodTangle_162.py b/spirit/game/scripts/cards/SWSH3/GlimwoodTangle_162.py index 3e9eee2..cb7bad3 100644 --- a/spirit/game/scripts/cards/SWSH3/GlimwoodTangle_162.py +++ b/spirit/game/scripts/cards/SWSH3/GlimwoodTangle_162.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import StadiumCardDef, unimplemented +from spirit.game.data_utils import StadiumCardDef from spirit.game.attributes import Rarities +from spirit.game.session.passives import Passive + + +class _GlimwoodTanglePassive(Passive): + """Once during each player's turn, after that player flips any coins for + an attack, they may ignore all results and begin flipping again.""" + + def offers_attack_coin_reroll(self, player_id, carrier): + return True + card = StadiumCardDef( guid="4d910b75-6845-5f9b-99b1-e6b12b65af27", @@ -11,5 +21,5 @@ card = StadiumCardDef( collector_number=162, set_code="SWSH3", rarity=Rarities.Uncommon, - effect=unimplemented + passive=_GlimwoodTanglePassive(), ) diff --git a/spirit/game/scripts/cards/SWSH3/Golisopod_18.py b/spirit/game/scripts/cards/SWSH3/Golisopod_18.py index 9567971..9d0633e 100644 --- a/spirit/game/scripts/cards/SWSH3/Golisopod_18.py +++ b/spirit/game/scripts/cards/SWSH3/Golisopod_18.py @@ -1,6 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import damage_per, count_in_play +from spirit.game.card_effects.support_common import switch_self_attack +from spirit.game.card_effects.pokemon import is_pokemon_gx +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, is_pokemon_v from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +def _is_v_or_gx(pokemon): + return is_pokemon_v(pokemon.archetype_id) or is_pokemon_gx(pokemon.archetype_id) + card = PokemonCardDef( guid="14780490-9698-5d31-9e28-977068e717df", key="SWSH3", @@ -25,14 +32,14 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=30, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_in_play("opponent", _is_v_or_gx), 50, base=30), ), Attack( title="Smash Turn", game_text="Switch this Pok\u00e9mon with 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 2}, damage=70, - effect=unimplemented, + effect=switch_self_attack(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Gothita_73.py b/spirit/game/scripts/cards/SWSH3/Gothita_73.py index c0c46c4..a689a29 100644 --- a/spirit/game/scripts/cards/SWSH3/Gothita_73.py +++ b/spirit/game/scripts/cards/SWSH3/Gothita_73.py @@ -1,6 +1,28 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def fortunate_eye(ctx): + """Look at the top 5 cards of your opponent's deck and put them back in + any order.""" + top = ctx.deck_top(5, ctx.opponent_id) + if len(top) <= 1: + return + picked_ids = await ctx.session.prompt_card_chooser( + ctx.player_id, ctx.source.entity_id, top, len(top), minimum=len(top), + prompt="Put the cards back in any order.", ordered=True, + ) + by_id = {c.entity_id: c for c in top} + order = [by_id[i] for i in picked_ids if i in by_id] + for card in top: + if card not in order: + order.append(card) + deck = ctx.board.find_player_area(ctx.opponent_id, "deck") + for card in order: + deck.children.remove(card) + for card in reversed(order): + deck.children.append(card) + card = PokemonCardDef( guid="9de00fec-28e5-5a73-a7cd-96cffa0f78d1", key="SWSH3", @@ -23,7 +45,7 @@ card = PokemonCardDef( title="Fortunate Eye", game_text="Look at the top 5 cards of your opponent's deck and put them back in any order.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=fortunate_eye, ), Attack( title="Gentle Slap", diff --git a/spirit/game/scripts/cards/SWSH3/Gothitelle_75.py b/spirit/game/scripts/cards/SWSH3/Gothitelle_75.py index f3eacf0..6f26b39 100644 --- a/spirit/game/scripts/cards/SWSH3/Gothitelle_75.py +++ b/spirit/game/scripts/cards/SWSH3/Gothitelle_75.py @@ -1,5 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +import random + +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack + + +async def bend(ctx): + """Choose 2 random cards from the opponent's hand, reveal, shuffle into their deck.""" + await ctx.deal_damage() + hand = ctx.hand(ctx.opponent_id) + if not hand: + return + picks = random.sample(hand, min(2, len(hand))) + await ctx.reveal_cards(picks, to_player=ctx.player_id) + await ctx.shuffle_into_deck(picks, ctx.opponent_id) + card = PokemonCardDef( guid="1d5ee57b-d628-5b22-bf71-d7c83c7bc4c8", @@ -25,14 +40,14 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Confused.", cost={PokemonTypes.PSYCHIC: 1}, damage=40, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED), ), Attack( title="Bend", game_text="Choose 2 random cards from your opponent's hand. Your opponent reveals those cards and shuffles them into their deck.", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=90, - effect=unimplemented, + effect=bend, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Gothorita_74.py b/spirit/game/scripts/cards/SWSH3/Gothorita_74.py index 07ce5f1..f9c5927 100644 --- a/spirit/game/scripts/cards/SWSH3/Gothorita_74.py +++ b/spirit/game/scripts/cards/SWSH3/Gothorita_74.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +import random + +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage + + +async def whiny_voice(ctx): + """Choose a random card from the opponent's hand, reveal it, shuffle it into their deck.""" + hand = ctx.hand(ctx.opponent_id) + if not hand: + return + card = random.choice(hand) + await ctx.reveal_cards([card], to_player=ctx.player_id) + await ctx.shuffle_into_deck([card], ctx.opponent_id) card = PokemonCardDef( guid="e234aaa3-e1e1-5c6f-8135-0f1a1b384c2f", @@ -24,7 +37,7 @@ card = PokemonCardDef( title="Whiny Voice", game_text="Choose a random card from your opponent's hand. Your opponent reveals that card and shuffles it into their deck.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=whiny_voice, ), Attack( title="Double Spin", @@ -32,7 +45,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=30, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=2, per_heads=30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Granbull_71.py b/spirit/game/scripts/cards/SWSH3/Granbull_71.py index e1d9d01..33add35 100644 --- a/spirit/game/scripts/cards/SWSH3/Granbull_71.py +++ b/spirit/game/scripts/cards/SWSH3/Granbull_71.py @@ -1,6 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def jaw_lock(ctx): + """50. During your opponent's next turn, the Defending Pokemon can't + retreat.""" + await ctx.deal_damage() + defender = ctx.defender + if defender is not None and not ctx.effects_blocked(defender): + ctx.lock_retreat(defender) + card = PokemonCardDef( guid="5173239b-c785-59ae-ba72-38324af83844", key="SWSH3", @@ -24,7 +33,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, the Defending Pok\u00e9mon can't retreat.", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=50, - effect=unimplemented, + effect=jaw_lock, ), Attack( title="Headbutt Bounce", diff --git a/spirit/game/scripts/cards/SWSH3/Greedent_153.py b/spirit/game/scripts/cards/SWSH3/Greedent_153.py index 73abb0f..f9369d8 100644 --- a/spirit/game/scripts/cards/SWSH3/Greedent_153.py +++ b/spirit/game/scripts/cards/SWSH3/Greedent_153.py @@ -1,5 +1,23 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID, TrainerType +from spirit.game.card_effects.support_common import remove_self_from_play + +_TOOL_TYPES = (TrainerType.POKEMON_TOOL.value, TrainerType.POKEMON_TOOL_F.value) + + +def _is_tool_card(card): + return card.get_attribute(AttrID.TRAINER_TYPE) in _TOOL_TYPES + + +async def scrape_off(ctx): + """20. Before doing damage, discard all Pokemon Tools from the opponent's Active.""" + defender = ctx.defender + if defender is not None and not ctx.effects_blocked(defender): + tools = [c for c in defender.children if _is_tool_card(c)] + if tools: + await ctx.discard_cards(tools) + await ctx.deal_damage() + card = PokemonCardDef( guid="4132812c-deeb-5a84-bd17-c42d61511c63", @@ -24,14 +42,14 @@ card = PokemonCardDef( game_text="Before doing damage, discard all Pok\u00e9mon Tools from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, damage=20, - effect=unimplemented, + effect=scrape_off, ), Attack( title="Smack and Run", game_text="Put this Pok\u00e9mon and all attached cards into your hand.", cost={PokemonTypes.COLORLESS: 2}, damage=100, - effect=unimplemented, + effect=remove_self_from_play(destination="hand", with_attachments="same"), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Grimer_100.py b/spirit/game/scripts/cards/SWSH3/Grimer_100.py index 155c01e..4dc1c9f 100644 --- a/spirit/game/scripts/cards/SWSH3/Grimer_100.py +++ b/spirit/game/scripts/cards/SWSH3/Grimer_100.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import mill_attack card = PokemonCardDef( guid="8d7cb772-3170-5aa1-9b62-f37b89034121", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Stomp Off", game_text="Discard the top card of your opponent's deck.", cost={PokemonTypes.DARKNESS: 1}, - effect=unimplemented, + effect=mill_attack(1), ), Attack( title="Sludge Whirlpool", diff --git a/spirit/game/scripts/cards/SWSH3/GrimmsnarlVMAX_115.py b/spirit/game/scripts/cards/SWSH3/GrimmsnarlVMAX_115.py index 9cdc09d..f24bb62 100644 --- a/spirit/game/scripts/cards/SWSH3/GrimmsnarlVMAX_115.py +++ b/spirit/game/scripts/cards/SWSH3/GrimmsnarlVMAX_115.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import count_energy + + +async def g_max_drill(ctx): + attached = count_energy("self", energy_type=PokemonTypes.DARKNESS)(ctx) + extra = max(0, attached - 3) + bonus = min(100, extra * 50) + await ctx.deal_damage(170 + bonus) + card = PokemonCardDef( guid="ece27e6b-f231-58a3-ad29-8d5c8e8e27d3", @@ -25,7 +34,7 @@ card = PokemonCardDef( cost={PokemonTypes.DARKNESS: 3}, damage=170, damage_operator="+", - effect=unimplemented, + effect=g_max_drill, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/GrimmsnarlV_114.py b/spirit/game/scripts/cards/SWSH3/GrimmsnarlV_114.py index 1f4bca9..b724293 100644 --- a/spirit/game/scripts/cards/SWSH3/GrimmsnarlV_114.py +++ b/spirit/game/scripts/cards/SWSH3/GrimmsnarlV_114.py @@ -1,5 +1,23 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import energy_provides_type + + +async def spiky_knuckle(ctx): + """200. Put 2 Darkness Energy attached to this Pokemon into your hand.""" + await ctx.deal_damage() + energies = [ + e for e in ctx.attached_energies(ctx.source) + if energy_provides_type(e, PokemonTypes.DARKNESS) + ] + if not energies: + return + picks = await ctx.choose_cards( + energies, 2, + prompt="Choose up to 2 Darkness Energy to put into your hand.", + ) + await ctx.put_in_hand(picks, reveal=False) + card = PokemonCardDef( guid="f880c265-6c9a-52ea-8769-b64edcddd9e9", @@ -28,7 +46,7 @@ card = PokemonCardDef( game_text="Put 2 Darkness Energy attached to this Pok\u00e9mon into your hand.", cost={PokemonTypes.DARKNESS: 3}, damage=200, - effect=unimplemented, + effect=spiky_knuckle, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Heatran_25.py b/spirit/game/scripts/cards/SWSH3/Heatran_25.py index 5da515a..45073d4 100644 --- a/spirit/game/scripts/cards/SWSH3/Heatran_25.py +++ b/spirit/game/scripts/cards/SWSH3/Heatran_25.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack, damage_per, damage_counters_on card = PokemonCardDef( guid="84bdb973-3311-558a-8c25-bf479ce43697", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Burned.", cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=condition_attack(SpecialConditions.BURNED), ), Attack( title="Raging Flare", @@ -31,7 +32,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=80, damage_operator="+", - effect=unimplemented, + effect=damage_per(damage_counters_on("self"), 10, base=80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Hippopotas_93.py b/spirit/game/scripts/cards/SWSH3/Hippopotas_93.py index 939fb34..a175b96 100644 --- a/spirit/game/scripts/cards/SWSH3/Hippopotas_93.py +++ b/spirit/game/scripts/cards/SWSH3/Hippopotas_93.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="f48e49a9-47fa-592b-aa23-2ff6dcc3f485", @@ -29,7 +30,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 3, PokemonTypes.COLORLESS: 1}, damage=70, damage_operator="+", - effect=unimplemented, + effect=flip_damage(coins=2, bonus_per_heads=30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Hippowdon_94.py b/spirit/game/scripts/cards/SWSH3/Hippowdon_94.py index 932ce5e..8db34e5 100644 --- a/spirit/game/scripts/cards/SWSH3/Hippowdon_94.py +++ b/spirit/game/scripts/cards/SWSH3/Hippowdon_94.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="97619755-4783-5fb0-bbbc-56dc06784c35", @@ -25,7 +26,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=80, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=3, per_heads=80), ), Attack( title="Land Crush", diff --git a/spirit/game/scripts/cards/SWSH3/Hoopa_111.py b/spirit/game/scripts/cards/SWSH3/Hoopa_111.py index 8d48840..7b9de1f 100644 --- a/spirit/game/scripts/cards/SWSH3/Hoopa_111.py +++ b/spirit/game/scripts/cards/SWSH3/Hoopa_111.py @@ -1,6 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def assault_gate(ctx): + """If this Pokemon didn't move Bench->Active this turn, this attack does nothing; damage ignores Weakness.""" + if not ctx.entered_active_this_turn(ctx.attacker): + return + await ctx.deal_damage(ignore_weakness=True) + card = PokemonCardDef( guid="28c53b5c-0eb1-5a20-8faf-e532e7ef419e", key="SWSH3", @@ -23,7 +30,7 @@ card = PokemonCardDef( game_text="If this Pok\u00e9mon didn't move from the Bench to the Active Spot this turn, this attack does nothing. This attack's damage isn't affected by Weakness.", cost={PokemonTypes.DARKNESS: 1}, damage=90, - effect=unimplemented, + effect=assault_gate, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/HoundoomV_178.py b/spirit/game/scripts/cards/SWSH3/HoundoomV_178.py index ee706e9..a37803b 100644 --- a/spirit/game/scripts/cards/SWSH3/HoundoomV_178.py +++ b/spirit/game/scripts/cards/SWSH3/HoundoomV_178.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import bonus_if, condition_attack + + +def _benched_fire_has_damage(ctx): + for pokemon in ctx.my_bench(): + types = pokemon.get_attribute(AttrID.POKEMON_TYPES) or [] + if PokemonTypes.FIRE.value in types and \ + pokemon.get_attribute(AttrID.HP, 0) < ctx.max_hp(pokemon): + return True + return False + card = PokemonCardDef( guid="e852fdf7-4789-5c14-a8a0-2715368aa0aa", @@ -20,18 +31,18 @@ card = PokemonCardDef( abilities=[ Attack( title="Searing Flame", - game_text="Your opponent's Active Pok\u00e9mon is now Burned.", + game_text="Your opponent's Active Pokémon is now Burned.", cost={PokemonTypes.FIRE: 1}, damage=20, - effect=unimplemented, + effect=condition_attack(SpecialConditions.BURNED), ), Attack( title="Vengeful Flame", - game_text="If your Benched Fire Pok\u00e9mon have any damage counters on them, this attack does 100 more damage.", + game_text="If your Benched Fire Pokémon have any damage counters on them, this attack does 100 more damage.", cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=100, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_benched_fire_has_damage, 100), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH3/HoundoomV_21.py b/spirit/game/scripts/cards/SWSH3/HoundoomV_21.py index 6b71308..0e784a2 100644 --- a/spirit/game/scripts/cards/SWSH3/HoundoomV_21.py +++ b/spirit/game/scripts/cards/SWSH3/HoundoomV_21.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import bonus_if, condition_attack + + +def _benched_fire_has_damage(ctx): + for pokemon in ctx.my_bench(): + types = pokemon.get_attribute(AttrID.POKEMON_TYPES) or [] + if PokemonTypes.FIRE.value in types and \ + pokemon.get_attribute(AttrID.HP, 0) < ctx.max_hp(pokemon): + return True + return False + card = PokemonCardDef( guid="b5390f15-4b6f-521f-8da2-e26b96f23813", @@ -20,18 +31,18 @@ card = PokemonCardDef( abilities=[ Attack( title="Searing Flame", - game_text="Your opponent's Active Pok\u00e9mon is now Burned.", + game_text="Your opponent's Active Pokémon is now Burned.", cost={PokemonTypes.FIRE: 1}, damage=20, - effect=unimplemented, + effect=condition_attack(SpecialConditions.BURNED), ), Attack( title="Vengeful Flame", - game_text="If your Benched Fire Pok\u00e9mon have any damage counters on them, this attack does 100 more damage.", + game_text="If your Benched Fire Pokémon have any damage counters on them, this attack does 100 more damage.", cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=100, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_benched_fire_has_damage, 100), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH3/Hydreigon_110.py b/spirit/game/scripts/cards/SWSH3/Hydreigon_110.py index baf1df5..4641d41 100644 --- a/spirit/game/scripts/cards/SWSH3/Hydreigon_110.py +++ b/spirit/game/scripts/cards/SWSH3/Hydreigon_110.py @@ -1,5 +1,30 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.session.effects import is_special_energy +from spirit.game.card_effects.pokemon import is_energy_card + + +def _is_darkness_energy_card(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_energy_card(card) and not is_special_energy(card) and PokemonTypes.DARKNESS.value in types + + +def _dark_squall_condition(board, player_id, pokemon): + hand = board.find_player_area(player_id, "hand") + return bool(hand) and any(_is_darkness_energy_card(c) for c in hand.children) + + +async def dark_squall(ctx): + """As often as you like: attach a Darkness Energy card from hand to 1 of your Pokemon.""" + energies = [c for c in ctx.hand() if _is_darkness_energy_card(c)] + picked = await ctx.choose_cards(energies, 1, minimum=1, prompt="Choose a Darkness Energy card to attach") + if not picked: + return + targets = ctx.my_pokemon_in_play() + target = await ctx.choose_pokemon(targets, "Choose the Pokémon to attach it to") + if target is not None: + await ctx.attach_energy(picked[0], target) + card = PokemonCardDef( guid="570ffde2-d8d8-5985-92d7-79ad7505b434", @@ -22,7 +47,9 @@ card = PokemonCardDef( Ability( title="Dark Squall", game_text="As often as you like during your turn, you may attach a Darkness Energy card from your hand to 1 of your Pok\u00e9mon.", - effect=unimplemented, + activation=Activations.UNLIMITED, + condition=_dark_squall_condition, + effect=dark_squall, ), Attack( title="Pitch-Black Fangs", diff --git a/spirit/game/scripts/cards/SWSH3/Kangaskhan_133.py b/spirit/game/scripts/cards/SWSH3/Kangaskhan_133.py index 6b1f303..c37ddbf 100644 --- a/spirit/game/scripts/cards/SWSH3/Kangaskhan_133.py +++ b/spirit/game/scripts/cards/SWSH3/Kangaskhan_133.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if card = PokemonCardDef( guid="055d029d-7631-5b1c-8829-ce111fec5a63", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=30, damage_operator="+", - effect=unimplemented, + effect=bonus_if(lambda ctx: ctx.kos_suffered_last_turn() > 0, 90), ), Attack( title="Hammer In", diff --git a/spirit/game/scripts/cards/SWSH3/Karrablast_8.py b/spirit/game/scripts/cards/SWSH3/Karrablast_8.py index 3856dee..1ab00b2 100644 --- a/spirit/game/scripts/cards/SWSH3/Karrablast_8.py +++ b/spirit/game/scripts/cards/SWSH3/Karrablast_8.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="b06df0b4-70a2-5d5b-b741-1951bd2cfbca", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 1}, damage=20, damage_operator="x", - effect=unimplemented, + effect=flip_damage(until_tails=True, per_heads=20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Klang_126.py b/spirit/game/scripts/cards/SWSH3/Klang_126.py index 1bfa33c..115a580 100644 --- a/spirit/game/scripts/cards/SWSH3/Klang_126.py +++ b/spirit/game/scripts/cards/SWSH3/Klang_126.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.session.effects import is_pokemon_card + + +def _is_metal_pokemon(card) -> bool: + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_pokemon_card(card) and PokemonTypes.METAL.value in types card = PokemonCardDef( guid="8a5c09e9-0d53-55eb-b73d-d3e29ea22f2e", @@ -24,7 +31,10 @@ card = PokemonCardDef( title="Call for Backup", game_text="Search your deck for a Metal Pok\u00e9mon, reveal it, and put it into your hand. Then, shuffle your deck.", cost={PokemonTypes.METAL: 1}, - effect=unimplemented, + effect=search_to_hand( + _is_metal_pokemon, count=1, reveal=True, + prompt="Choose a Metal Pokémon to put into your hand.", + ), ), Attack( title="Spinning Attack", diff --git a/spirit/game/scripts/cards/SWSH3/Klink_125.py b/spirit/game/scripts/cards/SWSH3/Klink_125.py index ce508ba..b03a3e6 100644 --- a/spirit/game/scripts/cards/SWSH3/Klink_125.py +++ b/spirit/game/scripts/cards/SWSH3/Klink_125.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.session.effects import is_pokemon_card + + +def _is_metal_pokemon(card) -> bool: + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_pokemon_card(card) and PokemonTypes.METAL.value in types card = PokemonCardDef( guid="53f2dbdb-bf15-5fbf-9852-14584b1d7983", @@ -23,7 +30,10 @@ card = PokemonCardDef( title="Call for Backup", game_text="Search your deck for a Metal Pok\u00e9mon, reveal it, and put it into your hand. Then, shuffle your deck.", cost={PokemonTypes.METAL: 1}, - effect=unimplemented, + effect=search_to_hand( + _is_metal_pokemon, count=1, reveal=True, + prompt="Choose a Metal Pokémon to put into your hand.", + ), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Klinklang_127.py b/spirit/game/scripts/cards/SWSH3/Klinklang_127.py index ce4a55e..28669f1 100644 --- a/spirit/game/scripts/cards/SWSH3/Klinklang_127.py +++ b/spirit/game/scripts/cards/SWSH3/Klinklang_127.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities + +_KLINK_NAME = "Klink" +_KLANG_NAME = "Klang" + + +async def clockwork(ctx): + """200 damage; does nothing unless you have Klink AND Klang on your Bench.""" + bench_names = {p.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) for p in ctx.my_bench()} + if _KLINK_NAME not in bench_names or _KLANG_NAME not in bench_names: + return + await ctx.deal_damage() + card = PokemonCardDef( guid="d8fd31aa-1719-576d-b8e0-ab168a72645f", @@ -30,7 +42,7 @@ card = PokemonCardDef( game_text="If you don't have Klink and Klang on your Bench, this attack does nothing.", cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 2}, damage=200, - effect=unimplemented, + effect=clockwork, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Lairon_122.py b/spirit/game/scripts/cards/SWSH3/Lairon_122.py index 5547a47..c9c59d8 100644 --- a/spirit/game/scripts/cards/SWSH3/Lairon_122.py +++ b/spirit/game/scripts/cards/SWSH3/Lairon_122.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus card = PokemonCardDef( guid="4e9bb593-606d-518e-9557-2fb75d4ebf7f", @@ -26,7 +27,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=30, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(30), ), Attack( title="Lunge Out", diff --git a/spirit/game/scripts/cards/SWSH3/Larvesta_29.py b/spirit/game/scripts/cards/SWSH3/Larvesta_29.py index 8b7a8f5..eb91532 100644 --- a/spirit/game/scripts/cards/SWSH3/Larvesta_29.py +++ b/spirit/game/scripts/cards/SWSH3/Larvesta_29.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="1e8a6749-d05a-5e58-9c24-1069a9951e9f", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Singe", game_text="Your opponent's Active Pok\u00e9mon is now Burned.", cost={PokemonTypes.FIRE: 1}, - effect=unimplemented, + effect=condition_attack(SpecialConditions.BURNED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Liepard_107.py b/spirit/game/scripts/cards/SWSH3/Liepard_107.py index 864116e..cefeea9 100644 --- a/spirit/game/scripts/cards/SWSH3/Liepard_107.py +++ b/spirit/game/scripts/cards/SWSH3/Liepard_107.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.passives_common import condition_immunity_passive card = PokemonCardDef( guid="df23d0e6-bf8b-53e9-b6b0-737583a41048", @@ -22,7 +23,7 @@ card = PokemonCardDef( Ability( title="Limber", game_text="This Pok\u00e9mon can't be Paralyzed.", - effect=unimplemented, + passive=condition_immunity_passive(SpecialConditions.PARALYZED), ), Attack( title="Slashing Claw", diff --git a/spirit/game/scripts/cards/SWSH3/Lugia_140.py b/spirit/game/scripts/cards/SWSH3/Lugia_140.py index b42c5f6..c2dc114 100644 --- a/spirit/game/scripts/cards/SWSH3/Lugia_140.py +++ b/spirit/game/scripts/cards/SWSH3/Lugia_140.py @@ -1,6 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def energy_loop(ctx): + """120. Put an Energy attached to this Pokémon into your hand.""" + await ctx.deal_damage() + energies = ctx.attached_energies(ctx.source) + if not energies: + return + picks = await ctx.choose_cards( + energies, 1, minimum=1, + prompt="Choose an Energy card to put into your hand.", + ) + await ctx.put_in_hand(picks, reveal=False) + card = PokemonCardDef( guid="5a5bb12f-0e4f-5738-98f6-8324955741ac", key="SWSH3", @@ -29,7 +42,7 @@ card = PokemonCardDef( game_text="Put an Energy attached to this Pok\u00e9mon into your hand.", cost={PokemonTypes.COLORLESS: 3}, damage=120, - effect=unimplemented, + effect=energy_loop, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Lunatone_72.py b/spirit/game/scripts/cards/SWSH3/Lunatone_72.py index 04e4fd7..9a38c53 100644 --- a/spirit/game/scripts/cards/SWSH3/Lunatone_72.py +++ b/spirit/game/scripts/cards/SWSH3/Lunatone_72.py @@ -1,5 +1,31 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy + + +async def future_sight(ctx): + """Look at the top 4 cards of either player's deck, put back in any order.""" + choice = await ctx.choose( + "Look at the top 4 cards of which deck?", + ["Your deck", "Opponent's deck"], use_panel=False, + ) + target_pid = ctx.player_id if choice == 0 else ctx.opponent_id + top = ctx.deck_top(4, target_pid) + if len(top) <= 1: + return + order = await ctx.choose_cards( + top, len(top), minimum=len(top), ordered=True, player_id=ctx.player_id, + prompt="Put the cards back in any order", + ) + if not order: + order = top + deck = ctx.board.find_player_area(target_pid, "deck") + for card in order: + if card in deck.children: + deck.children.remove(card) + for card in reversed(order): + deck.children.append(card) + card = PokemonCardDef( guid="0f25213f-8b0b-555e-85de-1bb672a225a4", @@ -23,7 +49,7 @@ card = PokemonCardDef( title="Future Sight", game_text="Look at the top 4 cards of either player's deck and put them back in any order.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=future_sight, ), Attack( title="Psychic", @@ -31,7 +57,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("defender"), 20, base=20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Manectric_59.py b/spirit/game/scripts/cards/SWSH3/Manectric_59.py index 1111ce2..64bcc33 100644 --- a/spirit/game/scripts/cards/SWSH3/Manectric_59.py +++ b/spirit/game/scripts/cards/SWSH3/Manectric_59.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import switch_self_attack +from spirit.game.card_effects.attacks_common import snipe_attack card = PokemonCardDef( guid="1f644cc9-ad30-5f13-9257-07e684127351", @@ -24,14 +26,14 @@ card = PokemonCardDef( game_text="You may switch this Pok\u00e9mon with 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.LIGHTNING: 1}, damage=30, - effect=unimplemented, + effect=switch_self_attack(optional=True), ), Attack( title="Flash Impact", game_text="This attack also does 30 damage to 1 of your Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 1}, damage=150, - effect=unimplemented, + effect=snipe_attack(30, pool="bench", side="mine", count=1, also_base=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Mareanie_51.py b/spirit/game/scripts/cards/SWSH3/Mareanie_51.py index 7ce481e..97a95e7 100644 --- a/spirit/game/scripts/cards/SWSH3/Mareanie_51.py +++ b/spirit/game/scripts/cards/SWSH3/Mareanie_51.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.support_common import heal_attack card = PokemonCardDef( guid="6c0f065a-501e-5654-9942-b3dc7897c827", @@ -22,14 +24,14 @@ card = PokemonCardDef( title="Regeneration", game_text="Heal 30 damage from this Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=heal_attack(30), ), Attack( title="Poison Tentacles", game_text="Your opponent's Active Pok\u00e9mon is now Poisoned.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 1}, damage=20, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Melmetal_130.py b/spirit/game/scripts/cards/SWSH3/Melmetal_130.py index cf6865a..c17f9c9 100644 --- a/spirit/game/scripts/cards/SWSH3/Melmetal_130.py +++ b/spirit/game/scripts/cards/SWSH3/Melmetal_130.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import attach_from_discard card = PokemonCardDef( guid="726bf353-6f66-50d3-85bd-485349171e7d", @@ -25,7 +26,7 @@ card = PokemonCardDef( game_text="Attach an Energy card from your discard pile to this Pok\u00e9mon.", cost={PokemonTypes.METAL: 1}, damage=30, - effect=unimplemented, + effect=attach_from_discard(), ), Attack( title="Heavy Impact", diff --git a/spirit/game/scripts/cards/SWSH3/MewV_69.py b/spirit/game/scripts/cards/SWSH3/MewV_69.py index b5afc2e..0d13ed7 100644 --- a/spirit/game/scripts/cards/SWSH3/MewV_69.py +++ b/spirit/game/scripts/cards/SWSH3/MewV_69.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per +from spirit.game.session.legal_actions import energy_provided_count + + +def _both_actives_energy(ctx): + total = 0 + for pokemon in (ctx.my_active(), ctx.opponent_active()): + if pokemon is None: + continue + for energy in ctx.attached_energies(pokemon): + total += energy_provided_count(energy) + return total + card = PokemonCardDef( guid="a45d39b9-d278-5dd8-83f1-0ba97c38f8a0", @@ -25,7 +38,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 2}, damage=30, damage_operator="x", - effect=unimplemented, + effect=damage_per(_both_actives_energy, 30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Milotic_39.py b/spirit/game/scripts/cards/SWSH3/Milotic_39.py index c465589..c3387ee 100644 --- a/spirit/game/scripts/cards/SWSH3/Milotic_39.py +++ b/spirit/game/scripts/cards/SWSH3/Milotic_39.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import heal_targets, requires_damaged_pokemon + + +async def bright_heal(ctx): + if await ctx.ask_yes_no("Heal 20 damage from each of your Pokémon?"): + await heal_targets(20, "each_own")(ctx) + card = PokemonCardDef( guid="d250a912-f03a-555e-a0bc-32b1d3266157", @@ -22,7 +29,9 @@ card = PokemonCardDef( Ability( title="Bright Heal", game_text="Once during your turn, you may heal 20 damage from each of your Pok\u00e9mon.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=requires_damaged_pokemon(), + effect=bright_heal, ), Attack( title="Surf", diff --git a/spirit/game/scripts/cards/SWSH3/Mimikyu_81.py b/spirit/game/scripts/cards/SWSH3/Mimikyu_81.py index 1b107b9..46672a7 100644 --- a/spirit/game/scripts/cards/SWSH3/Mimikyu_81.py +++ b/spirit/game/scripts/cards/SWSH3/Mimikyu_81.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import ( + healing_block_passive, opposing_pokemon, is_in_active_spot, +) card = PokemonCardDef( guid="8bc14fdc-7fda-5b30-bcfa-52bbffcd937f", @@ -22,7 +25,9 @@ card = PokemonCardDef( Ability( title="Heal Jamming", game_text="Your opponent's Benched Pok\u00e9mon can't be healed.", - effect=unimplemented, + passive=healing_block_passive( + lambda p, c: opposing_pokemon(p, c) and not is_in_active_spot(p) + ), ), Attack( title="Claw Slash", diff --git a/spirit/game/scripts/cards/SWSH3/Morelull_79.py b/spirit/game/scripts/cards/SWSH3/Morelull_79.py index 97e1277..5f5bba3 100644 --- a/spirit/game/scripts/cards/SWSH3/Morelull_79.py +++ b/spirit/game/scripts/cards/SWSH3/Morelull_79.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="546c3705-a69d-584b-bae7-b1faaabecea4", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Spore", game_text="Your opponent's Active Pok\u00e9mon is now Asleep.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=condition_attack(SpecialConditions.ASLEEP), ), Attack( title="Flop", diff --git a/spirit/game/scripts/cards/SWSH3/Muk_101.py b/spirit/game/scripts/cards/SWSH3/Muk_101.py index 2547e28..da5fcda 100644 --- a/spirit/game/scripts/cards/SWSH3/Muk_101.py +++ b/spirit/game/scripts/cards/SWSH3/Muk_101.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="8d386fdd-2bff-5367-9551-c959ec78e3e1", @@ -23,7 +24,7 @@ card = PokemonCardDef( title="Triple Poison", game_text="Your opponent's Active Pok\u00e9mon is now Poisoned. During Pok\u00e9mon Checkup, put 3 damage counters on that Pok\u00e9mon instead of 1.", cost={PokemonTypes.DARKNESS: 1}, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED, counters=3), ), Attack( title="Sludge Whirlpool", diff --git a/spirit/game/scripts/cards/SWSH3/Nickit_112.py b/spirit/game/scripts/cards/SWSH3/Nickit_112.py index 926bd8f..698c096 100644 --- a/spirit/game/scripts/cards/SWSH3/Nickit_112.py +++ b/spirit/game/scripts/cards/SWSH3/Nickit_112.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import lock_defender_attacks + + +async def tail_whip(ctx): + """Flip a coin. If heads, the Defending Pokémon can't attack next turn.""" + heads = await ctx.flip_coins(1, "Tail Whip") + if heads[0]: + lock_defender_attacks(ctx) card = PokemonCardDef( guid="6b8927a2-9650-5a0a-9ca7-b010ba01f11c", @@ -22,7 +30,7 @@ card = PokemonCardDef( title="Tail Whip", game_text="Flip a coin. If heads, during your opponent's next turn, the Defending Pok\u00e9mon can't attack.", cost={PokemonTypes.DARKNESS: 1}, - effect=unimplemented, + effect=tail_whip, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/OldPC_164.py b/spirit/game/scripts/cards/SWSH3/OldPC_164.py index 35b4594..64838aa 100644 --- a/spirit/game/scripts/cards/SWSH3/OldPC_164.py +++ b/spirit/game/scripts/cards/SWSH3/OldPC_164.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.attacks_common import flip_or_nothing + + +async def _put_discard_card_in_hand(ctx): + discard = ctx.discard_pile() + if not discard: + return + picks = await ctx.choose_cards( + discard, 1, minimum=1, prompt="Choose a card to put into your hand", + ) + await ctx.put_in_hand(picks, reveal=False) + card = ItemCardDef( guid="3279662b-8876-5b8b-a540-ff9da908bb21", @@ -11,5 +23,5 @@ card = ItemCardDef( collector_number=164, set_code="SWSH3", rarity=Rarities.Uncommon, - effect=unimplemented + effect=flip_or_nothing(coins=2, then=_put_discard_card_in_hand), ) diff --git a/spirit/game/scripts/cards/SWSH3/Parasect_4.py b/spirit/game/scripts/cards/SWSH3/Parasect_4.py index 1463a27..bc5bd1a 100644 --- a/spirit/game/scripts/cards/SWSH3/Parasect_4.py +++ b/spirit/game/scripts/cards/SWSH3/Parasect_4.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="c048cbc9-9e2b-58ed-92a8-95a1708d2207", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.GRASS: 1}, damage=30, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), Attack( title="Solar Beam", diff --git a/spirit/game/scripts/cards/SWSH3/Passimian_97.py b/spirit/game/scripts/cards/SWSH3/Passimian_97.py index c0c8930..097a0d9 100644 --- a/spirit/game/scripts/cards/SWSH3/Passimian_97.py +++ b/spirit/game/scripts/cards/SWSH3/Passimian_97.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_bench + + +async def rock_hurl(ctx): + """This attack's damage isn't affected by Resistance.""" + await ctx.deal_damage(ignore_resistance=True) + card = PokemonCardDef( guid="ec80b174-a01e-5b08-95e0-45ae1aff5114", @@ -20,16 +27,16 @@ card = PokemonCardDef( abilities=[ Attack( title="Call for Family", - game_text="Search your deck for up to 2 Basic Pok\u00e9mon and put them onto your Bench. Then, shuffle your deck.", + game_text="Search your deck for up to 2 Basic Pokémon and put them onto your Bench. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_to_bench(count=2), ), Attack( title="Rock Hurl", game_text="This attack's damage isn't affected by Resistance.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 2}, damage=70, - effect=unimplemented, + effect=rock_hurl, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH3/Pincurchin_64.py b/spirit/game/scripts/cards/SWSH3/Pincurchin_64.py index 4c7fb86..7959fa9 100644 --- a/spirit/game/scripts/cards/SWSH3/Pincurchin_64.py +++ b/spirit/game/scripts/cards/SWSH3/Pincurchin_64.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.support_common import draw_attack +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="ac68ecec-b6ea-5e5c-9cc0-0f94e03f02d9", @@ -22,14 +24,14 @@ card = PokemonCardDef( title="Double Draw", game_text="Draw 2 cards.", cost={PokemonTypes.LIGHTNING: 1}, - effect=unimplemented, + effect=draw_attack(2), ), Attack( title="Zing Zap", game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 2}, damage=50, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/PokmonBreedersNurturing_166.py b/spirit/game/scripts/cards/SWSH3/PokmonBreedersNurturing_166.py index c6a9c99..534d6e0 100644 --- a/spirit/game/scripts/cards/SWSH3/PokmonBreedersNurturing_166.py +++ b/spirit/game/scripts/cards/SWSH3/PokmonBreedersNurturing_166.py @@ -1,5 +1,41 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import SupporterCardDef +from spirit.game.attributes import AttrID, Rarities + + +def _breeders_nurturing_condition(board, player_id): + turn_state = getattr(board, "turn_state", None) + return turn_state is not None and turn_state.turn_number > 2 + + +async def pokemon_breeders_nurturing(ctx): + """Choose up to 2 of your Pokemon in play (not put into play this turn); + for each, search the deck for a card that evolves from it and evolve it.""" + turn_state = ctx.session.turn_state + candidates = [ + p for p in ctx.my_pokemon_in_play() + if turn_state.entered_play_turn.get(p.entity_id) != turn_state.turn_number + ] + if not candidates: + return + targets = await ctx.choose_cards( + candidates, 2, minimum=0, + prompt="Choose up to 2 of your Pokémon in play to evolve.", + ) + if not targets: + return + for target in targets: + logic_name = target.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) + if not logic_name: + continue + picks = await ctx.search_deck( + lambda c, name=logic_name: c.get_attribute(AttrID.EVOLUTION_LOGIC_FROM) == name, + count=1, minimum=0, + prompt="Choose a card that evolves from that Pokémon.", + ) + if picks: + await ctx.evolve_pokemon(target, picks[0]) + await ctx.shuffle_deck() + card = SupporterCardDef( guid="08ff5298-b4d9-5bf0-bb0d-00d48987609f", @@ -11,5 +47,6 @@ card = SupporterCardDef( collector_number=166, set_code="SWSH3", rarity=Rarities.Uncommon, - effect=unimplemented + effect=pokemon_breeders_nurturing, + condition=_breeders_nurturing_condition ) diff --git a/spirit/game/scripts/cards/SWSH3/PokmonBreedersNurturing_188.py b/spirit/game/scripts/cards/SWSH3/PokmonBreedersNurturing_188.py index b096d1b..3675cda 100644 --- a/spirit/game/scripts/cards/SWSH3/PokmonBreedersNurturing_188.py +++ b/spirit/game/scripts/cards/SWSH3/PokmonBreedersNurturing_188.py @@ -1,5 +1,41 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import SupporterCardDef +from spirit.game.attributes import AttrID, Rarities + + +def _breeders_nurturing_condition(board, player_id): + turn_state = getattr(board, "turn_state", None) + return turn_state is not None and turn_state.turn_number > 2 + + +async def pokemon_breeders_nurturing(ctx): + """Choose up to 2 of your Pokemon in play (not put into play this turn); + for each, search the deck for a card that evolves from it and evolve it.""" + turn_state = ctx.session.turn_state + candidates = [ + p for p in ctx.my_pokemon_in_play() + if turn_state.entered_play_turn.get(p.entity_id) != turn_state.turn_number + ] + if not candidates: + return + targets = await ctx.choose_cards( + candidates, 2, minimum=0, + prompt="Choose up to 2 of your Pokémon in play to evolve.", + ) + if not targets: + return + for target in targets: + logic_name = target.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) + if not logic_name: + continue + picks = await ctx.search_deck( + lambda c, name=logic_name: c.get_attribute(AttrID.EVOLUTION_LOGIC_FROM) == name, + count=1, minimum=0, + prompt="Choose a card that evolves from that Pokémon.", + ) + if picks: + await ctx.evolve_pokemon(target, picks[0]) + await ctx.shuffle_deck() + card = SupporterCardDef( guid="133e9406-bed0-526d-9353-6742c96f626d", @@ -11,5 +47,6 @@ card = SupporterCardDef( collector_number=188, set_code="SWSH3", rarity=Rarities.RareUltra, - effect=unimplemented + effect=pokemon_breeders_nurturing, + condition=_breeders_nurturing_condition ) diff --git a/spirit/game/scripts/cards/SWSH3/PokmonBreedersNurturing_195.py b/spirit/game/scripts/cards/SWSH3/PokmonBreedersNurturing_195.py index 50b5a6e..8d9dc02 100644 --- a/spirit/game/scripts/cards/SWSH3/PokmonBreedersNurturing_195.py +++ b/spirit/game/scripts/cards/SWSH3/PokmonBreedersNurturing_195.py @@ -1,5 +1,41 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import SupporterCardDef +from spirit.game.attributes import AttrID, Rarities + + +def _breeders_nurturing_condition(board, player_id): + turn_state = getattr(board, "turn_state", None) + return turn_state is not None and turn_state.turn_number > 2 + + +async def pokemon_breeders_nurturing(ctx): + """Choose up to 2 of your Pokemon in play (not put into play this turn); + for each, search the deck for a card that evolves from it and evolve it.""" + turn_state = ctx.session.turn_state + candidates = [ + p for p in ctx.my_pokemon_in_play() + if turn_state.entered_play_turn.get(p.entity_id) != turn_state.turn_number + ] + if not candidates: + return + targets = await ctx.choose_cards( + candidates, 2, minimum=0, + prompt="Choose up to 2 of your Pokémon in play to evolve.", + ) + if not targets: + return + for target in targets: + logic_name = target.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) + if not logic_name: + continue + picks = await ctx.search_deck( + lambda c, name=logic_name: c.get_attribute(AttrID.EVOLUTION_LOGIC_FROM) == name, + count=1, minimum=0, + prompt="Choose a card that evolves from that Pokémon.", + ) + if picks: + await ctx.evolve_pokemon(target, picks[0]) + await ctx.shuffle_deck() + card = SupporterCardDef( guid="f208756d-1c45-50e1-8fe0-61a971d8fd8e", @@ -11,5 +47,6 @@ card = SupporterCardDef( collector_number=195, set_code="SWSH3", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=pokemon_breeders_nurturing, + condition=_breeders_nurturing_condition ) diff --git a/spirit/game/scripts/cards/SWSH3/Polteageist_83.py b/spirit/game/scripts/cards/SWSH3/Polteageist_83.py index 5990a66..363d218 100644 --- a/spirit/game/scripts/cards/SWSH3/Polteageist_83.py +++ b/spirit/game/scripts/cards/SWSH3/Polteageist_83.py @@ -1,5 +1,23 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import requires_hand +from spirit.game.card_effects.attacks_common import count_discard, damage_per, has_attack_titled + +_HAS_MAD_PARTY = has_attack_titled("Mad Party") + + +async def tea_break(ctx): + """Discard a Pokémon with the Mad Party attack from hand to use this + Ability. Once during your turn, you may draw 2 cards.""" + picks = await ctx.discard_from_hand( + 1, predicate=_HAS_MAD_PARTY, + prompt="Discard a Pokémon that has the Mad Party attack.", + ) + if not picks: + return + if await ctx.ask_yes_no("Draw 2 cards?"): + await ctx.draw_cards(2) + card = PokemonCardDef( guid="607b8809-852a-5dd7-bd38-305fb7069c68", @@ -22,16 +40,18 @@ card = PokemonCardDef( abilities=[ Ability( title="Tea Break", - game_text="You must discard a Pok\u00e9mon that has the Mad Party attack from your hand in order to use this Ability. Once during your turn, you may draw 2 cards.", - effect=unimplemented, + game_text="You must discard a Pokémon that has the Mad Party attack from your hand in order to use this Ability. Once during your turn, you may draw 2 cards.", + activation=Activations.ONCE_PER_TURN, + condition=requires_hand(_HAS_MAD_PARTY), + effect=tea_break, ), Attack( title="Mad Party", - game_text="This attack does 20 damage for each Pok\u00e9mon in your discard pile that has the Mad Party attack.", + game_text="This attack does 20 damage for each Pokémon in your discard pile that has the Mad Party attack.", cost={PokemonTypes.COLORLESS: 2}, damage=20, damage_operator="x", - effect=unimplemented, + effect=damage_per(count_discard("mine", _HAS_MAD_PARTY), 20), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH3/Pupitar_87.py b/spirit/game/scripts/cards/SWSH3/Pupitar_87.py index 1eb31fd..d1dbf7a 100644 --- a/spirit/game/scripts/cards/SWSH3/Pupitar_87.py +++ b/spirit/game/scripts/cards/SWSH3/Pupitar_87.py @@ -1,5 +1,24 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities + + +async def rocket_evolution(ctx): + """Search a card that evolves from this Pokémon and put it onto this + Pokémon to evolve it. Then, shuffle the deck.""" + await ctx.deal_damage() + my_logic_name = ctx.source.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) + + def matches(c): + return c.get_attribute(AttrID.EVOLUTION_LOGIC_FROM) == my_logic_name + + picks = await ctx.search_deck( + matches, count=1, minimum=0, + prompt="Choose a card that evolves from this Pokémon.", + ) + if picks: + await ctx.evolve_pokemon(ctx.source, picks[0]) + await ctx.shuffle_deck() + card = PokemonCardDef( guid="1b328806-47e1-5088-8d88-7d778d59b386", @@ -26,10 +45,10 @@ card = PokemonCardDef( ), Attack( title="Rocket Evolution", - game_text="Search your deck for a card that evolves from this Pok\u00e9mon and put it onto this Pok\u00e9mon to evolve it. Then, shuffle your deck.", + game_text="Search your deck for a card that evolves from this Pokémon and put it onto this Pokémon to evolve it. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 3}, damage=40, - effect=unimplemented, + effect=rocket_evolution, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH3/Purrloin_106.py b/spirit/game/scripts/cards/SWSH3/Purrloin_106.py index 0785c7a..8df8a8e 100644 --- a/spirit/game/scripts/cards/SWSH3/Purrloin_106.py +++ b/spirit/game/scripts/cards/SWSH3/Purrloin_106.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="7bc63f28-d3a6-5de9-af6b-786fd6b5f3e4", @@ -28,7 +29,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.COLORLESS: 2}, damage=20, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/RareFossil_167.py b/spirit/game/scripts/cards/SWSH3/RareFossil_167.py index bb96469..7786ea9 100644 --- a/spirit/game/scripts/cards/SWSH3/RareFossil_167.py +++ b/spirit/game/scripts/cards/SWSH3/RareFossil_167.py @@ -1,7 +1,8 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import FossilItemCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import FossilBodyPassive, fossil_discard_ability -card = ItemCardDef( +card = FossilItemCardDef( guid="be893da7-2edb-575b-9220-f38f3f685444", key="SWSH3", name="com.direwolfdigital.cake.data.archetypes.trainer.RareFossil.Name", @@ -11,5 +12,7 @@ card = ItemCardDef( collector_number=167, set_code="SWSH3", rarity=Rarities.Uncommon, - effect=unimplemented + hp=70, + passive=FossilBodyPassive(blocks_conditions=True), + abilities=[fossil_discard_ability()], ) diff --git a/spirit/game/scripts/cards/SWSH3/Relicanth_40.py b/spirit/game/scripts/cards/SWSH3/Relicanth_40.py index 0f38dfc..51cf29c 100644 --- a/spirit/game/scripts/cards/SWSH3/Relicanth_40.py +++ b/spirit/game/scripts/cards/SWSH3/Relicanth_40.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.trainers import fossil_search, is_rare_fossil card = PokemonCardDef( guid="5610a1f5-b6ed-5b97-bdfc-93f525baa035", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Fossil Search", game_text="Search your deck for up to 2 Rare Fossil cards and put them onto your Bench. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=fossil_search(is_rare_fossil), ), Attack( title="Surf", @@ -30,4 +31,4 @@ card = PokemonCardDef( damage=40, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH3/RhyperiorV_181.py b/spirit/game/scripts/cards/SWSH3/RhyperiorV_181.py index 4a3b207..4a821ca 100644 --- a/spirit/game/scripts/cards/SWSH3/RhyperiorV_181.py +++ b/spirit/game/scripts/cards/SWSH3/RhyperiorV_181.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import discard_opponent_energy_attack card = PokemonCardDef( guid="df0e83ae-0bc6-5680-b00b-3b6ec9b00eaa", @@ -23,14 +24,14 @@ card = PokemonCardDef( game_text="Discard an Energy from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 2}, damage=80, - effect=unimplemented, + effect=discard_opponent_energy_attack(), ), Attack( title="Heavy Rock Artillery", game_text="During your next turn, this Pok\u00e9mon can't use Heavy Rock Artillery.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 3}, damage=210, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/RhyperiorV_95.py b/spirit/game/scripts/cards/SWSH3/RhyperiorV_95.py index e188482..244925a 100644 --- a/spirit/game/scripts/cards/SWSH3/RhyperiorV_95.py +++ b/spirit/game/scripts/cards/SWSH3/RhyperiorV_95.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import discard_opponent_energy_attack card = PokemonCardDef( guid="a21484ae-0b01-5b8b-82ef-85cd896f5633", @@ -23,14 +24,14 @@ card = PokemonCardDef( game_text="Discard an Energy from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 2}, damage=80, - effect=unimplemented, + effect=discard_opponent_energy_attack(), ), Attack( title="Heavy Rock Artillery", game_text="During your next turn, this Pok\u00e9mon can't use Heavy Rock Artillery.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 3}, damage=210, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Rillaboom_197.py b/spirit/game/scripts/cards/SWSH3/Rillaboom_197.py index f167542..ca9b2cb 100644 --- a/spirit/game/scripts/cards/SWSH3/Rillaboom_197.py +++ b/spirit/game/scripts/cards/SWSH3/Rillaboom_197.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.trainers import is_grass_energy_card +from spirit.game.card_effects.support_common import search_attach_energy card = PokemonCardDef( guid="20fcc91d-1cb0-5fbd-97a6-e2120e2cc9a2", @@ -22,7 +24,8 @@ card = PokemonCardDef( Ability( title="Voltage Beat", game_text="Once during your turn, you may search your deck for up to 2 Grass Energy cards and attach them to 1 of your Pok\u00e9mon. Then, shuffle your deck.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + effect=search_attach_energy(predicate=is_grass_energy_card, count=2, distribute=False), ), Attack( title="Hammer In", diff --git a/spirit/game/scripts/cards/SWSH3/Rookidee_154.py b/spirit/game/scripts/cards/SWSH3/Rookidee_154.py index ed81b19..79ea77f 100644 --- a/spirit/game/scripts/cards/SWSH3/Rookidee_154.py +++ b/spirit/game/scripts/cards/SWSH3/Rookidee_154.py @@ -1,6 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def pluck(ctx): + """Before doing damage, discard all Pokémon Tools from the opponent's Active.""" + target = ctx.defender + if target is not None and not ctx.effects_blocked(target): + tools = [t for t, p in ctx.tools_in_play() if p is target] + if tools: + await ctx.discard_cards(tools) + await ctx.deal_damage() + + card = PokemonCardDef( guid="baacab2c-1b95-548b-8843-dca3c64fba18", key="SWSH3", @@ -24,7 +35,7 @@ card = PokemonCardDef( game_text="Before doing damage, discard all Pok\u00e9mon Tools from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, damage=10, - effect=unimplemented, + effect=pluck, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/RoseTower_169.py b/spirit/game/scripts/cards/SWSH3/RoseTower_169.py index a0f7a9f..3efc8de 100644 --- a/spirit/game/scripts/cards/SWSH3/RoseTower_169.py +++ b/spirit/game/scripts/cards/SWSH3/RoseTower_169.py @@ -1,6 +1,25 @@ -from spirit.game.data_utils import StadiumCardDef, unimplemented +from spirit.game.data_utils import StadiumCardDef, Ability, Activations from spirit.game.attributes import Rarities + +def _rose_tower_condition(board, player_id, stadium): + hand = board.find_player_area(player_id, "hand") + return bool(hand) and len(hand.children) < 3 + + +async def _rose_tower_effect(ctx): + if await ctx.ask_yes_no("Draw cards until you have 3 cards in your hand?"): + await ctx.draw_until(3) + + +ROSE_TOWER_ABILITY = Ability( + title="Rose Tower", + game_text="Once during each player's turn, that player may draw cards until they have 3 cards in their hand.", + activation=Activations.ONCE_PER_TURN, + condition=_rose_tower_condition, + effect=_rose_tower_effect, +) + card = StadiumCardDef( guid="2c6fa6b9-5fc8-5870-b10a-95cd47339c50", key="SWSH3", @@ -11,5 +30,5 @@ card = StadiumCardDef( collector_number=169, set_code="SWSH3", rarity=Rarities.Uncommon, - effect=unimplemented + ability=ROSE_TOWER_ABILITY, ) diff --git a/spirit/game/scripts/cards/SWSH3/Rowlet_11.py b/spirit/game/scripts/cards/SWSH3/Rowlet_11.py index 39af378..815f845 100644 --- a/spirit/game/scripts/cards/SWSH3/Rowlet_11.py +++ b/spirit/game/scripts/cards/SWSH3/Rowlet_11.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.passives import Passive +from spirit.game.card_effects.attacks_common import snipe_attack + + +class _SkyCircusPassive(Passive): + def modify_attack_cost(self, cost, pokemon, carrier, board): + if pokemon is not carrier: + return cost + state = getattr(board, "turn_state", None) + if state and any(name == "Bird Keeper" for _, name, _ in state.trainers_played): + return {} + return cost + card = PokemonCardDef( guid="e35c2272-428c-5c09-a29e-f112deb739f9", @@ -21,13 +34,13 @@ card = PokemonCardDef( Ability( title="Sky Circus", game_text="If you played Bird Keeper from your hand during this turn, ignore all Energy in this Pok\u00e9mon's attack costs.", - effect=unimplemented, + passive=_SkyCircusPassive(), ), Attack( title="Wind Shard", game_text="This attack does 60 damage to 1 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.COLORLESS: 3}, - effect=unimplemented, + effect=snipe_attack(60, pool="bench"), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/SalamenceVMAX_144.py b/spirit/game/scripts/cards/SWSH3/SalamenceVMAX_144.py index 1cdad01..acb715e 100644 --- a/spirit/game/scripts/cards/SWSH3/SalamenceVMAX_144.py +++ b/spirit/game/scripts/cards/SWSH3/SalamenceVMAX_144.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import snipe_attack card = PokemonCardDef( guid="bb349d05-047e-562a-b8cf-80b19c7d35b8", @@ -24,14 +25,14 @@ card = PokemonCardDef( title="Sonic Double", game_text="This attack does 40 damage to 2 of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=snipe_attack(40, pool="any", count=2), ), Attack( title="Max Wings", game_text="During your next turn, this Pok\u00e9mon can't use Max Wings.", cost={PokemonTypes.COLORLESS: 4}, damage=240, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/SalamenceVMAX_194.py b/spirit/game/scripts/cards/SWSH3/SalamenceVMAX_194.py index f17ea5b..e5192bc 100644 --- a/spirit/game/scripts/cards/SWSH3/SalamenceVMAX_194.py +++ b/spirit/game/scripts/cards/SWSH3/SalamenceVMAX_194.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import snipe_attack card = PokemonCardDef( guid="a70e6497-18d8-50ac-91b1-95e4c8249570", @@ -24,14 +25,14 @@ card = PokemonCardDef( title="Sonic Double", game_text="This attack does 40 damage to 2 of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=snipe_attack(40, pool="any", count=2), ), Attack( title="Max Wings", game_text="During your next turn, this Pok\u00e9mon can't use Max Wings.", cost={PokemonTypes.COLORLESS: 4}, damage=240, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/SalamenceV_143.py b/spirit/game/scripts/cards/SWSH3/SalamenceV_143.py index 51b198d..643af53 100644 --- a/spirit/game/scripts/cards/SWSH3/SalamenceV_143.py +++ b/spirit/game/scripts/cards/SWSH3/SalamenceV_143.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_all_opponents card = PokemonCardDef( guid="285f13b2-558d-59c5-b930-68eea0c42225", @@ -23,7 +24,7 @@ card = PokemonCardDef( title="Swoop Across", game_text="This attack does 30 damage to each of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.COLORLESS: 3}, - effect=unimplemented, + effect=damage_all_opponents(30), ), Attack( title="Heavy Storm", diff --git a/spirit/game/scripts/cards/SWSH3/SalamenceV_185.py b/spirit/game/scripts/cards/SWSH3/SalamenceV_185.py index 264757d..4fa125e 100644 --- a/spirit/game/scripts/cards/SWSH3/SalamenceV_185.py +++ b/spirit/game/scripts/cards/SWSH3/SalamenceV_185.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_all_opponents card = PokemonCardDef( guid="3ff45355-32a4-56c1-b266-de35b57d80df", @@ -23,7 +24,7 @@ card = PokemonCardDef( title="Swoop Across", game_text="This attack does 30 damage to each of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.COLORLESS: 3}, - effect=unimplemented, + effect=damage_all_opponents(30), ), Attack( title="Heavy Storm", diff --git a/spirit/game/scripts/cards/SWSH3/ScizorVMAX_119.py b/spirit/game/scripts/cards/SWSH3/ScizorVMAX_119.py index e6873f5..94da39a 100644 --- a/spirit/game/scripts/cards/SWSH3/ScizorVMAX_119.py +++ b/spirit/game/scripts/cards/SWSH3/ScizorVMAX_119.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.passives_common import protect_next_turn +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -25,7 +26,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, this Pok\u00e9mon takes 30 less damage from attacks (after applying Weakness and Resistance).", cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 1}, damage=90, - effect=unimplemented, + effect=protect_next_turn(reduce=30), ), Attack( title="Max Steelspike", diff --git a/spirit/game/scripts/cards/SWSH3/ScizorVMAX_193.py b/spirit/game/scripts/cards/SWSH3/ScizorVMAX_193.py index c7c93de..10ee1b5 100644 --- a/spirit/game/scripts/cards/SWSH3/ScizorVMAX_193.py +++ b/spirit/game/scripts/cards/SWSH3/ScizorVMAX_193.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.passives_common import protect_next_turn +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -25,7 +26,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, this Pok\u00e9mon takes 30 less damage from attacks (after applying Weakness and Resistance).", cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 1}, damage=90, - effect=unimplemented, + effect=protect_next_turn(reduce=30), ), Attack( title="Max Steelspike", diff --git a/spirit/game/scripts/cards/SWSH3/ScizorV_118.py b/spirit/game/scripts/cards/SWSH3/ScizorV_118.py index e4ebe62..e35f972 100644 --- a/spirit/game/scripts/cards/SWSH3/ScizorV_118.py +++ b/spirit/game/scripts/cards/SWSH3/ScizorV_118.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_special_energy + + +async def hack_off(ctx): + """Discard a Pokémon Tool and a Special Energy from the opponent's Active.""" + await ctx.deal_damage() + target = ctx.opponent_active() + if target is None or ctx.effects_blocked(target): + return + tools = [t for t, p in ctx.tools_in_play() if p is target] + if tools: + await ctx.discard_cards(tools[:1]) + await ctx.discard_energy_from( + target, 1, predicate=is_special_energy, + prompt="Choose a Special Energy to discard from the Defending Pokémon") + card = PokemonCardDef( guid="21d2cbae-91ac-58e3-a1e4-0c90114d0027", @@ -24,7 +40,7 @@ card = PokemonCardDef( game_text="Discard a Pok\u00e9mon Tool and a Special Energy from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.METAL: 1}, damage=30, - effect=unimplemented, + effect=hack_off, ), Attack( title="Slashing Claw", diff --git a/spirit/game/scripts/cards/SWSH3/ScizorV_183.py b/spirit/game/scripts/cards/SWSH3/ScizorV_183.py index 0a3e71d..8dbefd6 100644 --- a/spirit/game/scripts/cards/SWSH3/ScizorV_183.py +++ b/spirit/game/scripts/cards/SWSH3/ScizorV_183.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_special_energy + + +async def hack_off(ctx): + """Discard a Pokémon Tool and a Special Energy from the opponent's Active.""" + await ctx.deal_damage() + target = ctx.opponent_active() + if target is None or ctx.effects_blocked(target): + return + tools = [t for t, p in ctx.tools_in_play() if p is target] + if tools: + await ctx.discard_cards(tools[:1]) + await ctx.discard_energy_from( + target, 1, predicate=is_special_energy, + prompt="Choose a Special Energy to discard from the Defending Pokémon") + card = PokemonCardDef( guid="4783dfac-25ae-5f0f-9813-0ac8c7ccf5c9", @@ -24,7 +40,7 @@ card = PokemonCardDef( game_text="Discard a Pok\u00e9mon Tool and a Special Energy from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.METAL: 1}, damage=30, - effect=unimplemented, + effect=hack_off, ), Attack( title="Slashing Claw", diff --git a/spirit/game/scripts/cards/SWSH3/Sentret_135.py b/spirit/game/scripts/cards/SWSH3/Sentret_135.py index ff42132..d7942b6 100644 --- a/spirit/game/scripts/cards/SWSH3/Sentret_135.py +++ b/spirit/game/scripts/cards/SWSH3/Sentret_135.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_bench card = PokemonCardDef( guid="90ddf0aa-b188-503f-8a27-7e005b3312c0", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Call for Family", game_text="Search your deck for a Basic Pok\u00e9mon and put it onto your Bench. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_to_bench(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Shiinotic_80.py b/spirit/game/scripts/cards/SWSH3/Shiinotic_80.py index 440b8c0..d2b21f1 100644 --- a/spirit/game/scripts/cards/SWSH3/Shiinotic_80.py +++ b/spirit/game/scripts/cards/SWSH3/Shiinotic_80.py @@ -1,5 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, lock_defender_attacks + +GLIMWOOD_TANGLE_GUID = "4d910b75-6845-5f9b-99b1-e6b12b65af27" + + +def _glimwood_tangle_in_play(ctx): + stadium = ctx.stadium_in_play() + return stadium is not None and stadium.archetype_id == GLIMWOOD_TANGLE_GUID + + +async def flickering_light(ctx): + await ctx.deal_damage() + heads = await ctx.flip_coins(1, "Flickering Light") + if heads[0]: + lock_defender_attacks(ctx) card = PokemonCardDef( guid="2f3e87ab-1777-56bd-9b30-ab1fc0286201", @@ -24,7 +39,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, during your opponent's next turn, the Defending Pok\u00e9mon can't attack.", cost={PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=flickering_light, ), Attack( title="Fear the Forest", @@ -32,7 +47,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=60, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_glimwood_tangle_in_play, 60), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Simipour_42.py b/spirit/game/scripts/cards/SWSH3/Simipour_42.py index e36b85f..721f360 100644 --- a/spirit/game/scripts/cards/SWSH3/Simipour_42.py +++ b/spirit/game/scripts/cards/SWSH3/Simipour_42.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import lock_all_attacks + + +async def giga_impact(ctx): + """110 damage; during your next turn, this Pokémon can't attack.""" + await ctx.deal_damage() + lock_all_attacks(ctx, ctx.attacker) card = PokemonCardDef( guid="40de2afc-e4bd-546b-9055-3ef770b6ad17", @@ -29,7 +36,7 @@ card = PokemonCardDef( game_text="During your next turn, this Pok\u00e9mon can't attack.", cost={PokemonTypes.COLORLESS: 3}, damage=110, - effect=unimplemented, + effect=giga_impact, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Simisage_7.py b/spirit/game/scripts/cards/SWSH3/Simisage_7.py index 51b0e21..d12d639 100644 --- a/spirit/game/scripts/cards/SWSH3/Simisage_7.py +++ b/spirit/game/scripts/cards/SWSH3/Simisage_7.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import lock_all_attacks + + +async def giga_impact(ctx): + """110 damage; during your next turn, this Pokémon can't attack.""" + await ctx.deal_damage() + lock_all_attacks(ctx, ctx.attacker) card = PokemonCardDef( guid="15ba56ce-3632-5e50-8d81-bb95f870a65e", @@ -29,7 +36,7 @@ card = PokemonCardDef( game_text="During your next turn, this Pok\u00e9mon can't attack.", cost={PokemonTypes.COLORLESS: 3}, damage=110, - effect=unimplemented, + effect=giga_impact, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Simisear_27.py b/spirit/game/scripts/cards/SWSH3/Simisear_27.py index 10f96c7..e1a73b1 100644 --- a/spirit/game/scripts/cards/SWSH3/Simisear_27.py +++ b/spirit/game/scripts/cards/SWSH3/Simisear_27.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import lock_all_attacks + + +async def giga_impact(ctx): + """110 damage; during your next turn, this Pokémon can't attack.""" + await ctx.deal_damage() + lock_all_attacks(ctx, ctx.attacker) card = PokemonCardDef( guid="ec73df0f-f690-5cc2-8f52-3f675e095c7e", @@ -29,7 +36,7 @@ card = PokemonCardDef( game_text="During your next turn, this Pok\u00e9mon can't attack.", cost={PokemonTypes.COLORLESS: 3}, damage=110, - effect=unimplemented, + effect=giga_impact, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Sinistea_82.py b/spirit/game/scripts/cards/SWSH3/Sinistea_82.py index 85fff29..e76b481 100644 --- a/spirit/game/scripts/cards/SWSH3/Sinistea_82.py +++ b/spirit/game/scripts/cards/SWSH3/Sinistea_82.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import place_counters card = PokemonCardDef( guid="464d5255-c6c9-5647-8f79-434ec12eeb82", @@ -23,7 +24,7 @@ card = PokemonCardDef( title="Furtive Drop", game_text="Put 2 damage counters on your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=place_counters(2, "opponent_active"), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Skarmory_120.py b/spirit/game/scripts/cards/SWSH3/Skarmory_120.py index c9b06f6..7d81630 100644 --- a/spirit/game/scripts/cards/SWSH3/Skarmory_120.py +++ b/spirit/game/scripts/cards/SWSH3/Skarmory_120.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, has_tool card = PokemonCardDef( guid="8995eebe-8e3d-56c4-a6ae-506c1c1e652f", @@ -25,7 +26,7 @@ card = PokemonCardDef( cost={PokemonTypes.METAL: 1}, damage=10, damage_operator="+", - effect=unimplemented, + effect=bonus_if(lambda ctx: has_tool(ctx.attacker), 40), ), Attack( title="Cutting Wind", diff --git a/spirit/game/scripts/cards/SWSH3/Skitty_141.py b/spirit/game/scripts/cards/SWSH3/Skitty_141.py index 4f00540..36d15c1 100644 --- a/spirit/game/scripts/cards/SWSH3/Skitty_141.py +++ b/spirit/game/scripts/cards/SWSH3/Skitty_141.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_hand, is_energy + +drawup_power = search_to_hand( + predicate=is_energy, count=1, reveal=True, + prompt="Choose an Energy card to put into your hand.", +) card = PokemonCardDef( guid="62b1d1a7-a160-503d-9af8-8198033f51ea", @@ -22,7 +28,7 @@ card = PokemonCardDef( title="Drawup Power", game_text="Search your deck for an Energy card, reveal it, and put it into your hand. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=drawup_power, ), Attack( title="Cat Kick", diff --git a/spirit/game/scripts/cards/SWSH3/Skwovet_152.py b/spirit/game/scripts/cards/SWSH3/Skwovet_152.py index 87360f5..abfc7f1 100644 --- a/spirit/game/scripts/cards/SWSH3/Skwovet_152.py +++ b/spirit/game/scripts/cards/SWSH3/Skwovet_152.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_or_nothing card = PokemonCardDef( guid="015025f4-47f9-5856-abdd-5bf493bf2969", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Flip a coin. If tails, this attack does nothing.", cost={PokemonTypes.COLORLESS: 1}, damage=40, - effect=unimplemented, + effect=flip_or_nothing(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Solrock_92.py b/spirit/game/scripts/cards/SWSH3/Solrock_92.py index f98f908..897cc9f 100644 --- a/spirit/game/scripts/cards/SWSH3/Solrock_92.py +++ b/spirit/game/scripts/cards/SWSH3/Solrock_92.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.passives_common import no_resistance_passive + + +def _lunatone_in_play(calc, carrier): + if calc.attacker is None or carrier.owning_player_id != calc.attacker.owning_player_id: + return False + return any( + p.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) == "Lunatone" + for p in calc.board.pokemon_in_play(carrier.owning_player_id) + ) + card = PokemonCardDef( guid="fa37c045-f9e9-5767-b415-7b5d9ee608b6", @@ -21,7 +32,7 @@ card = PokemonCardDef( Ability( title="Resistance Shade", game_text="If you have Lunatone in play, your opponent's Pok\u00e9mon in play have no Resistance.", - effect=unimplemented, + passive=no_resistance_passive(when=_lunatone_in_play), ), Attack( title="Rock Throw", diff --git a/spirit/game/scripts/cards/SWSH3/Spikemuth_170.py b/spirit/game/scripts/cards/SWSH3/Spikemuth_170.py index 5e3c572..b84c5f2 100644 --- a/spirit/game/scripts/cards/SWSH3/Spikemuth_170.py +++ b/spirit/game/scripts/cards/SWSH3/Spikemuth_170.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import StadiumCardDef, unimplemented +from spirit.game.data_utils import StadiumCardDef from spirit.game.attributes import Rarities +from spirit.game.session.passives import Passive + + +class _SpikemuthPassive(Passive): + """Whenever a player's Active Pokemon moves to the Bench during their + turn, put 2 damage counters on that Pokemon.""" + + def counters_on_active_to_bench(self, pokemon, carrier): + return 2 + card = StadiumCardDef( guid="d9d948bc-f256-5705-b541-ba076c9cb68f", @@ -11,5 +21,5 @@ card = StadiumCardDef( collector_number=170, set_code="SWSH3", rarity=Rarities.Uncommon, - effect=unimplemented + passive=_SpikemuthPassive(), ) diff --git a/spirit/game/scripts/cards/SWSH3/Staraptor_147.py b/spirit/game/scripts/cards/SWSH3/Staraptor_147.py index 75f7c7f..ba2e5e5 100644 --- a/spirit/game/scripts/cards/SWSH3/Staraptor_147.py +++ b/spirit/game/scripts/cards/SWSH3/Staraptor_147.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack + + +async def hurricane_blender(ctx): + """70 damage. Move any amount of Energy from your Pokemon to your other Pokemon in any way you like.""" + await ctx.deal_damage() + pokemon = ctx.my_pokemon_in_play() + await ctx.move_energy_freely(pokemon, pokemon, prompt="Choose an Energy to move") + card = PokemonCardDef( guid="602991ff-f83d-5337-87fe-b052b22b85fb", @@ -25,14 +34,14 @@ card = PokemonCardDef( game_text="Move any amount of Energy from your Pok\u00e9mon to your other Pok\u00e9mon in any way you like.", cost={PokemonTypes.COLORLESS: 1}, damage=70, - effect=unimplemented, + effect=hurricane_blender, ), Attack( title="Brave Bird", game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.COLORLESS: 3}, damage=170, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Staravia_146.py b/spirit/game/scripts/cards/SWSH3/Staravia_146.py index 3de3081..b9dde45 100644 --- a/spirit/game/scripts/cards/SWSH3/Staravia_146.py +++ b/spirit/game/scripts/cards/SWSH3/Staravia_146.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus card = PokemonCardDef( guid="b9595f61-b2b2-536c-b5e3-c5617993faeb", @@ -26,7 +27,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(20), ), Attack( title="Wing Attack", diff --git a/spirit/game/scripts/cards/SWSH3/Starly_145.py b/spirit/game/scripts/cards/SWSH3/Starly_145.py index b680d4f..1de1231 100644 --- a/spirit/game/scripts/cards/SWSH3/Starly_145.py +++ b/spirit/game/scripts/cards/SWSH3/Starly_145.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.passives import Passive +from spirit.game.card_effects.support_common import search_to_hand + + +class _SkyCircusPassive(Passive): + def modify_attack_cost(self, cost, pokemon, carrier, board): + if pokemon is not carrier: + return cost + state = getattr(board, "turn_state", None) + if state and any(name == "Bird Keeper" for _, name, _ in state.trainers_played): + return {} + return cost + card = PokemonCardDef( guid="0be748d9-42d7-51c6-81aa-9daefb441bc2", @@ -22,13 +35,13 @@ card = PokemonCardDef( Ability( title="Sky Circus", game_text="If you played Bird Keeper from your hand during this turn, ignore all Energy in this Pok\u00e9mon's attack costs.", - effect=unimplemented, + passive=_SkyCircusPassive(), ), Attack( title="Keen Eye", game_text="Search your deck for up to 2 cards and put them into your hand. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + effect=search_to_hand(count=2), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Steenee_15.py b/spirit/game/scripts/cards/SWSH3/Steenee_15.py index 0bbce54..05e8b94 100644 --- a/spirit/game/scripts/cards/SWSH3/Steenee_15.py +++ b/spirit/game/scripts/cards/SWSH3/Steenee_15.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import gust_attack card = PokemonCardDef( guid="0ea98e7a-b544-5d31-8a46-66f4e6bc5f4d", @@ -23,7 +24,7 @@ card = PokemonCardDef( title="Captivate", game_text="Switch 1 of your opponent's Benched Pok\u00e9mon with their Active Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=gust_attack(), ), Attack( title="Smack", diff --git a/spirit/game/scripts/cards/SWSH3/StruggleGloves_171.py b/spirit/game/scripts/cards/SWSH3/StruggleGloves_171.py index 2bc33ae..473ace9 100644 --- a/spirit/game/scripts/cards/SWSH3/StruggleGloves_171.py +++ b/spirit/game/scripts/cards/SWSH3/StruggleGloves_171.py @@ -1,5 +1,20 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import PokemonToolCardDef +from spirit.game.attributes import Rarities, AttrID +from spirit.game.session.passives import Passive, carrier_pokemon + + +class StruggleGlovesPassive(Passive): + def modify_damage_dealt(self, calc, carrier): + if not (calc.is_attack and calc.is_opposing): + return + holder = carrier_pokemon(carrier) + if holder is None or holder is not calc.attacker or not calc.to_active: + return + weak = holder.get_attribute(AttrID.WEAKNESS_TYPES) or [] + target_types = calc.target.get_attribute(AttrID.POKEMON_TYPES) or [] + if any(t in weak for t in target_types): + calc.amount += 30 + card = PokemonToolCardDef( guid="b9a1540c-e933-543d-a27a-653634e006c8", @@ -11,5 +26,5 @@ card = PokemonToolCardDef( collector_number=171, set_code="SWSH3", rarity=Rarities.Uncommon, - effect=unimplemented + passive=StruggleGlovesPassive(), ) diff --git a/spirit/game/scripts/cards/SWSH3/Suicune_37.py b/spirit/game/scripts/cards/SWSH3/Suicune_37.py index dfe6a02..aeda3f8 100644 --- a/spirit/game/scripts/cards/SWSH3/Suicune_37.py +++ b/spirit/game/scripts/cards/SWSH3/Suicune_37.py @@ -1,5 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import energy_provides_type + + +async def _aurora_loop(ctx): + await ctx.deal_damage() + water_energies = [ + e for e in ctx.attached_energies(ctx.attacker) + if energy_provides_type(e, PokemonTypes.WATER.value) + ] + picks = await ctx.choose_cards( + water_energies, 2, + prompt="Choose 2 Water Energy to put into your hand", + ) + await ctx.put_in_hand(picks, reveal=False) + card = PokemonCardDef( guid="1bf418ff-c175-5f25-b650-a956c62cc59c", @@ -28,7 +43,7 @@ card = PokemonCardDef( game_text="Put 2 Water Energy attached to this Pok\u00e9mon into your hand.", cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, damage=130, - effect=unimplemented, + effect=_aurora_loop, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Swanna_149.py b/spirit/game/scripts/cards/SWSH3/Swanna_149.py index 0a0a808..3417291 100644 --- a/spirit/game/scripts/cards/SWSH3/Swanna_149.py +++ b/spirit/game/scripts/cards/SWSH3/Swanna_149.py @@ -1,5 +1,27 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.passives import Passive + + +class _SkyCircusPassive(Passive): + def modify_attack_cost(self, cost, pokemon, carrier, board): + if pokemon is not carrier: + return cost + state = getattr(board, "turn_state", None) + if state and any(name == "Bird Keeper" for _, name, _ in state.trainers_played): + return {} + return cost + + +async def _feather_slice(ctx): + bonus = False + if ctx.hand_size() > 0 and await ctx.ask_yes_no( + "You may discard a card from your hand. If you do, this attack does 70 more damage." + ): + picks = await ctx.discard_from_hand(1, prompt="Discard a card for Feather Slice") + bonus = bool(picks) + await ctx.deal_damage(70 + (70 if bonus else 0)) + card = PokemonCardDef( guid="c6823dda-d344-58f4-a5e0-827f2070747e", @@ -23,7 +45,7 @@ card = PokemonCardDef( Ability( title="Sky Circus", game_text="If you played Bird Keeper from your hand during this turn, ignore all Energy in this Pok\u00e9mon's attack costs.", - effect=unimplemented, + passive=_SkyCircusPassive(), ), Attack( title="Feather Slice", @@ -31,7 +53,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=70, damage_operator="+", - effect=unimplemented, + effect=_feather_slice, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Talonflame_32.py b/spirit/game/scripts/cards/SWSH3/Talonflame_32.py index f24f2d3..d35e913 100644 --- a/spirit/game/scripts/cards/SWSH3/Talonflame_32.py +++ b/spirit/game/scripts/cards/SWSH3/Talonflame_32.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.pokemon import in_active_spot + + +async def scorching_feathers(ctx): + """If this Pokémon (in the Active Spot) is damaged by an attack, burn the attacker.""" + if not in_active_spot(ctx.board, ctx.player_id, ctx.source): + return + await ctx.apply_special_condition(ctx.damaged_by, SpecialConditions.BURNED) + card = PokemonCardDef( guid="c4f7b0ea-8692-5daa-a748-14df67353dfb", @@ -23,14 +33,15 @@ card = PokemonCardDef( Ability( title="Scorching Feathers", game_text="If this Pok\u00e9mon is in the Active Spot and is damaged by an attack from your opponent's Pok\u00e9mon (even if this Pok\u00e9mon is Knocked Out), the Attacking Pok\u00e9mon is now Burned.", - effect=unimplemented, + trigger=Triggers.ON_DAMAGED_BY_ATTACK, + effect=scorching_feathers, ), Attack( title="Mach Flight", game_text="During your opponent's next turn, the Defending Pok\u00e9mon can't retreat.", cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 2}, damage=120, - effect=unimplemented, + effect=condition_attack(no_retreat=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/TapuKoko_61.py b/spirit/game/scripts/cards/SWSH3/TapuKoko_61.py index a81c602..ebe5713 100644 --- a/spirit/game/scripts/cards/SWSH3/TapuKoko_61.py +++ b/spirit/game/scripts/cards/SWSH3/TapuKoko_61.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import draw_attack card = PokemonCardDef( guid="78fb512e-814c-5082-a9a1-7e6ea71f8a49", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Allure", game_text="Draw 2 cards.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=draw_attack(2), ), Attack( title="Electric Ball", diff --git a/spirit/game/scripts/cards/SWSH3/Teddiursa_138.py b/spirit/game/scripts/cards/SWSH3/Teddiursa_138.py index 2b6753e..8511d9a 100644 --- a/spirit/game/scripts/cards/SWSH3/Teddiursa_138.py +++ b/spirit/game/scripts/cards/SWSH3/Teddiursa_138.py @@ -1,6 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def baby_doll_eyes(ctx): + """During your opponent's next turn, the Defending Pokemon can't retreat.""" + defender = ctx.defender + if defender is not None and not ctx.effects_blocked(defender): + ctx.lock_retreat(defender) + + card = PokemonCardDef( guid="2a01ecb8-7a8e-5fcc-8a14-9e7a5f8f50c4", key="SWSH3", @@ -22,7 +30,7 @@ card = PokemonCardDef( title="Baby-Doll Eyes", game_text="During your opponent's next turn, the Defending Pok\u00e9mon can't retreat.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=baby_doll_eyes, ), Attack( title="Dig Claws", diff --git a/spirit/game/scripts/cards/SWSH3/Thievul_113.py b/spirit/game/scripts/cards/SWSH3/Thievul_113.py index 42a660b..f0d1214 100644 --- a/spirit/game/scripts/cards/SWSH3/Thievul_113.py +++ b/spirit/game/scripts/cards/SWSH3/Thievul_113.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_hand card = PokemonCardDef( guid="9206f148-f170-533c-96c3-8b94bfe3359f", @@ -23,7 +24,7 @@ card = PokemonCardDef( title="Nasty Plot", game_text="Search your deck for up to 2 cards and put them into your hand. Then, shuffle your deck.", cost={PokemonTypes.DARKNESS: 1}, - effect=unimplemented, + effect=search_to_hand(count=2), ), Attack( title="Sharp Fang", diff --git a/spirit/game/scripts/cards/SWSH3/Torchic_22.py b/spirit/game/scripts/cards/SWSH3/Torchic_22.py index 3172849..2600104 100644 --- a/spirit/game/scripts/cards/SWSH3/Torchic_22.py +++ b/spirit/game/scripts/cards/SWSH3/Torchic_22.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_bench card = PokemonCardDef( guid="ee286069-d121-5785-abef-678a2201af45", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Call for Family", game_text="Search your deck for a Basic Pok\u00e9mon and put it onto your Bench. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_to_bench(), ), Attack( title="Live Coal", diff --git a/spirit/game/scripts/cards/SWSH3/Toxapex_52.py b/spirit/game/scripts/cards/SWSH3/Toxapex_52.py index 7ca643c..2801702 100644 --- a/spirit/game/scripts/cards/SWSH3/Toxapex_52.py +++ b/spirit/game/scripts/cards/SWSH3/Toxapex_52.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.support_common import heal_attack card = PokemonCardDef( guid="4e2fa8bc-a6dc-5b4c-afe3-a14b191d970c", @@ -23,14 +25,14 @@ card = PokemonCardDef( title="Recover", game_text="Discard an Energy from this Pok\u00e9mon and heal all damage from it.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=heal_attack(all_damage=True, discard_energy=1, target="self"), ), Attack( title="Poisonous Whip", game_text="Your opponent's Active Pok\u00e9mon is now Poisoned.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=80, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Toxtricity_63.py b/spirit/game/scripts/cards/SWSH3/Toxtricity_63.py index 33dbeed..a898b54 100644 --- a/spirit/game/scripts/cards/SWSH3/Toxtricity_63.py +++ b/spirit/game/scripts/cards/SWSH3/Toxtricity_63.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack + + +async def risk_taker(ctx): + """Flip a coin. If heads, discard the top 5 cards of your opponent's + deck. If tails, discard the top 5 cards of your deck.""" + heads = await ctx.flip_coins(1, "Risk Taker") + pid = ctx.opponent_id if heads[0] else ctx.player_id + await ctx.discard_cards(ctx.deck_top(5, player_id=pid)) card = PokemonCardDef( guid="2686fb18-a6c3-507a-ac69-3b9084244e84", @@ -23,14 +32,14 @@ card = PokemonCardDef( title="Risk Taker", game_text="Flip a coin. If heads, discard the top 5 cards of your opponent's deck. If tails, discard the top 5 cards of your deck.", cost={PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + effect=risk_taker, ), Attack( title="Thunder Jolt", game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 1}, damage=160, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Trapinch_89.py b/spirit/game/scripts/cards/SWSH3/Trapinch_89.py index a03c969..dce37c1 100644 --- a/spirit/game/scripts/cards/SWSH3/Trapinch_89.py +++ b/spirit/game/scripts/cards/SWSH3/Trapinch_89.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if card = PokemonCardDef( guid="07f00c67-8ced-57b6-aa97-17c69854a741", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1}, damage=10, damage_operator="+", - effect=unimplemented, + effect=bonus_if(lambda ctx: ctx.stadium_in_play() is not None, 10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Tsareena_16.py b/spirit/game/scripts/cards/SWSH3/Tsareena_16.py index 17fc5f3..04c1f6e 100644 --- a/spirit/game/scripts/cards/SWSH3/Tsareena_16.py +++ b/spirit/game/scripts/cards/SWSH3/Tsareena_16.py @@ -1,5 +1,41 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import count_energy + + +async def power_whip(ctx): + """20 damage to 1 of your opponent's Pokemon for each Energy attached + to this Pokemon. (No W/R for Benched targets.)""" + amount = 20 * count_energy("self")(ctx) + if amount <= 0: + return + candidates = ctx.opponent_pokemon_in_play() + if not candidates: + return + target = await ctx.choose_pokemon( + candidates, "Choose 1 of your opponent's Pokémon to take damage.") + if target is not None: + await ctx.deal_damage(amount, target=target) + + +async def time_out_kick(ctx): + """100 damage. You may put an Energy attached to the opponent's Active + into their hand.""" + await ctx.deal_damage() + defender = ctx.opponent_active() + if defender is None or ctx.effects_blocked(defender): + return + energies = ctx.attached_energies(defender) + if not energies: + return + if not await ctx.ask_yes_no( + "Put an Energy attached to your opponent's Active Pokémon into their hand?" + ): + return + picks = await ctx.choose_cards( + energies, 1, prompt="Choose an Energy card to put into your opponent's hand.") + await ctx.put_in_hand(picks, reveal=False) + card = PokemonCardDef( guid="beb9e421-3375-5639-a705-b726c3f73565", @@ -23,14 +59,14 @@ card = PokemonCardDef( title="Power Whip", game_text="This attack does 20 damage to 1 of your opponent's Pok\u00e9mon for each Energy attached to this Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=power_whip, ), Attack( title="Time Out Kick", game_text="You may put an Energy attached to your opponent's Active Pok\u00e9mon into their hand.", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=100, - effect=unimplemented, + effect=time_out_kick, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/TurboPatch_172.py b/spirit/game/scripts/cards/SWSH3/TurboPatch_172.py index 770e49a..4f74c81 100644 --- a/spirit/game/scripts/cards/SWSH3/TurboPatch_172.py +++ b/spirit/game/scripts/cards/SWSH3/TurboPatch_172.py @@ -1,5 +1,38 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.card_effects.pokemon import is_pokemon_gx +from spirit.game.session.effects import is_basic_pokemon + + +async def turbo_patch(ctx): + """Flip a coin. If heads, attach a basic Energy card from your discard + pile to 1 of your Basic Pokemon that isn't a Pokemon-GX.""" + results = await ctx.flip_coins(1, "Turbo Patch") + if not results[0]: + return + energy = [c for c in ctx.discard_pile() if is_basic_energy_card(c)] + if not energy: + return + candidates = [ + p for p in ctx.my_pokemon_in_play() + if is_basic_pokemon(p) and not is_pokemon_gx(p.archetype_id) + ] + if not candidates: + return + picks = await ctx.choose_cards( + energy, 1, minimum=1, + prompt="Choose a basic Energy card to attach.", + ) + if not picks: + return + holder = await ctx.choose_pokemon( + candidates, "Choose a Basic Pokémon to attach the Energy to" + ) + if holder is None: + return + await ctx.attach_energy(picks[0], holder) + card = ItemCardDef( guid="def9aa09-7cab-5807-adcb-f92d74492dd1", @@ -11,5 +44,5 @@ card = ItemCardDef( collector_number=172, set_code="SWSH3", rarity=Rarities.Uncommon, - effect=unimplemented + effect=turbo_patch ) diff --git a/spirit/game/scripts/cards/SWSH3/TurboPatch_200.py b/spirit/game/scripts/cards/SWSH3/TurboPatch_200.py index 37d188e..f56ad47 100644 --- a/spirit/game/scripts/cards/SWSH3/TurboPatch_200.py +++ b/spirit/game/scripts/cards/SWSH3/TurboPatch_200.py @@ -1,5 +1,38 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.card_effects.pokemon import is_pokemon_gx +from spirit.game.session.effects import is_basic_pokemon + + +async def turbo_patch(ctx): + """Flip a coin. If heads, attach a basic Energy card from your discard + pile to 1 of your Basic Pokemon that isn't a Pokemon-GX.""" + results = await ctx.flip_coins(1, "Turbo Patch") + if not results[0]: + return + energy = [c for c in ctx.discard_pile() if is_basic_energy_card(c)] + if not energy: + return + candidates = [ + p for p in ctx.my_pokemon_in_play() + if is_basic_pokemon(p) and not is_pokemon_gx(p.archetype_id) + ] + if not candidates: + return + picks = await ctx.choose_cards( + energy, 1, minimum=1, + prompt="Choose a basic Energy card to attach.", + ) + if not picks: + return + holder = await ctx.choose_pokemon( + candidates, "Choose a Basic Pokémon to attach the Energy to" + ) + if holder is None: + return + await ctx.attach_energy(picks[0], holder) + card = ItemCardDef( guid="26ae623c-f182-52e5-9eb9-e150487126f3", @@ -11,5 +44,5 @@ card = ItemCardDef( collector_number=200, set_code="SWSH3", rarity=Rarities.RareSecret, - effect=unimplemented + effect=turbo_patch ) diff --git a/spirit/game/scripts/cards/SWSH3/Tyranitar_88.py b/spirit/game/scripts/cards/SWSH3/Tyranitar_88.py index 14dd497..bea47e7 100644 --- a/spirit/game/scripts/cards/SWSH3/Tyranitar_88.py +++ b/spirit/game/scripts/cards/SWSH3/Tyranitar_88.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import mill_attack + + +async def bedrock_breaker(ctx): + """120 damage, then discard a Stadium in play.""" + await ctx.deal_damage() + await ctx.discard_stadium() + card = PokemonCardDef( guid="468abb32-1857-5933-8013-64a5b15d51f7", @@ -24,14 +32,14 @@ card = PokemonCardDef( game_text="Discard a Stadium in play.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 2}, damage=120, - effect=unimplemented, + effect=bedrock_breaker, ), Attack( title="Mountain Swing", game_text="Discard the top 5 cards of your deck.", cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 2}, damage=250, - effect=unimplemented, + effect=mill_attack(5, opponent=False), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH3/Ursaring_139.py b/spirit/game/scripts/cards/SWSH3/Ursaring_139.py index 06e63ff..cd8fe8a 100644 --- a/spirit/game/scripts/cards/SWSH3/Ursaring_139.py +++ b/spirit/game/scripts/cards/SWSH3/Ursaring_139.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import mill_attack card = PokemonCardDef( guid="549f2f07-1440-5f1f-80a8-c7b1b4aba0c0", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Discard the top card of your opponent's deck.", cost={PokemonTypes.COLORLESS: 3}, damage=70, - effect=unimplemented, + effect=mill_attack(1, opponent=True), ), Attack( title="Claw Slash", diff --git a/spirit/game/scripts/cards/SWSH3/Vanillish_46.py b/spirit/game/scripts/cards/SWSH3/Vanillish_46.py index 5329fb7..f22f840 100644 --- a/spirit/game/scripts/cards/SWSH3/Vanillish_46.py +++ b/spirit/game/scripts/cards/SWSH3/Vanillish_46.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="48c8c42e-42b4-5910-ac10-e96a66dbd068", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.WATER: 1}, damage=30, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Vanillite_45.py b/spirit/game/scripts/cards/SWSH3/Vanillite_45.py index a2d2ef6..a0a0efd 100644 --- a/spirit/game/scripts/cards/SWSH3/Vanillite_45.py +++ b/spirit/game/scripts/cards/SWSH3/Vanillite_45.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="cae29e40-ee61-5cdb-97f4-3254a6346b9b", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.WATER: 1}, damage=10, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Vanilluxe_47.py b/spirit/game/scripts/cards/SWSH3/Vanilluxe_47.py index 2618fbd..08bbaf4 100644 --- a/spirit/game/scripts/cards/SWSH3/Vanilluxe_47.py +++ b/spirit/game/scripts/cards/SWSH3/Vanilluxe_47.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.pokemon import in_active_spot + + +async def bitter_cold(ctx): + """Once per turn, in the Active spot: you may flip a coin. Heads paralyzes the opponent's Active.""" + if not await ctx.ask_yes_no("Flip a coin?"): + return + if not (await ctx.flip_coins(1, "Bitter Cold"))[0]: + return + target = ctx.opponent_active() + if target is not None: + await ctx.apply_special_condition(target, SpecialConditions.PARALYZED) + card = PokemonCardDef( guid="d534da76-204d-52bf-ad38-18ee956ad1bb", @@ -22,7 +35,9 @@ card = PokemonCardDef( Ability( title="Bitter Cold", game_text="Once during your turn, if this Pok\u00e9mon is in the Active Spot, you may flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=in_active_spot, + effect=bitter_cold, ), Attack( title="Frost Smash", diff --git a/spirit/game/scripts/cards/SWSH3/Vibrava_90.py b/spirit/game/scripts/cards/SWSH3/Vibrava_90.py index 04b97f8..05b90ff 100644 --- a/spirit/game/scripts/cards/SWSH3/Vibrava_90.py +++ b/spirit/game/scripts/cards/SWSH3/Vibrava_90.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import spread_damage card = PokemonCardDef( guid="c54c0388-b430-5cd5-b5c1-1d4da58e7fcd", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="This attack also does 10 damage to each of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.FIGHTING: 1}, damage=20, - effect=unimplemented, + effect=spread_damage(10, side="opponent", also_base=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/VikavoltV_180.py b/spirit/game/scripts/cards/SWSH3/VikavoltV_180.py index ae3f346..ba93263 100644 --- a/spirit/game/scripts/cards/SWSH3/VikavoltV_180.py +++ b/spirit/game/scripts/cards/SWSH3/VikavoltV_180.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import self_energy_discard_attack +from spirit.game.session.effects import is_item_card + + +async def paralyzing_bolt(ctx): + """50 damage. During your opponent's next turn, they can't play any Item + cards from their hand.""" + await ctx.deal_damage() + ctx.lock_plays(ctx.opponent_id, is_item_card) + card = PokemonCardDef( guid="09a65584-324e-5e4a-a62f-062639f24345", @@ -23,14 +33,14 @@ card = PokemonCardDef( game_text="During your opponent's next turn, they can't play any Item cards from their hand.", cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 1}, damage=50, - effect=unimplemented, + effect=paralyzing_bolt, ), Attack( title="Super Zap Cannon", - game_text="Discard 2 Energy from this Pok\u00e9mon.", + game_text="Discard 2 Energy from this Pokémon.", cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 1}, damage=190, - effect=unimplemented, + effect=self_energy_discard_attack(count=2), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH3/VikavoltV_60.py b/spirit/game/scripts/cards/SWSH3/VikavoltV_60.py index 99af4f1..863c8da 100644 --- a/spirit/game/scripts/cards/SWSH3/VikavoltV_60.py +++ b/spirit/game/scripts/cards/SWSH3/VikavoltV_60.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import self_energy_discard_attack +from spirit.game.session.effects import is_item_card + + +async def paralyzing_bolt(ctx): + """50 damage. During your opponent's next turn, they can't play any Item + cards from their hand.""" + await ctx.deal_damage() + ctx.lock_plays(ctx.opponent_id, is_item_card) + card = PokemonCardDef( guid="a5c94ec1-7a70-58f1-a782-0bb279f6e4d5", @@ -23,14 +33,14 @@ card = PokemonCardDef( game_text="During your opponent's next turn, they can't play any Item cards from their hand.", cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 1}, damage=50, - effect=unimplemented, + effect=paralyzing_bolt, ), Attack( title="Super Zap Cannon", - game_text="Discard 2 Energy from this Pok\u00e9mon.", + game_text="Discard 2 Energy from this Pokémon.", cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 1}, damage=190, - effect=unimplemented, + effect=self_energy_discard_attack(count=2), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH3/Volcarona_30.py b/spirit/game/scripts/cards/SWSH3/Volcarona_30.py index 1418677..2e752d7 100644 --- a/spirit/game/scripts/cards/SWSH3/Volcarona_30.py +++ b/spirit/game/scripts/cards/SWSH3/Volcarona_30.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, has_damage card = PokemonCardDef( guid="98be6da3-5ced-5f5f-bd10-bfd86bc35794", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="If your opponent's Active Pok\u00e9mon has no damage counters on it before this attack does damage, this attack does nothing.", cost={PokemonTypes.FIRE: 1}, damage=80, - effect=unimplemented, + effect=bonus_if(has_damage("defender"), 0, else_nothing=True), ), Attack( title="Fire Wing", diff --git a/spirit/game/scripts/cards/SWSH3/Wigglytuff_68.py b/spirit/game/scripts/cards/SWSH3/Wigglytuff_68.py index e1e0230..5384caf 100644 --- a/spirit/game/scripts/cards/SWSH3/Wigglytuff_68.py +++ b/spirit/game/scripts/cards/SWSH3/Wigglytuff_68.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import flip_damage, condition_attack card = PokemonCardDef( guid="250b6445-f73f-552e-89b2-a168d787cadc", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Asleep.", cost={PokemonTypes.PSYCHIC: 1}, damage=30, - effect=unimplemented, + effect=condition_attack(SpecialConditions.ASLEEP), ), Attack( title="Double Smash", @@ -32,7 +33,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=90, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=2, per_heads=90), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Wimpod_17.py b/spirit/game/scripts/cards/SWSH3/Wimpod_17.py index 5866a06..080934f 100644 --- a/spirit/game/scripts/cards/SWSH3/Wimpod_17.py +++ b/spirit/game/scripts/cards/SWSH3/Wimpod_17.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import switch_self_attack card = PokemonCardDef( guid="489e6617-79b5-5b08-8186-322145f84bc1", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Switch this Pok\u00e9mon with 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.GRASS: 1}, damage=10, - effect=unimplemented, + effect=switch_self_attack(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH3/Wishiwashi_50.py b/spirit/game/scripts/cards/SWSH3/Wishiwashi_50.py index feb2eb8..15836fd 100644 --- a/spirit/game/scripts/cards/SWSH3/Wishiwashi_50.py +++ b/spirit/game/scripts/cards/SWSH3/Wishiwashi_50.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import shuffle_hand_into_deck_draw card = PokemonCardDef( guid="1afc9468-a7ce-5865-8656-21b2119ada76", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Deep Sea Swirl", game_text="Shuffle your hand into your deck. Then, draw 8 cards.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=shuffle_hand_into_deck_draw(8), ), Attack( title="Wave Splash", diff --git a/spirit/game/scripts/cards/SWSH3/YellHorn_173.py b/spirit/game/scripts/cards/SWSH3/YellHorn_173.py index 7beb768..ae17c21 100644 --- a/spirit/game/scripts/cards/SWSH3/YellHorn_173.py +++ b/spirit/game/scripts/cards/SWSH3/YellHorn_173.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import ItemCardDef +from spirit.game.attributes import Rarities, SpecialConditions + + +async def yell_horn(ctx): + """Both Active Pokemon are now Confused.""" + my_active = ctx.my_active() + if my_active is not None: + await ctx.apply_special_condition(my_active, SpecialConditions.CONFUSED) + opp_active = ctx.opponent_active() + if opp_active is not None: + await ctx.apply_special_condition(opp_active, SpecialConditions.CONFUSED) + card = ItemCardDef( guid="3a6478e1-c769-5d5d-83e4-ed876b96d75b", @@ -11,5 +22,5 @@ card = ItemCardDef( collector_number=173, set_code="SWSH3", rarity=Rarities.Uncommon, - effect=unimplemented + effect=yell_horn ) diff --git a/spirit/game/scripts/cards/SWSH35/AlcremieVMAX_23.py b/spirit/game/scripts/cards/SWSH35/AlcremieVMAX_23.py index 797265b..1f47f58 100644 --- a/spirit/game/scripts/cards/SWSH35/AlcremieVMAX_23.py +++ b/spirit/game/scripts/cards/SWSH35/AlcremieVMAX_23.py @@ -1,6 +1,45 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.pokemon import energy_provides_type +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, def_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +def _is_psychic_energy(card): + return is_basic_energy_card(card) and \ + energy_provides_type(card, PokemonTypes.PSYCHIC.value) + + +async def adornment(ctx): + """For each Benched Pokemon, search a Psychic Energy onto it; shuffle.""" + for pokemon in list(ctx.my_bench()): + definition = def_for(pokemon.archetype_id) + name = getattr(definition, "display_name", None) or "your Benched Pokémon" + picks = await ctx.search_deck( + _is_psychic_energy, count=1, minimum=0, + prompt=f"Choose a Psychic Energy card to attach to {name}.", + ) + if picks: + if pokemon.entity_id not in ctx.visual_targets: + ctx.visual_targets.append(pokemon.entity_id) + await ctx.attach_energy(picks[0], pokemon) + await ctx.shuffle_deck() + + +async def g_max_whisk(ctx): + """Discard any amount of Energy from your Pokemon; 60 per card discarded.""" + energies = [e for p in ctx.my_pokemon_in_play() + for e in ctx.attached_energies(p)] + picks = [] + if energies: + picks = await ctx.choose_cards( + energies, len(energies), minimum=0, + prompt="Choose any amount of Energy to discard from your Pokémon.", + ) + if picks: + await ctx.discard_cards(picks) + await ctx.deal_damage(60 * len(picks)) + + card = PokemonCardDef( guid="9bed37b6-27be-5e5d-b060-8d85c4558174", key="SWSH35", @@ -21,17 +60,17 @@ card = PokemonCardDef( abilities=[ Attack( title="Adornment", - game_text="For each of your Benched Pok\u00e9mon, search your deck for a Psychic Energy card and attach it to that Pok\u00e9mon. Then, shuffle your deck.", + game_text="For each of your Benched Pokémon, search your deck for a Psychic Energy card and attach it to that Pokémon. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=adornment, ), Attack( title="G-Max Whisk", - game_text="Discard any amount of Energy from your Pok\u00e9mon. This attack does 60 damage for each card you discarded in this way.", + game_text="Discard any amount of Energy from your Pokémon. This attack does 60 damage for each card you discarded in this way.", cost={PokemonTypes.PSYCHIC: 2}, damage=60, damage_operator="x", - effect=unimplemented, + effect=g_max_whisk, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH35/AlcremieV_22.py b/spirit/game/scripts/cards/SWSH35/AlcremieV_22.py index 5e6e226..26d38b1 100644 --- a/spirit/game/scripts/cards/SWSH35/AlcremieV_22.py +++ b/spirit/game/scripts/cards/SWSH35/AlcremieV_22.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_basic_pokemon +from spirit.game.card_effects.attacks_common import lock_defender_attacks + + +async def sugary_sprinkles(ctx): + """Heal 30 damage from each of your Benched Pokemon.""" + for pokemon in ctx.my_bench(): + await ctx.heal(30, pokemon) + + +async def sweet_splash(ctx): + """100 damage. If the Defending Pokemon is a Basic Pokemon, it can't attack next turn.""" + await ctx.deal_damage() + defender = ctx.defender + if defender is not None and is_basic_pokemon(defender): + lock_defender_attacks(ctx) + card = PokemonCardDef( guid="20b7d156-1a87-541f-8701-635a4f00af34", @@ -22,14 +39,14 @@ card = PokemonCardDef( title="Sugary Sprinkles", game_text="Heal 30 damage from each of your Benched Pok\u00e9mon.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=sugary_sprinkles, ), Attack( title="Sweet Splash", game_text="If the Defending Pok\u00e9mon is a Basic Pok\u00e9mon, it can't attack during your opponent's next turn.", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 2}, damage=100, - effect=unimplemented, + effect=sweet_splash, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH35/Altaria_49.py b/spirit/game/scripts/cards/SWSH35/Altaria_49.py index 2e2d6df..66246f2 100644 --- a/spirit/game/scripts/cards/SWSH35/Altaria_49.py +++ b/spirit/game/scripts/cards/SWSH35/Altaria_49.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, is_pokemon_v from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import prevent_damage_when +from spirit.game.card_effects.pokemon import is_pokemon_gx + + +def _v_or_gx_attack_on_carrier(calc, carrier) -> bool: + return ( + calc.target is carrier + and calc.attacker is not None + and (is_pokemon_v(calc.attacker.archetype_id) or is_pokemon_gx(calc.attacker.archetype_id)) + ) + card = PokemonCardDef( guid="15143446-31a6-5341-b5aa-670dd522342d", @@ -23,7 +34,7 @@ card = PokemonCardDef( Ability( title="Miraculous Charm", game_text="Prevent all damage done to this Pok\u00e9mon by attacks from your opponent's Pok\u00e9mon V and Pok\u00e9mon-GX.", - effect=unimplemented, + passive=prevent_damage_when(_v_or_gx_attack_on_carrier), ), Attack( title="Speed Dive", diff --git a/spirit/game/scripts/cards/SWSH35/Beedrill_4.py b/spirit/game/scripts/cards/SWSH35/Beedrill_4.py index b2e3fc8..155dc1e 100644 --- a/spirit/game/scripts/cards/SWSH35/Beedrill_4.py +++ b/spirit/game/scripts/cards/SWSH35/Beedrill_4.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.data_utils import PokemonCardDef, Attack +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions card = PokemonCardDef( guid="d1eafda6-4d85-591b-a209-874b59185f69", @@ -21,10 +22,10 @@ card = PokemonCardDef( abilities=[ Attack( title="Poison Jab", - game_text="Your opponent's Active Pok\u00e9mon is now Poisoned.", + game_text="Your opponent's Active Pokémon is now Poisoned.", cost={PokemonTypes.GRASS: 1}, damage=80, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH35/Centiskorch_10.py b/spirit/game/scripts/cards/SWSH35/Centiskorch_10.py index 9b4e2f2..b594be7 100644 --- a/spirit/game/scripts/cards/SWSH35/Centiskorch_10.py +++ b/spirit/game/scripts/cards/SWSH35/Centiskorch_10.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="4fb2b086-2019-52b8-a161-bd748c96544e", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Burned.", cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 2}, damage=50, - effect=unimplemented, + effect=condition_attack(SpecialConditions.BURNED), ), Attack( title="Heat Crawler", diff --git a/spirit/game/scripts/cards/SWSH35/CharizardVMAX_74.py b/spirit/game/scripts/cards/SWSH35/CharizardVMAX_74.py index 2bddcba..9d120a2 100644 --- a/spirit/game/scripts/cards/SWSH35/CharizardVMAX_74.py +++ b/spirit/game/scripts/cards/SWSH35/CharizardVMAX_74.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import self_energy_discard_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -29,7 +30,7 @@ card = PokemonCardDef( game_text="Discard 2 Energy from this Pok\u00e9mon.", cost={PokemonTypes.FIRE: 3, PokemonTypes.COLORLESS: 2}, damage=300, - effect=unimplemented, + effect=self_energy_discard_attack(count=2), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH35/CharizardV_79.py b/spirit/game/scripts/cards/SWSH35/CharizardV_79.py index f9a002d..b98f0a2 100644 --- a/spirit/game/scripts/cards/SWSH35/CharizardV_79.py +++ b/spirit/game/scripts/cards/SWSH35/CharizardV_79.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import self_energy_discard_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -28,7 +29,7 @@ card = PokemonCardDef( game_text="Discard 2 Energy from this Pok\u00e9mon.", cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 2}, damage=220, - effect=unimplemented, + effect=self_energy_discard_attack(count=2), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH35/DrednawVMAX_15.py b/spirit/game/scripts/cards/SWSH35/DrednawVMAX_15.py index fd19762..402f2b8 100644 --- a/spirit/game/scripts/cards/SWSH35/DrednawVMAX_15.py +++ b/spirit/game/scripts/cards/SWSH35/DrednawVMAX_15.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus +from spirit.game.card_effects.passives_common import takes_less_passive card = PokemonCardDef( guid="ebda6d7b-eac9-57ca-921f-592f186edf65", @@ -22,7 +24,7 @@ card = PokemonCardDef( Ability( title="Solid Shell", game_text="This Pok\u00e9mon takes 30 less damage from attacks (after applying Weakness and Resistance).", - effect=unimplemented, + passive=takes_less_passive(30), ), Attack( title="G-Max Headbutt", @@ -30,7 +32,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, damage=160, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH35/DrednawVMAX_75.py b/spirit/game/scripts/cards/SWSH35/DrednawVMAX_75.py index f8fc31d..33f17ce 100644 --- a/spirit/game/scripts/cards/SWSH35/DrednawVMAX_75.py +++ b/spirit/game/scripts/cards/SWSH35/DrednawVMAX_75.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus +from spirit.game.card_effects.passives_common import takes_less_passive card = PokemonCardDef( guid="b93d9d2e-8870-5ad7-a687-61d647737357", @@ -22,7 +24,7 @@ card = PokemonCardDef( Ability( title="Solid Shell", game_text="This Pok\u00e9mon takes 30 less damage from attacks (after applying Weakness and Resistance).", - effect=unimplemented, + passive=takes_less_passive(30), ), Attack( title="G-Max Headbutt", @@ -30,7 +32,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, damage=160, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH35/DrednawV_14.py b/spirit/game/scripts/cards/SWSH35/DrednawV_14.py index 67d05e9..39870b2 100644 --- a/spirit/game/scripts/cards/SWSH35/DrednawV_14.py +++ b/spirit/game/scripts/cards/SWSH35/DrednawV_14.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import takes_less_passive +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="2d251cfb-4b45-5d50-8608-945478d94d2c", @@ -21,14 +23,14 @@ card = PokemonCardDef( Ability( title="Solid Shell", game_text="This Pok\u00e9mon takes 30 less damage from attacks (after applying Weakness and Resistance).", - effect=unimplemented, + passive=takes_less_passive(30), ), Attack( title="Powerful Bite", game_text="During your opponent's next turn, the Defending Pok\u00e9mon can't retreat.", cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, damage=130, - effect=unimplemented, + effect=condition_attack(no_retreat=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH35/DrednawV_69.py b/spirit/game/scripts/cards/SWSH35/DrednawV_69.py index 8d53b5b..d6f59bf 100644 --- a/spirit/game/scripts/cards/SWSH35/DrednawV_69.py +++ b/spirit/game/scripts/cards/SWSH35/DrednawV_69.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import takes_less_passive +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="65c26128-0121-5b6b-b9d5-82ca35bcba27", @@ -21,14 +23,14 @@ card = PokemonCardDef( Ability( title="Solid Shell", game_text="This Pok\u00e9mon takes 30 less damage from attacks (after applying Weakness and Resistance).", - effect=unimplemented, + passive=takes_less_passive(30), ), Attack( title="Powerful Bite", game_text="During your opponent's next turn, the Defending Pok\u00e9mon can't retreat.", cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, damage=130, - effect=unimplemented, + effect=condition_attack(no_retreat=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH35/DuraludonV_47.py b/spirit/game/scripts/cards/SWSH35/DuraludonV_47.py index 2a6f29f..0ce9e0b 100644 --- a/spirit/game/scripts/cards/SWSH35/DuraludonV_47.py +++ b/spirit/game/scripts/cards/SWSH35/DuraludonV_47.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy +from spirit.game.card_effects.passives_common import takes_less_passive card = PokemonCardDef( guid="8523b874-1d31-51cb-86ad-af56f008c716", @@ -22,7 +24,7 @@ card = PokemonCardDef( Ability( title="Hard Coat", game_text="This Pok\u00e9mon takes 30 less damage from attacks (after applying Weakness and Resistance).", - effect=unimplemented, + passive=takes_less_passive(30), ), Attack( title="Gatling Slug", @@ -30,7 +32,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=10, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("self", energy_type=PokemonTypes.METAL), 40, base=10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH35/EldegossV_5.py b/spirit/game/scripts/cards/SWSH35/EldegossV_5.py index e046282..a206b81 100644 --- a/spirit/game/scripts/cards/SWSH35/EldegossV_5.py +++ b/spirit/game/scripts/cards/SWSH35/EldegossV_5.py @@ -1,5 +1,23 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import remove_self_from_play +from spirit.game.session.effects import is_supporter_card + + +async def happy_match(ctx): + """On play from hand onto Bench: you may put a Supporter card from your + discard pile into your hand.""" + supporters = [c for c in ctx.discard_pile() if is_supporter_card(c)] + if not supporters: + return + if not await ctx.ask_yes_no( + "Put a Supporter card from your discard pile into your hand?" + ): + return + picks = await ctx.choose_cards( + supporters, 1, prompt="Choose a Supporter card to put into your hand." + ) + await ctx.put_in_hand(picks, reveal=False) card = PokemonCardDef( guid="38550279-82e4-527e-82f4-6deacda81cac", @@ -21,14 +39,15 @@ card = PokemonCardDef( Ability( title="Happy Match", game_text="When you play this Pok\u00e9mon from your hand onto your Bench during your turn, you may put a Supporter card from your discard pile into your hand.", - effect=unimplemented, + trigger=Triggers.ON_PLAY, + effect=happy_match, ), Attack( title="Float Up", game_text="You may shuffle this Pok\u00e9mon and all cards attached to it into your deck.", cost={PokemonTypes.COLORLESS: 2}, damage=50, - effect=unimplemented, + effect=remove_self_from_play("deck", optional=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH35/FullHeal_51.py b/spirit/game/scripts/cards/SWSH35/FullHeal_51.py index 38795fe..4cf13d9 100644 --- a/spirit/game/scripts/cards/SWSH35/FullHeal_51.py +++ b/spirit/game/scripts/cards/SWSH35/FullHeal_51.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.support_common import cure_conditions_effect card = ItemCardDef( guid="b74e0d98-d02b-5b18-8527-d8afd24ebf08", @@ -11,5 +12,5 @@ card = ItemCardDef( collector_number=51, set_code="SWSH35", rarity=Rarities.Uncommon, - effect=unimplemented + effect=cure_conditions_effect("active") ) diff --git a/spirit/game/scripts/cards/SWSH35/GalarianCursolaV_21.py b/spirit/game/scripts/cards/SWSH35/GalarianCursolaV_21.py index 0059a63..e4f63c2 100644 --- a/spirit/game/scripts/cards/SWSH35/GalarianCursolaV_21.py +++ b/spirit/game/scripts/cards/SWSH35/GalarianCursolaV_21.py @@ -1,5 +1,24 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import in_active_spot + + +async def gnawing_aura(ctx): + if not in_active_spot(ctx.board, ctx.player_id, ctx.source): + return + if ctx.attaching_player_id != ctx.opponent_id: + return + receiver = ctx.energy_receiver + if receiver is None or receiver.owning_player_id != ctx.opponent_id: + return + await ctx.deal_damage(30, target=receiver, as_counters=True) + + +async def hollow_missile(ctx): + await ctx.deal_damage() + bench = ctx.opponent_bench() + if bench: + await ctx.place_damage_counters(3, bench) card = PokemonCardDef( guid="b84138c7-be04-5eb9-bbc7-a05f28bb0729", @@ -22,14 +41,15 @@ card = PokemonCardDef( Ability( title="Gnawing Aura", game_text="As long as this Pok\u00e9mon is in the Active Spot, whenever your opponent attaches an Energy card from their hand to 1 of their Pok\u00e9mon, put 3 damage counters on that Pok\u00e9mon.", - effect=unimplemented, + trigger=Triggers.ON_ENERGY_ATTACHED, + effect=gnawing_aura, ), Attack( title="Hollow Missile", game_text="Put 3 damage counters on your opponent's Benched Pok\u00e9mon in any way you like.", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=60, - effect=unimplemented, + effect=hollow_missile, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH35/GalarianCursolaV_71.py b/spirit/game/scripts/cards/SWSH35/GalarianCursolaV_71.py index 5cd9681..b96e05f 100644 --- a/spirit/game/scripts/cards/SWSH35/GalarianCursolaV_71.py +++ b/spirit/game/scripts/cards/SWSH35/GalarianCursolaV_71.py @@ -1,5 +1,24 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import in_active_spot + + +async def gnawing_aura(ctx): + if not in_active_spot(ctx.board, ctx.player_id, ctx.source): + return + if ctx.attaching_player_id != ctx.opponent_id: + return + receiver = ctx.energy_receiver + if receiver is None or receiver.owning_player_id != ctx.opponent_id: + return + await ctx.deal_damage(30, target=receiver, as_counters=True) + + +async def hollow_missile(ctx): + await ctx.deal_damage() + bench = ctx.opponent_bench() + if bench: + await ctx.place_damage_counters(3, bench) card = PokemonCardDef( guid="b9a4964e-aed4-5b33-80e0-5f7545517600", @@ -22,14 +41,15 @@ card = PokemonCardDef( Ability( title="Gnawing Aura", game_text="As long as this Pok\u00e9mon is in the Active Spot, whenever your opponent attaches an Energy card from their hand to 1 of their Pok\u00e9mon, put 3 damage counters on that Pok\u00e9mon.", - effect=unimplemented, + trigger=Triggers.ON_ENERGY_ATTACHED, + effect=gnawing_aura, ), Attack( title="Hollow Missile", game_text="Put 3 damage counters on your opponent's Benched Pok\u00e9mon in any way you like.", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=60, - effect=unimplemented, + effect=hollow_missile, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH35/GalarianLinoone_36.py b/spirit/game/scripts/cards/SWSH35/GalarianLinoone_36.py index 7651665..a6cc4f1 100644 --- a/spirit/game/scripts/cards/SWSH35/GalarianLinoone_36.py +++ b/spirit/game/scripts/cards/SWSH35/GalarianLinoone_36.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack card = PokemonCardDef( guid="f189dc20-9f3b-5806-b976-5d0b240b0c3c", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="This Pok\u00e9mon also does 20 damage to itself.", cost={PokemonTypes.COLORLESS: 2}, damage=60, - effect=unimplemented, + effect=recoil_attack(20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH35/GalarianObstagoon_37.py b/spirit/game/scripts/cards/SWSH35/GalarianObstagoon_37.py index 098bfea..4d27f6e 100644 --- a/spirit/game/scripts/cards/SWSH35/GalarianObstagoon_37.py +++ b/spirit/game/scripts/cards/SWSH35/GalarianObstagoon_37.py @@ -1,5 +1,31 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import lock_all_attacks + + +def _wicked_ruler_condition(board, player_id, pokemon): + opponent = next((pid for pid in board.player_ids if pid != player_id), None) + if opponent is None: + return False + hand = board.find_player_area(opponent, "hand") + return bool(hand) and len(hand.children) > 4 + + +async def wicked_ruler(ctx): + hand = ctx.hand(ctx.opponent_id) + excess = len(hand) - 4 + if excess <= 0: + return + await ctx.discard_from_hand( + excess, minimum=excess, player_id=ctx.opponent_id, + prompt="Discard cards until you have 4 cards in your hand.", + ) + + +async def knuckle_impact(ctx): + await ctx.deal_damage() + lock_all_attacks(ctx, ctx.attacker) + card = PokemonCardDef( guid="fee652b7-2d45-54cc-96af-0de41ccfe985", @@ -22,14 +48,16 @@ card = PokemonCardDef( Ability( title="Wicked Ruler", game_text="Once during your turn, you may have your opponent discard cards from their hand until they have 4 cards in their hand.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=_wicked_ruler_condition, + effect=wicked_ruler, ), Attack( title="Knuckle Impact", game_text="During your next turn, this Pok\u00e9mon can't attack.", cost={PokemonTypes.COLORLESS: 3}, damage=180, - effect=unimplemented, + effect=knuckle_impact, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH35/GalarianZigzagoon_35.py b/spirit/game/scripts/cards/SWSH35/GalarianZigzagoon_35.py index 8b9070e..8e05eb0 100644 --- a/spirit/game/scripts/cards/SWSH35/GalarianZigzagoon_35.py +++ b/spirit/game/scripts/cards/SWSH35/GalarianZigzagoon_35.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="f56dacb4-3377-552a-8e78-687193fbc142", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 1}, damage=10, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=4, per_heads=10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH35/GardevoirVMAX_17.py b/spirit/game/scripts/cards/SWSH35/GardevoirVMAX_17.py index 2cfa98d..29bee8d 100644 --- a/spirit/game/scripts/cards/SWSH35/GardevoirVMAX_17.py +++ b/spirit/game/scripts/cards/SWSH35/GardevoirVMAX_17.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import heal_attack card = PokemonCardDef( guid="6fabb536-2341-533a-a50e-4f6b152fb674", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Heal 50 damage from this Pok\u00e9mon.", cost={PokemonTypes.PSYCHIC: 2, PokemonTypes.COLORLESS: 1}, damage=180, - effect=unimplemented, + effect=heal_attack(50), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH35/GardevoirVMAX_76.py b/spirit/game/scripts/cards/SWSH35/GardevoirVMAX_76.py index e218ad2..1c498e0 100644 --- a/spirit/game/scripts/cards/SWSH35/GardevoirVMAX_76.py +++ b/spirit/game/scripts/cards/SWSH35/GardevoirVMAX_76.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import heal_attack card = PokemonCardDef( guid="a3f770e9-2819-5ab9-835a-36f3df1a8a66", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Heal 50 damage from this Pok\u00e9mon.", cost={PokemonTypes.PSYCHIC: 2, PokemonTypes.COLORLESS: 1}, damage=180, - effect=unimplemented, + effect=heal_attack(50), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH35/GardevoirV_16.py b/spirit/game/scripts/cards/SWSH35/GardevoirV_16.py index 8b76ace..d135aff 100644 --- a/spirit/game/scripts/cards/SWSH35/GardevoirV_16.py +++ b/spirit/game/scripts/cards/SWSH35/GardevoirV_16.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if + + +def _healed_this_turn(ctx): + return ctx.attacker.entity_id in ctx.session.turn_state.healed_entities + card = PokemonCardDef( guid="7aca8ca1-5bee-5796-a864-844d62096c66", @@ -29,7 +35,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 2, PokemonTypes.COLORLESS: 1}, damage=120, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_healed_this_turn, 80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH35/GardevoirV_70.py b/spirit/game/scripts/cards/SWSH35/GardevoirV_70.py index 12efd9e..89d7355 100644 --- a/spirit/game/scripts/cards/SWSH35/GardevoirV_70.py +++ b/spirit/game/scripts/cards/SWSH35/GardevoirV_70.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if + + +def _healed_this_turn(ctx): + return ctx.attacker.entity_id in ctx.session.turn_state.healed_entities + card = PokemonCardDef( guid="09446d01-b852-5d6b-8e85-faec31fd84ca", @@ -29,7 +35,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 2, PokemonTypes.COLORLESS: 1}, damage=120, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_healed_this_turn, 80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH35/GrapploctV_32.py b/spirit/game/scripts/cards/SWSH35/GrapploctV_32.py index fab91ec..a986e94 100644 --- a/spirit/game/scripts/cards/SWSH35/GrapploctV_32.py +++ b/spirit/game/scripts/cards/SWSH35/GrapploctV_32.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus, lock_defender_attacks +from spirit.game.session.effects import is_basic_pokemon + + +async def tie_up(ctx): + """20. If the Defending Pokemon is a Basic Pokemon, it can't attack next turn.""" + await ctx.deal_damage() + defender = ctx.defender + if defender is not None and is_basic_pokemon(defender): + lock_defender_attacks(ctx) card = PokemonCardDef( guid="8cd580fa-4f9a-5092-9f33-47025ace07dd", @@ -23,7 +33,7 @@ card = PokemonCardDef( game_text="If the Defending Pok\u00e9mon is a Basic Pok\u00e9mon, it can't attack during your opponent's next turn.", cost={PokemonTypes.FIGHTING: 1}, damage=20, - effect=unimplemented, + effect=tie_up, ), Attack( title="Moonsault Press", @@ -31,7 +41,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=120, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(100), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH35/GrapploctV_72.py b/spirit/game/scripts/cards/SWSH35/GrapploctV_72.py index 9e9c6a8..b3b90e3 100644 --- a/spirit/game/scripts/cards/SWSH35/GrapploctV_72.py +++ b/spirit/game/scripts/cards/SWSH35/GrapploctV_72.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus, lock_defender_attacks +from spirit.game.session.effects import is_basic_pokemon + + +async def tie_up(ctx): + """20. If the Defending Pokemon is a Basic Pokemon, it can't attack next turn.""" + await ctx.deal_damage() + defender = ctx.defender + if defender is not None and is_basic_pokemon(defender): + lock_defender_attacks(ctx) card = PokemonCardDef( guid="2f7d8d20-2774-5675-88bc-69748b09faf6", @@ -23,7 +33,7 @@ card = PokemonCardDef( game_text="If the Defending Pok\u00e9mon is a Basic Pok\u00e9mon, it can't attack during your opponent's next turn.", cost={PokemonTypes.FIGHTING: 1}, damage=20, - effect=unimplemented, + effect=tie_up, ), Attack( title="Moonsault Press", @@ -31,7 +41,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=120, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(100), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH35/GreatBall_52.py b/spirit/game/scripts/cards/SWSH35/GreatBall_52.py index b52fc82..8be8e4d 100644 --- a/spirit/game/scripts/cards/SWSH35/GreatBall_52.py +++ b/spirit/game/scripts/cards/SWSH35/GreatBall_52.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef from spirit.game.attributes import Rarities +from spirit.game.session.effects import is_pokemon_card + + +async def great_ball(ctx): + """Look at the top 7 cards of your deck. You may reveal a Pokemon you + find there and put it into your hand. Shuffle the other cards back.""" + top = ctx.deck_top(7) + candidates = [c for c in top if is_pokemon_card(c)] + if candidates: + picks = await ctx.choose_cards( + candidates, 1, minimum=0, + prompt="You may put a Pokémon into your hand.", + display_cards=top, + ) + await ctx.put_in_hand(picks, reveal=True) + await ctx.shuffle_deck() + card = ItemCardDef( guid="7f1116a3-d6a8-509d-83d1-f9147fb353b1", @@ -11,5 +28,5 @@ card = ItemCardDef( collector_number=52, set_code="SWSH35", rarity=Rarities.Uncommon, - effect=unimplemented + effect=great_ball ) diff --git a/spirit/game/scripts/cards/SWSH35/Hatterene_20.py b/spirit/game/scripts/cards/SWSH35/Hatterene_20.py index 7860e73..f2fdbbc 100644 --- a/spirit/game/scripts/cards/SWSH35/Hatterene_20.py +++ b/spirit/game/scripts/cards/SWSH35/Hatterene_20.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.support_common import heal_attack +from spirit.game.card_effects.pokemon import in_active_spot + + +async def hazard_sensor(ctx): + """Active Spot only: after being damaged (even if Knocked Out), the + Attacking Pokemon is now Confused.""" + if not in_active_spot(ctx.board, ctx.player_id, ctx.source): + return + attacker = ctx.damaged_by + if attacker is None: + return + await ctx.apply_special_condition(attacker, SpecialConditions.CONFUSED) + card = PokemonCardDef( guid="b6b48707-12f3-59da-9660-ae6568c803c0", @@ -23,14 +37,15 @@ card = PokemonCardDef( Ability( title="Hazard Sensor", game_text="If this Pok\u00e9mon is in the Active Spot and is damaged by an attack from your opponent's Pok\u00e9mon (even if this Pok\u00e9mon is Knocked Out), the Attacking Pok\u00e9mon is now Confused.", - effect=unimplemented, + trigger=Triggers.ON_DAMAGED_BY_ATTACK, + effect=hazard_sensor, ), Attack( title="Life Sucker", game_text="Heal 30 damage from this Pok\u00e9mon.", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=100, - effect=unimplemented, + effect=heal_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH35/HyperPotion_54.py b/spirit/game/scripts/cards/SWSH35/HyperPotion_54.py index b4857b5..4207410 100644 --- a/spirit/game/scripts/cards/SWSH35/HyperPotion_54.py +++ b/spirit/game/scripts/cards/SWSH35/HyperPotion_54.py @@ -1,5 +1,27 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.support_common import requires_in_play +from spirit.game.models.board import BoardState + + +def _has_two_energy(pokemon): + return len(BoardState.attached_energies(pokemon)) >= 2 + + +async def hyper_potion(ctx): + """Heal 120 from 1 of your Pokemon with >=2 Energy attached; if healed, discard 2 Energy from it.""" + candidates = [p for p in ctx.my_pokemon_in_play() if _has_two_energy(p)] + if not candidates: + return + target = await ctx.choose_pokemon( + candidates, "Choose a Pokémon with at least 2 Energy attached" + ) + if target is None: + return + healed = await ctx.heal(120, target) + if healed: + await ctx.discard_energy_from(target, 2, prompt="Discard 2 Energy") + card = ItemCardDef( guid="3e5af8f4-dd69-526e-89ab-ac2efe65def2", @@ -11,5 +33,6 @@ card = ItemCardDef( collector_number=54, set_code="SWSH35", rarity=Rarities.Uncommon, - effect=unimplemented + effect=hyper_potion, + condition=requires_in_play(_has_two_energy), ) diff --git a/spirit/game/scripts/cards/SWSH35/IncineroarV_8.py b/spirit/game/scripts/cards/SWSH35/IncineroarV_8.py index 48cbbf2..ff4f133 100644 --- a/spirit/game/scripts/cards/SWSH35/IncineroarV_8.py +++ b/spirit/game/scripts/cards/SWSH35/IncineroarV_8.py @@ -1,5 +1,24 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack +from spirit.game.card_effects.support_common import attach_from_discard +from spirit.game.card_effects.pokemon import is_energy_card, energy_provides_type + + +def _is_fire_energy(card): + return is_energy_card(card) and energy_provides_type(card, PokemonTypes.FIRE.value) + + +def _is_benched(pokemon): + parent = getattr(pokemon, "parent", None) + return bool(parent) and parent.get_attribute(AttrID.NAME) == "bench" + + +grand_flame = attach_from_discard( + predicate=_is_fire_energy, count=2, target=_is_benched, + prompt="Choose up to 2 Fire Energy cards to attach.", +) + card = PokemonCardDef( guid="85c3c274-ebeb-55ba-9399-6d0464feb806", @@ -23,14 +42,14 @@ card = PokemonCardDef( game_text="Attach up to 2 Fire Energy cards from your discard pile to 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=90, - effect=unimplemented, + effect=grand_flame, ), Attack( title="Flare Blitzer", game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.FIRE: 3, PokemonTypes.COLORLESS: 1}, damage=220, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH35/Inkay_44.py b/spirit/game/scripts/cards/SWSH35/Inkay_44.py index 9d68fa1..fd33c72 100644 --- a/spirit/game/scripts/cards/SWSH35/Inkay_44.py +++ b/spirit/game/scripts/cards/SWSH35/Inkay_44.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import bonus_if, defender_has_condition card = PokemonCardDef( guid="2b98d304-57b8-5825-9d87-37364a80ee33", @@ -25,11 +26,11 @@ card = PokemonCardDef( ), Attack( title="Attack from Behind", - game_text="If your opponent's Active Pok\u00e9mon is Confused, this attack does 50 more damage.", + game_text="If your opponent's Active Pokémon is Confused, this attack does 50 more damage.", cost={PokemonTypes.COLORLESS: 2}, damage=20, damage_operator="+", - effect=unimplemented, + effect=bonus_if(defender_has_condition(SpecialConditions.CONFUSED), 50), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH35/Liepard_40.py b/spirit/game/scripts/cards/SWSH35/Liepard_40.py index 9d14225..74aae2d 100644 --- a/spirit/game/scripts/cards/SWSH35/Liepard_40.py +++ b/spirit/game/scripts/cards/SWSH35/Liepard_40.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.passives_common import condition_immunity_passive card = PokemonCardDef( guid="3ce55b0b-fde2-51ca-bfa2-c5878146bc43", @@ -22,7 +23,7 @@ card = PokemonCardDef( Ability( title="Limber", game_text="This Pok\u00e9mon can't be Paralyzed.", - effect=unimplemented, + passive=condition_immunity_passive(SpecialConditions.PARALYZED), ), Attack( title="Slashing Claw", diff --git a/spirit/game/scripts/cards/SWSH35/LucarioV_27.py b/spirit/game/scripts/cards/SWSH35/LucarioV_27.py index d61eb96..e924482 100644 --- a/spirit/game/scripts/cards/SWSH35/LucarioV_27.py +++ b/spirit/game/scripts/cards/SWSH35/LucarioV_27.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import snipe_attack card = PokemonCardDef( guid="b409c006-964b-50e3-b0f6-7f0f5b4e4245", @@ -23,14 +24,14 @@ card = PokemonCardDef( game_text="This attack also does 20 damage to 1 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.FIGHTING: 1}, damage=40, - effect=unimplemented, + effect=snipe_attack(20, pool="bench", count=1, also_base=True), ), Attack( title="Beatdown Smash", game_text="During your next turn, this Pok\u00e9mon can't use Beatdown Smash.", cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=180, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH35/Lycanroc_30.py b/spirit/game/scripts/cards/SWSH35/Lycanroc_30.py index 8d26147..9cda886 100644 --- a/spirit/game/scripts/cards/SWSH35/Lycanroc_30.py +++ b/spirit/game/scripts/cards/SWSH35/Lycanroc_30.py @@ -1,4 +1,4 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -29,7 +29,7 @@ card = PokemonCardDef( game_text="During your next turn, this Pok\u00e9mon can't use Slashing Strike.", cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=150, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH35/Machamp_26.py b/spirit/game/scripts/cards/SWSH35/Machamp_26.py index 48c0040..c35cbe4 100644 --- a/spirit/game/scripts/cards/SWSH35/Machamp_26.py +++ b/spirit/game/scripts/cards/SWSH35/Machamp_26.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_discard, recoil_attack +from spirit.game.session.effects import is_pokemon_card + + +def _is_fighting_pokemon(card): + return is_pokemon_card(card) and \ + PokemonTypes.FIGHTING.value in (card.get_attribute(AttrID.POKEMON_TYPES) or []) + card = PokemonCardDef( guid="7313c25e-63b7-52bf-82c1-69dd4e02e6d5", @@ -25,14 +33,14 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 1}, damage=20, damage_operator="x", - effect=unimplemented, + effect=damage_per(count_discard("mine", pred=_is_fighting_pokemon), 20), ), Attack( title="Dynamite Punch", game_text="This Pok\u00e9mon also does 50 damage to itself.", cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=200, - effect=unimplemented, + effect=recoil_attack(50), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH35/Machoke_25.py b/spirit/game/scripts/cards/SWSH35/Machoke_25.py index 2ddd7df..4948c6f 100644 --- a/spirit/game/scripts/cards/SWSH35/Machoke_25.py +++ b/spirit/game/scripts/cards/SWSH35/Machoke_25.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus card = PokemonCardDef( guid="008dbf69-1408-588f-9737-0229fe46aaf8", @@ -30,7 +31,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=50, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(70), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH35/Machop_24.py b/spirit/game/scripts/cards/SWSH35/Machop_24.py index 78311ac..0bd5a8c 100644 --- a/spirit/game/scripts/cards/SWSH35/Machop_24.py +++ b/spirit/game/scripts/cards/SWSH35/Machop_24.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="6bd4d57d-c633-5ee3-83e1-6bbb4f14c1fd", @@ -29,7 +30,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=flip_damage(bonus_per_heads=40), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH35/Malamar_45.py b/spirit/game/scripts/cards/SWSH35/Malamar_45.py index 8e196d5..5a60df1 100644 --- a/spirit/game/scripts/cards/SWSH35/Malamar_45.py +++ b/spirit/game/scripts/cards/SWSH35/Malamar_45.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack, flip_damage card = PokemonCardDef( guid="c5bf0144-2769-550f-a546-0410c1993f83", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Confused.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 1}, damage=50, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED), ), Attack( title="Random Peck", @@ -32,7 +33,7 @@ card = PokemonCardDef( cost={PokemonTypes.DARKNESS: 2, PokemonTypes.COLORLESS: 1}, damage=80, damage_operator="+", - effect=unimplemented, + effect=flip_damage(coins=2, bonus_per_heads=40), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH35/Nickit_46.py b/spirit/game/scripts/cards/SWSH35/Nickit_46.py index ed959b6..40853be 100644 --- a/spirit/game/scripts/cards/SWSH35/Nickit_46.py +++ b/spirit/game/scripts/cards/SWSH35/Nickit_46.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import draw_attack card = PokemonCardDef( guid="1ef9e2c5-2103-5449-83ed-c06b2637bd2b", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Filch", game_text="Draw a card.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=draw_attack(1), ), Attack( title="Tail Smack", diff --git a/spirit/game/scripts/cards/SWSH35/PokBall_59.py b/spirit/game/scripts/cards/SWSH35/PokBall_59.py index 22a0b97..5068973 100644 --- a/spirit/game/scripts/cards/SWSH35/PokBall_59.py +++ b/spirit/game/scripts/cards/SWSH35/PokBall_59.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef from spirit.game.attributes import Rarities +from spirit.game.session.effects import is_pokemon_card + + +async def poke_ball(ctx): + """Flip a coin. If heads, search the deck for a Pokemon, reveal it, and + put it into your hand. Then, shuffle your deck.""" + if (await ctx.flip_coins(1, "Poké Ball"))[0]: + picks = await ctx.search_deck( + is_pokemon_card, count=1, minimum=0, + prompt="Choose a Pokémon to put into your hand.", + ) + await ctx.put_in_hand(picks, reveal=True) + await ctx.shuffle_deck() + card = ItemCardDef( guid="48d40f73-67d8-5590-a364-b2924d11e230", @@ -11,5 +25,5 @@ card = ItemCardDef( collector_number=59, set_code="SWSH35", rarity=Rarities.Common, - effect=unimplemented + effect=poke_ball ) diff --git a/spirit/game/scripts/cards/SWSH35/Potion_61.py b/spirit/game/scripts/cards/SWSH35/Potion_61.py index 2414317..0bf1821 100644 --- a/spirit/game/scripts/cards/SWSH35/Potion_61.py +++ b/spirit/game/scripts/cards/SWSH35/Potion_61.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.support_common import heal_item, requires_damaged_pokemon card = ItemCardDef( guid="71dc4fb3-ce42-51a8-940d-1684a0cfc130", @@ -11,5 +12,6 @@ card = ItemCardDef( collector_number=61, set_code="SWSH35", rarity=Rarities.Common, - effect=unimplemented + condition=requires_damaged_pokemon(), + effect=heal_item(30) ) diff --git a/spirit/game/scripts/cards/SWSH35/Purrloin_39.py b/spirit/game/scripts/cards/SWSH35/Purrloin_39.py index 5278103..acd1398 100644 --- a/spirit/game/scripts/cards/SWSH35/Purrloin_39.py +++ b/spirit/game/scripts/cards/SWSH35/Purrloin_39.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="37009835-3964-51db-b000-b050a99f5417", @@ -28,7 +29,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.COLORLESS: 2}, damage=20, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH35/Rockruff_29.py b/spirit/game/scripts/cards/SWSH35/Rockruff_29.py index a4c2f68..2fda7bc 100644 --- a/spirit/game/scripts/cards/SWSH35/Rockruff_29.py +++ b/spirit/game/scripts/cards/SWSH35/Rockruff_29.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import draw_attack card = PokemonCardDef( guid="f3531071-63a4-59bf-bae0-ede9d6160bfc", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Collect", game_text="Draw a card.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=draw_attack(1), ), Attack( title="Bite", diff --git a/spirit/game/scripts/cards/SWSH35/RotomBike_63.py b/spirit/game/scripts/cards/SWSH35/RotomBike_63.py index 6e8e0b7..ccf7eca 100644 --- a/spirit/game/scripts/cards/SWSH35/RotomBike_63.py +++ b/spirit/game/scripts/cards/SWSH35/RotomBike_63.py @@ -1,6 +1,13 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef from spirit.game.attributes import Rarities + +async def _rotom_bike_effect(ctx): + """Draw cards until you have 6 cards in your hand. Your turn ends.""" + await ctx.draw_until(6) + ctx.ends_turn = True + + card = ItemCardDef( guid="a78976d1-210b-51fc-b16c-686b107a2df6", key="SWSH35", @@ -11,5 +18,5 @@ card = ItemCardDef( collector_number=63, set_code="SWSH35", rarity=Rarities.Uncommon, - effect=unimplemented + effect=_rotom_bike_effect, ) diff --git a/spirit/game/scripts/cards/SWSH35/Scrafty_42.py b/spirit/game/scripts/cards/SWSH35/Scrafty_42.py index 727fe42..1b5f174 100644 --- a/spirit/game/scripts/cards/SWSH35/Scrafty_42.py +++ b/spirit/game/scripts/cards/SWSH35/Scrafty_42.py @@ -1,6 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import bonus_if +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +_played_piers_this_turn = lambda ctx: ctx.played_trainer_this_turn("Piers") > 0 + + +async def corner(ctx): + """30 damage; the Defending Pokemon can't retreat during your opponent's next turn.""" + await ctx.deal_damage() + defender = ctx.defender + if defender is not None and not ctx.effects_blocked(defender): + ctx.lock_retreat(defender) + + card = PokemonCardDef( guid="6675e84d-50f9-5bad-881d-1bf39c83c4ba", key="SWSH35", @@ -24,7 +36,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, the Defending Pok\u00e9mon can't retreat.", cost={PokemonTypes.DARKNESS: 1}, damage=30, - effect=unimplemented, + effect=corner, ), Attack( title="Bad Brawl", @@ -32,7 +44,7 @@ card = PokemonCardDef( cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 2}, damage=90, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_played_piers_this_turn, 90), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH35/Sharpedo_12.py b/spirit/game/scripts/cards/SWSH35/Sharpedo_12.py index e1a572b..bed30a0 100644 --- a/spirit/game/scripts/cards/SWSH35/Sharpedo_12.py +++ b/spirit/game/scripts/cards/SWSH35/Sharpedo_12.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import snipe_attack card = PokemonCardDef( guid="128212c9-925f-5a01-9ce1-7a6e7f7a06c1", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="This attack also does 20 damage to 1 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.WATER: 1}, damage=50, - effect=unimplemented, + effect=snipe_attack(20, also_base=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH35/SuspiciousFoodTin_66.py b/spirit/game/scripts/cards/SWSH35/SuspiciousFoodTin_66.py index 6844783..25e7e29 100644 --- a/spirit/game/scripts/cards/SWSH35/SuspiciousFoodTin_66.py +++ b/spirit/game/scripts/cards/SWSH35/SuspiciousFoodTin_66.py @@ -1,5 +1,36 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import ItemCardDef +from spirit.game.attributes import Rarities, PokemonTypes +from spirit.game.card_effects.support_common import requires_in_play +from spirit.game.card_effects.pokemon import energy_provides_type +from spirit.game.models.board import BoardState + + +def _has_psychic_energy(pokemon): + return any( + energy_provides_type(e, PokemonTypes.PSYCHIC.value) + for e in BoardState.attached_energies(pokemon) + ) + + +async def suspicious_food_tin(ctx): + """Heal 80 from 1 of your Pokemon with a Psychic Energy attached; if you + healed any damage, discard a Psychic Energy from it.""" + candidates = [p for p in ctx.my_pokemon_in_play() if _has_psychic_energy(p)] + if not candidates: + return + target = await ctx.choose_pokemon( + candidates, "Choose a Pokémon with a Psychic Energy attached." + ) + if target is None: + return + healed = await ctx.heal(80, target=target) + if healed > 0: + await ctx.discard_energy_from( + target, 1, + predicate=lambda e: energy_provides_type(e, PokemonTypes.PSYCHIC.value), + prompt="Discard a Psychic Energy", + ) + card = ItemCardDef( guid="ccfd3ce5-c0c1-546d-8f83-3d8a1c13f123", @@ -11,5 +42,6 @@ card = ItemCardDef( collector_number=66, set_code="SWSH35", rarity=Rarities.Uncommon, - effect=unimplemented + effect=suspicious_food_tin, + condition=requires_in_play(_has_psychic_energy), ) diff --git a/spirit/game/scripts/cards/SWSH35/SuspiciousFoodTin_80.py b/spirit/game/scripts/cards/SWSH35/SuspiciousFoodTin_80.py index 1027220..57f2f86 100644 --- a/spirit/game/scripts/cards/SWSH35/SuspiciousFoodTin_80.py +++ b/spirit/game/scripts/cards/SWSH35/SuspiciousFoodTin_80.py @@ -1,5 +1,36 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import ItemCardDef +from spirit.game.attributes import Rarities, PokemonTypes +from spirit.game.card_effects.support_common import requires_in_play +from spirit.game.card_effects.pokemon import energy_provides_type +from spirit.game.models.board import BoardState + + +def _has_psychic_energy(pokemon): + return any( + energy_provides_type(e, PokemonTypes.PSYCHIC.value) + for e in BoardState.attached_energies(pokemon) + ) + + +async def suspicious_food_tin(ctx): + """Heal 80 from 1 of your Pokemon with a Psychic Energy attached; if you + healed any damage, discard a Psychic Energy from it.""" + candidates = [p for p in ctx.my_pokemon_in_play() if _has_psychic_energy(p)] + if not candidates: + return + target = await ctx.choose_pokemon( + candidates, "Choose a Pokémon with a Psychic Energy attached." + ) + if target is None: + return + healed = await ctx.heal(80, target=target) + if healed > 0: + await ctx.discard_energy_from( + target, 1, + predicate=lambda e: energy_provides_type(e, PokemonTypes.PSYCHIC.value), + prompt="Discard a Psychic Energy", + ) + card = ItemCardDef( guid="e308d253-7bdb-53bf-afd3-489be5af1e07", @@ -11,5 +42,6 @@ card = ItemCardDef( collector_number=80, set_code="SWSH35", rarity=Rarities.RareSecret, - effect=unimplemented + effect=suspicious_food_tin, + condition=requires_in_play(_has_psychic_energy), ) diff --git a/spirit/game/scripts/cards/SWSH35/Trubbish_43.py b/spirit/game/scripts/cards/SWSH35/Trubbish_43.py index aa91fdb..326e5a4 100644 --- a/spirit/game/scripts/cards/SWSH35/Trubbish_43.py +++ b/spirit/game/scripts/cards/SWSH35/Trubbish_43.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_bonus_attack card = PokemonCardDef( guid="b40a1482-a785-5011-96eb-7cc0c4d58c46", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=20, damage_operator="+", - effect=unimplemented, + effect=condition_bonus_attack(50, SpecialConditions.POISONED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH35/TurffieldStadium_68.py b/spirit/game/scripts/cards/SWSH35/TurffieldStadium_68.py index ccf2d47..325e87f 100644 --- a/spirit/game/scripts/cards/SWSH35/TurffieldStadium_68.py +++ b/spirit/game/scripts/cards/SWSH35/TurffieldStadium_68.py @@ -1,5 +1,30 @@ -from spirit.game.data_utils import StadiumCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import StadiumCardDef, Ability, Activations +from spirit.game.attributes import Rarities, PokemonTypes, AttrID +from spirit.game.session.effects import is_evolution_pokemon + + +def _is_evolution_grass(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_evolution_pokemon(card) and PokemonTypes.GRASS.value in types + + +async def turffield_stadium(ctx): + """Once during each player's turn, that player may search their deck for + an Evolution Grass Pokemon, reveal it, and put it into their hand.""" + picks = await ctx.search_deck( + _is_evolution_grass, count=1, minimum=0, + prompt="Choose an Evolution Grass Pokémon to put into your hand.", + ) + await ctx.put_in_hand(picks, reveal=True) + await ctx.shuffle_deck() + + +TURFFIELD_STADIUM_ABILITY = Ability( + title="Turffield Stadium", + game_text="Once during each player's turn, that player may search their deck for an Evolution Grass Pokémon, reveal it, and put it into their hand. Then, that player shuffles their deck.", + activation=Activations.ONCE_PER_TURN, + effect=turffield_stadium, +) card = StadiumCardDef( guid="645a53c8-3e64-58c3-8d2b-9f451b815cf0", @@ -11,5 +36,5 @@ card = StadiumCardDef( collector_number=68, set_code="SWSH35", rarity=Rarities.Uncommon, - effect=unimplemented + ability=TURFFIELD_STADIUM_ABILITY, ) diff --git a/spirit/game/scripts/cards/SWSH35/VenusaurV_1.py b/spirit/game/scripts/cards/SWSH35/VenusaurV_1.py index 9ecdf43..9c3730d 100644 --- a/spirit/game/scripts/cards/SWSH35/VenusaurV_1.py +++ b/spirit/game/scripts/cards/SWSH35/VenusaurV_1.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="51a3799e-5340-54c7-8eb7-52443988cd3c", @@ -23,14 +24,14 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Asleep and Poisoned.", cost={PokemonTypes.GRASS: 2, PokemonTypes.COLORLESS: 1}, damage=80, - effect=unimplemented, + effect=condition_attack(SpecialConditions.ASLEEP, SpecialConditions.POISONED), ), Attack( title="Solar Typhoon", game_text="During your next turn, this Pok\u00e9mon can't use Solar Typhoon.", cost={PokemonTypes.GRASS: 3, PokemonTypes.COLORLESS: 1}, damage=220, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH35/Victini_7.py b/spirit/game/scripts/cards/SWSH35/Victini_7.py index 15b1e39..c27034e 100644 --- a/spirit/game/scripts/cards/SWSH35/Victini_7.py +++ b/spirit/game/scripts/cards/SWSH35/Victini_7.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import draw_attack card = PokemonCardDef( guid="5a308c17-2dcc-51f9-8346-c92a5c43e464", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Quick Draw", game_text="Draw a card.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=draw_attack(1), ), Attack( title="Combustion", diff --git a/spirit/game/scripts/cards/SWSH35/Vulpix_6.py b/spirit/game/scripts/cards/SWSH35/Vulpix_6.py index 099de69..dcc6bd7 100644 --- a/spirit/game/scripts/cards/SWSH35/Vulpix_6.py +++ b/spirit/game/scripts/cards/SWSH35/Vulpix_6.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="96836189-448c-5ec5-b038-f449d7a7e34f", @@ -27,7 +28,7 @@ card = PokemonCardDef( title="Singe", game_text="Your opponent's Active Pok\u00e9mon is now Burned.", cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=condition_attack(SpecialConditions.BURNED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH35/WailordV_13.py b/spirit/game/scripts/cards/SWSH35/WailordV_13.py index 53ea020..04a2022 100644 --- a/spirit/game/scripts/cards/SWSH35/WailordV_13.py +++ b/spirit/game/scripts/cards/SWSH35/WailordV_13.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.attacks_common import flip_damage +from spirit.game.card_effects.support_common import attach_from_discard +from spirit.game.card_effects.trainers import is_energy_card + + +def _is_water_energy_card(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_energy_card(card) and PokemonTypes.WATER.value in types + card = PokemonCardDef( guid="341e6658-ed19-5f21-a01e-a721d6cde541", @@ -22,7 +31,10 @@ card = PokemonCardDef( title="Draw Up", game_text="Attach up to 3 Water Energy cards from your discard pile to this Pok\u00e9mon.", cost={PokemonTypes.WATER: 1}, - effect=unimplemented, + effect=attach_from_discard( + predicate=_is_water_energy_card, count=3, minimum=0, target="self", + prompt="Choose up to 3 Water Energy cards to attach to this Pokémon", + ), ), Attack( title="Ocean Waves", @@ -30,7 +42,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 4}, damage=120, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=3, per_heads=120), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH35/Weedle_2.py b/spirit/game/scripts/cards/SWSH35/Weedle_2.py index a284b4c..53cf1cf 100644 --- a/spirit/game/scripts/cards/SWSH35/Weedle_2.py +++ b/spirit/game/scripts/cards/SWSH35/Weedle_2.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_bench card = PokemonCardDef( guid="867776c3-c9a0-5785-916f-84c79da4d36b", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Call for Family", game_text="Search your deck for a Basic Pok\u00e9mon and put it onto your Bench. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_to_bench(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH35/Zygarde_28.py b/spirit/game/scripts/cards/SWSH35/Zygarde_28.py index 5c97076..8332043 100644 --- a/spirit/game/scripts/cards/SWSH35/Zygarde_28.py +++ b/spirit/game/scripts/cards/SWSH35/Zygarde_28.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import self_energy_discard_attack card = PokemonCardDef( guid="e1822a90-07ef-5210-98b4-dcd5c77d1dff", @@ -28,7 +29,7 @@ card = PokemonCardDef( game_text="Discard a Fighting Energy from this Pok\u00e9mon.", cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 2}, damage=130, - effect=unimplemented, + effect=self_energy_discard_attack(count=1, energy_type=PokemonTypes.FIGHTING), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/AegislashVMAX_127.py b/spirit/game/scripts/cards/SWSH4/AegislashVMAX_127.py index 5b76207..0804e53 100644 --- a/spirit/game/scripts/cards/SWSH4/AegislashVMAX_127.py +++ b/spirit/game/scripts/cards/SWSH4/AegislashVMAX_127.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_prizes_taken card = PokemonCardDef( guid="34cd3966-5bc2-508d-868e-c49c4b32005c", @@ -26,7 +27,7 @@ card = PokemonCardDef( cost={PokemonTypes.METAL: 2, PokemonTypes.COLORLESS: 1}, damage=160, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_prizes_taken("mine"), 30, base=160), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/AegislashVMAX_190.py b/spirit/game/scripts/cards/SWSH4/AegislashVMAX_190.py index 713c8b9..150cae8 100644 --- a/spirit/game/scripts/cards/SWSH4/AegislashVMAX_190.py +++ b/spirit/game/scripts/cards/SWSH4/AegislashVMAX_190.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_prizes_taken card = PokemonCardDef( guid="668200e8-a452-554c-a54d-5d6fed80754a", @@ -26,7 +27,7 @@ card = PokemonCardDef( cost={PokemonTypes.METAL: 2, PokemonTypes.COLORLESS: 1}, damage=160, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_prizes_taken("mine"), 30, base=160), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/AegislashV_126.py b/spirit/game/scripts/cards/SWSH4/AegislashV_126.py index 26fcf37..330c1b3 100644 --- a/spirit/game/scripts/cards/SWSH4/AegislashV_126.py +++ b/spirit/game/scripts/cards/SWSH4/AegislashV_126.py @@ -1,6 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def sonic_edge(ctx): + """This attack's damage isn't affected by any effects on the Defending Pokemon.""" + await ctx.deal_damage(ignore_target_effects=True) + + card = PokemonCardDef( guid="5067d94f-3bbf-5560-b203-4ed795d7bea5", key="SWSH4", @@ -29,7 +35,7 @@ card = PokemonCardDef( game_text="This attack's damage isn't affected by any effects on your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.METAL: 2, PokemonTypes.COLORLESS: 1}, damage=130, - effect=unimplemented, + effect=sonic_edge, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/AegislashV_177.py b/spirit/game/scripts/cards/SWSH4/AegislashV_177.py index deb3c41..c69b176 100644 --- a/spirit/game/scripts/cards/SWSH4/AegislashV_177.py +++ b/spirit/game/scripts/cards/SWSH4/AegislashV_177.py @@ -1,6 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def sonic_edge(ctx): + """This attack's damage isn't affected by any effects on the Defending Pokemon.""" + await ctx.deal_damage(ignore_target_effects=True) + + card = PokemonCardDef( guid="4428f9d9-dd5d-5ebb-9b53-c19aa10e9c13", key="SWSH4", @@ -29,7 +35,7 @@ card = PokemonCardDef( game_text="This attack's damage isn't affected by any effects on your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.METAL: 2, PokemonTypes.COLORLESS: 1}, damage=130, - effect=unimplemented, + effect=sonic_edge, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/AlakazamV_172.py b/spirit/game/scripts/cards/SWSH4/AlakazamV_172.py index f6617eb..cbd59f3 100644 --- a/spirit/game/scripts/cards/SWSH4/AlakazamV_172.py +++ b/spirit/game/scripts/cards/SWSH4/AlakazamV_172.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import ( + count_hand, damage_per, place_counters, +) card = PokemonCardDef( guid="437c158a-9906-5ac2-8fce-3e3ae7706a96", @@ -21,9 +24,9 @@ card = PokemonCardDef( abilities=[ Attack( title="Zen Spoon", - game_text="Put 3 damage counters on your opponent's Pok\u00e9mon in any way you like.", + game_text="Put 3 damage counters on your opponent's Pokémon in any way you like.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=place_counters(3, "choose_any_opponent"), ), Attack( title="Mind Ruler", @@ -31,7 +34,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 2}, damage=30, damage_operator="x", - effect=unimplemented, + effect=damage_per(count_hand("opponent"), 30), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH4/Alcremie_81.py b/spirit/game/scripts/cards/SWSH4/Alcremie_81.py index c900043..a6a7c20 100644 --- a/spirit/game/scripts/cards/SWSH4/Alcremie_81.py +++ b/spirit/game/scripts/cards/SWSH4/Alcremie_81.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions + + +async def sharing_sweets(ctx): + """On evolve: you may have each player draw a card.""" + if await ctx.ask_yes_no("Have each player draw a card?"): + await ctx.draw_cards(1) + await ctx.draw_cards(1, player_id=ctx.opponent_id) + card = PokemonCardDef( guid="caed74b9-f0e0-53cd-a5aa-837cd01b4fa8", @@ -21,15 +30,16 @@ card = PokemonCardDef( abilities=[ Ability( title="Sharing Sweets", - game_text="When you play this Pok\u00e9mon from your hand to evolve 1 of your Pok\u00e9mon during your turn, you may have each player draw a card.", - effect=unimplemented, + game_text="When you play this Pokémon from your hand to evolve 1 of your Pokémon during your turn, you may have each player draw a card.", + trigger=Triggers.ON_EVOLVE, + effect=sharing_sweets, ), Attack( title="Wonder Shine", - game_text="Your opponent's Active Pok\u00e9mon is now Confused.", + game_text="Your opponent's Active Pokémon is now Confused.", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 2}, damage=60, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH4/Allister_146.py b/spirit/game/scripts/cards/SWSH4/Allister_146.py index 88c7fee..c947f97 100644 --- a/spirit/game/scripts/cards/SWSH4/Allister_146.py +++ b/spirit/game/scripts/cards/SWSH4/Allister_146.py @@ -1,6 +1,18 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities + +async def allister(ctx): + """Draw 3 cards. If you drew any, discard up to 3 (at least 1) from hand.""" + drawn = await ctx.draw_cards(3) + if drawn <= 0: + return + count = min(3, ctx.hand_size()) + if count <= 0: + return + await ctx.discard_from_hand(count, minimum=1, prompt="Discard up to 3 cards from your hand") + + card = SupporterCardDef( guid="4cdb6c17-8450-575f-8ca8-3f514e171af9", key="SWSH4", @@ -11,5 +23,5 @@ card = SupporterCardDef( collector_number=146, set_code="SWSH4", rarity=Rarities.Uncommon, - effect=unimplemented + effect=allister ) diff --git a/spirit/game/scripts/cards/SWSH4/Allister_179.py b/spirit/game/scripts/cards/SWSH4/Allister_179.py index e61eea2..39c4b65 100644 --- a/spirit/game/scripts/cards/SWSH4/Allister_179.py +++ b/spirit/game/scripts/cards/SWSH4/Allister_179.py @@ -1,6 +1,18 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities + +async def allister(ctx): + """Draw 3 cards. If you drew any, discard up to 3 (at least 1) from hand.""" + drawn = await ctx.draw_cards(3) + if drawn <= 0: + return + count = min(3, ctx.hand_size()) + if count <= 0: + return + await ctx.discard_from_hand(count, minimum=1, prompt="Discard up to 3 cards from your hand") + + card = SupporterCardDef( guid="5907badd-fda5-51be-b93b-88475e5d0986", key="SWSH4", @@ -11,5 +23,5 @@ card = SupporterCardDef( collector_number=179, set_code="SWSH4", rarity=Rarities.RareUltra, - effect=unimplemented + effect=allister ) diff --git a/spirit/game/scripts/cards/SWSH4/Allister_192.py b/spirit/game/scripts/cards/SWSH4/Allister_192.py index 5374a59..e49d48a 100644 --- a/spirit/game/scripts/cards/SWSH4/Allister_192.py +++ b/spirit/game/scripts/cards/SWSH4/Allister_192.py @@ -1,6 +1,18 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities + +async def allister(ctx): + """Draw 3 cards. If you drew any, discard up to 3 (at least 1) from hand.""" + drawn = await ctx.draw_cards(3) + if drawn <= 0: + return + count = min(3, ctx.hand_size()) + if count <= 0: + return + await ctx.discard_from_hand(count, minimum=1, prompt="Discard up to 3 cards from your hand") + + card = SupporterCardDef( guid="3b15b907-d1ba-5330-b6dc-32a506b89346", key="SWSH4", @@ -11,5 +23,5 @@ card = SupporterCardDef( collector_number=192, set_code="SWSH4", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=allister ) diff --git a/spirit/game/scripts/cards/SWSH4/AmpharosV_171.py b/spirit/game/scripts/cards/SWSH4/AmpharosV_171.py index 43ffb87..2a3a7e7 100644 --- a/spirit/game/scripts/cards/SWSH4/AmpharosV_171.py +++ b/spirit/game/scripts/cards/SWSH4/AmpharosV_171.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.data_utils import PokemonCardDef, Attack +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities, SpecialConditions + + +async def damaging_spark(ctx): + """120, plus 30 to each damaged opposing Benched Pokemon (no W/R on Bench).""" + await ctx.deal_damage() + for target in ctx.opponent_bench(): + if target.get_attribute(AttrID.HP, 0) < ctx.max_hp(target): + await ctx.deal_damage(30, target=target, apply_modifiers=False) + card = PokemonCardDef( guid="d4b28a19-94ee-5481-97a7-48901bf07f1d", @@ -20,17 +30,17 @@ card = PokemonCardDef( abilities=[ Attack( title="Dazzle Blast", - game_text="Your opponent's Active Pok\u00e9mon is now Confused.", + game_text="Your opponent's Active Pokémon is now Confused.", cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 1}, damage=50, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED), ), Attack( title="Damaging Spark", - game_text="This attack also does 30 damage to each of your opponent's Benched Pok\u00e9mon that has any damage counters on it. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", + game_text="This attack also does 30 damage to each of your opponent's Benched Pokémon that has any damage counters on it. (Don't apply Weakness and Resistance for Benched Pokémon.)", cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + effect=damaging_spark, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH4/AmpharosV_49.py b/spirit/game/scripts/cards/SWSH4/AmpharosV_49.py index 7c6f828..83bda9e 100644 --- a/spirit/game/scripts/cards/SWSH4/AmpharosV_49.py +++ b/spirit/game/scripts/cards/SWSH4/AmpharosV_49.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.data_utils import PokemonCardDef, Attack +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities, SpecialConditions + + +async def damaging_spark(ctx): + """120, plus 30 to each damaged opposing Benched Pokemon (no W/R on Bench).""" + await ctx.deal_damage() + for target in ctx.opponent_bench(): + if target.get_attribute(AttrID.HP, 0) < ctx.max_hp(target): + await ctx.deal_damage(30, target=target, apply_modifiers=False) + card = PokemonCardDef( guid="484d8cc2-b2ee-5885-b55f-eb84ab1b52e6", @@ -20,17 +30,17 @@ card = PokemonCardDef( abilities=[ Attack( title="Dazzle Blast", - game_text="Your opponent's Active Pok\u00e9mon is now Confused.", + game_text="Your opponent's Active Pokémon is now Confused.", cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 1}, damage=50, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED), ), Attack( title="Damaging Spark", - game_text="This attack also does 30 damage to each of your opponent's Benched Pok\u00e9mon that has any damage counters on it. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", + game_text="This attack also does 30 damage to each of your opponent's Benched Pokémon that has any damage counters on it. (Don't apply Weakness and Resistance for Benched Pokémon.)", cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + effect=damaging_spark, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH4/Arrokuda_41.py b/spirit/game/scripts/cards/SWSH4/Arrokuda_41.py index b204091..922b6e0 100644 --- a/spirit/game/scripts/cards/SWSH4/Arrokuda_41.py +++ b/spirit/game/scripts/cards/SWSH4/Arrokuda_41.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_bench + + +async def flock(ctx): + """Search your deck for up to 2 Arrokuda and put them onto your Bench.""" + same_archetype = ctx.source.archetype_id + await search_to_bench( + lambda c: c.archetype_id == same_archetype, count=2, + prompt="Choose up to 2 Arrokuda to put onto your Bench.", + )(ctx) + card = PokemonCardDef( guid="abf209f4-394b-5963-979d-ea7ce09865c6", @@ -22,7 +33,7 @@ card = PokemonCardDef( title="Flock", game_text="Search your deck for up to 2 Arrokuda and put them onto your Bench. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=flock, ), Attack( title="Peck", diff --git a/spirit/game/scripts/cards/SWSH4/Banette_68.py b/spirit/game/scripts/cards/SWSH4/Banette_68.py index 3a662bf..00574f4 100644 --- a/spirit/game/scripts/cards/SWSH4/Banette_68.py +++ b/spirit/game/scripts/cards/SWSH4/Banette_68.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented, Triggers +from spirit.game.card_effects.pokemon import curse_of_devolution from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -22,8 +23,9 @@ card = PokemonCardDef( abilities=[ Ability( title="Curse of Devolution", + trigger=Triggers.ON_EVOLVE, game_text="When you play this Pok\u00e9mon from your hand to evolve 1 of your Pok\u00e9mon during your turn, you may devolve 1 of your opponent's Benched evolved Pok\u00e9mon by putting the highest Stage Evolution card on it into your opponent's hand.", - effect=unimplemented, + effect=curse_of_devolution, ), Attack( title="Spooky Shot", diff --git a/spirit/game/scripts/cards/SWSH4/Barraskewda_42.py b/spirit/game/scripts/cards/SWSH4/Barraskewda_42.py index cbf0a17..462c14d 100644 --- a/spirit/game/scripts/cards/SWSH4/Barraskewda_42.py +++ b/spirit/game/scripts/cards/SWSH4/Barraskewda_42.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID + + +async def targeted_skewer(ctx): + """20 damage to a chosen opposing Benched Pokémon for each damage + counter already on it (no Weakness/Resistance for Benched Pokémon).""" + bench = ctx.opponent_bench() + if not bench: + return + target = await ctx.choose_pokemon( + bench, "Choose 1 of your opponent's Benched Pokémon" + ) + if target is None: + return + counters = max(0, (ctx.max_hp(target) - target.get_attribute(AttrID.HP, 0)) // 10) + if counters: + await ctx.deal_damage(20 * counters, target=target, apply_modifiers=False) + card = PokemonCardDef( guid="39ae597b-4774-5908-974c-116cdbfa0069", @@ -23,7 +40,7 @@ card = PokemonCardDef( title="Targeted Skewer", game_text="This attack does 20 damage to 1 of your opponent's Benched Pok\u00e9mon for each damage counter on that Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.WATER: 1}, - effect=unimplemented, + effect=targeted_skewer, ), Attack( title="Jet Headbutt", diff --git a/spirit/game/scripts/cards/SWSH4/Bea_147.py b/spirit/game/scripts/cards/SWSH4/Bea_147.py index 34276a4..de1ab69 100644 --- a/spirit/game/scripts/cards/SWSH4/Bea_147.py +++ b/spirit/game/scripts/cards/SWSH4/Bea_147.py @@ -1,5 +1,27 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import SupporterCardDef +from spirit.game.attributes import AttrID, PokemonTypes, Rarities +from spirit.game.card_effects.trainers import is_energy_card +from spirit.game.card_effects.support_common import distribute_energy + + +def _is_fighting_pokemon(pokemon): + types = pokemon.get_attribute(AttrID.POKEMON_TYPES) or [] + return PokemonTypes.FIGHTING.value in types + + +async def bea(ctx): + """Discard the top 5 of your deck; attach any Energy discarded this way + to your Benched Fighting Pokemon in any way you like.""" + cards = ctx.deck_top(5) + await ctx.discard_cards(cards) + energies = [c for c in cards if is_energy_card(c)] + if not energies: + return + candidates = [p for p in ctx.my_bench() if _is_fighting_pokemon(p)] + if not candidates: + return + await distribute_energy(ctx, energies, candidates) + card = SupporterCardDef( guid="e3f000c5-28b6-5581-b094-5c841b3cd836", @@ -11,5 +33,5 @@ card = SupporterCardDef( collector_number=147, set_code="SWSH4", rarity=Rarities.Uncommon, - effect=unimplemented + effect=bea ) diff --git a/spirit/game/scripts/cards/SWSH4/Bea_180.py b/spirit/game/scripts/cards/SWSH4/Bea_180.py index ac3ef77..79a38b0 100644 --- a/spirit/game/scripts/cards/SWSH4/Bea_180.py +++ b/spirit/game/scripts/cards/SWSH4/Bea_180.py @@ -1,5 +1,27 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import SupporterCardDef +from spirit.game.attributes import AttrID, PokemonTypes, Rarities +from spirit.game.card_effects.trainers import is_energy_card +from spirit.game.card_effects.support_common import distribute_energy + + +def _is_fighting_pokemon(pokemon): + types = pokemon.get_attribute(AttrID.POKEMON_TYPES) or [] + return PokemonTypes.FIGHTING.value in types + + +async def bea(ctx): + """Discard the top 5 of your deck; attach any Energy discarded this way + to your Benched Fighting Pokemon in any way you like.""" + cards = ctx.deck_top(5) + await ctx.discard_cards(cards) + energies = [c for c in cards if is_energy_card(c)] + if not energies: + return + candidates = [p for p in ctx.my_bench() if _is_fighting_pokemon(p)] + if not candidates: + return + await distribute_energy(ctx, energies, candidates) + card = SupporterCardDef( guid="fa7ac168-9c7f-5b33-b660-ee5e96d999e4", @@ -11,5 +33,5 @@ card = SupporterCardDef( collector_number=180, set_code="SWSH4", rarity=Rarities.RareUltra, - effect=unimplemented + effect=bea ) diff --git a/spirit/game/scripts/cards/SWSH4/Bea_193.py b/spirit/game/scripts/cards/SWSH4/Bea_193.py index c10ebbd..67211b2 100644 --- a/spirit/game/scripts/cards/SWSH4/Bea_193.py +++ b/spirit/game/scripts/cards/SWSH4/Bea_193.py @@ -1,5 +1,27 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import SupporterCardDef +from spirit.game.attributes import AttrID, PokemonTypes, Rarities +from spirit.game.card_effects.trainers import is_energy_card +from spirit.game.card_effects.support_common import distribute_energy + + +def _is_fighting_pokemon(pokemon): + types = pokemon.get_attribute(AttrID.POKEMON_TYPES) or [] + return PokemonTypes.FIGHTING.value in types + + +async def bea(ctx): + """Discard the top 5 of your deck; attach any Energy discarded this way + to your Benched Fighting Pokemon in any way you like.""" + cards = ctx.deck_top(5) + await ctx.discard_cards(cards) + energies = [c for c in cards if is_energy_card(c)] + if not energies: + return + candidates = [p for p in ctx.my_bench() if _is_fighting_pokemon(p)] + if not candidates: + return + await distribute_energy(ctx, energies, candidates) + card = SupporterCardDef( guid="e123642f-55b8-5972-9465-1f185fc772e7", @@ -11,5 +33,5 @@ card = SupporterCardDef( collector_number=193, set_code="SWSH4", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=bea ) diff --git a/spirit/game/scripts/cards/SWSH4/Beauty_148.py b/spirit/game/scripts/cards/SWSH4/Beauty_148.py index c346473..5135b6b 100644 --- a/spirit/game/scripts/cards/SWSH4/Beauty_148.py +++ b/spirit/game/scripts/cards/SWSH4/Beauty_148.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.support_common import draw_attack card = SupporterCardDef( guid="f67747e6-7f5b-505e-b431-ab13598e8f9e", @@ -11,5 +12,5 @@ card = SupporterCardDef( collector_number=148, set_code="SWSH4", rarity=Rarities.Uncommon, - effect=unimplemented + effect=draw_attack(2) ) diff --git a/spirit/game/scripts/cards/SWSH4/Beauty_181.py b/spirit/game/scripts/cards/SWSH4/Beauty_181.py index d956152..74b3570 100644 --- a/spirit/game/scripts/cards/SWSH4/Beauty_181.py +++ b/spirit/game/scripts/cards/SWSH4/Beauty_181.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.support_common import draw_attack card = SupporterCardDef( guid="6184b061-8f6c-54a8-a9ec-887513082f34", @@ -11,5 +12,5 @@ card = SupporterCardDef( collector_number=181, set_code="SWSH4", rarity=Rarities.RareUltra, - effect=unimplemented + effect=draw_attack(2) ) diff --git a/spirit/game/scripts/cards/SWSH4/Beauty_194.py b/spirit/game/scripts/cards/SWSH4/Beauty_194.py index b45a2f3..78f8f33 100644 --- a/spirit/game/scripts/cards/SWSH4/Beauty_194.py +++ b/spirit/game/scripts/cards/SWSH4/Beauty_194.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.support_common import draw_attack card = SupporterCardDef( guid="b2562d66-edd6-5670-8235-1451870b2d95", @@ -11,5 +12,5 @@ card = SupporterCardDef( collector_number=194, set_code="SWSH4", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=draw_attack(2) ) diff --git a/spirit/game/scripts/cards/SWSH4/Beedrill_3.py b/spirit/game/scripts/cards/SWSH4/Beedrill_3.py index 5fac6c5..4908da1 100644 --- a/spirit/game/scripts/cards/SWSH4/Beedrill_3.py +++ b/spirit/game/scripts/cards/SWSH4/Beedrill_3.py @@ -1,5 +1,25 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import requires_bench_space + +_bench_space = requires_bench_space(1) + + +def _elusive_master_condition(board, player_id, pokemon): + hand = board.find_player_area(player_id, "hand") + if not hand or len(hand.children) != 1: + return False + return _bench_space(board, player_id) + + +async def elusive_master(ctx): + """Once during your turn, if this is your last card in hand, you may + play it onto your Bench. If you do, draw 3 cards.""" + if not await ctx.ask_yes_no("Play this Pokémon onto your Bench and draw 3 cards?"): + return + if await ctx.bench_pokemon(ctx.source): + await ctx.draw_cards(3) + card = PokemonCardDef( guid="51485d58-0dc4-546d-a182-3ead43f6fce9", @@ -22,7 +42,10 @@ card = PokemonCardDef( Ability( title="Elusive Master", game_text="Once during your turn, if this Pok\u00e9mon is the last card in your hand, you may play it onto your Bench. If you do, draw 3 cards.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + usable_from="hand", + condition=_elusive_master_condition, + effect=elusive_master, ), Attack( title="Sharp Sting", diff --git a/spirit/game/scripts/cards/SWSH4/Beldum_116.py b/spirit/game/scripts/cards/SWSH4/Beldum_116.py index 36963a7..309b6b4 100644 --- a/spirit/game/scripts/cards/SWSH4/Beldum_116.py +++ b/spirit/game/scripts/cards/SWSH4/Beldum_116.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_bench card = PokemonCardDef( guid="119926a0-7a0d-577a-a71c-08d3ed232da1", @@ -23,7 +24,7 @@ card = PokemonCardDef( title="Call for Family", game_text="Search your deck for up to 2 Basic Pok\u00e9mon and put them onto your Bench. Then, shuffle your deck.", cost={PokemonTypes.METAL: 1}, - effect=unimplemented, + effect=search_to_bench(count=2), ), Attack( title="Ram", diff --git a/spirit/game/scripts/cards/SWSH4/CapeofToughness_200.py b/spirit/game/scripts/cards/SWSH4/CapeofToughness_200.py index 42943c0..4ace598 100644 --- a/spirit/game/scripts/cards/SWSH4/CapeofToughness_200.py +++ b/spirit/game/scripts/cards/SWSH4/CapeofToughness_200.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented +from spirit.game.data_utils import PokemonToolCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.passives_common import hp_bonus_tool +from spirit.game.card_effects.pokemon import is_pokemon_gx +from spirit.game.session.effects import is_basic_pokemon card = PokemonToolCardDef( guid="b724d66a-1853-5a9d-a9da-d3140f8557c1", @@ -11,5 +14,7 @@ card = PokemonToolCardDef( collector_number=200, set_code="SWSH4", rarity=Rarities.RareSecret, - effect=unimplemented + passive=hp_bonus_tool( + 50, holder_pred=lambda p: is_basic_pokemon(p) and not is_pokemon_gx(p.archetype_id) + ), ) diff --git a/spirit/game/scripts/cards/SWSH4/CaraLiss_149.py b/spirit/game/scripts/cards/SWSH4/CaraLiss_149.py index 6feeae1..98b0189 100644 --- a/spirit/game/scripts/cards/SWSH4/CaraLiss_149.py +++ b/spirit/game/scripts/cards/SWSH4/CaraLiss_149.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import bench_has_room, fossil_search, is_rare_fossil card = SupporterCardDef( guid="1713c760-1937-5afc-88bf-f07996103dd8", @@ -11,5 +12,6 @@ card = SupporterCardDef( collector_number=149, set_code="SWSH4", rarity=Rarities.Uncommon, - effect=unimplemented + effect=fossil_search(is_rare_fossil), + condition=bench_has_room, ) diff --git a/spirit/game/scripts/cards/SWSH4/Celebi_9.py b/spirit/game/scripts/cards/SWSH4/Celebi_9.py index 306e716..dbd7796 100644 --- a/spirit/game/scripts/cards/SWSH4/Celebi_9.py +++ b/spirit/game/scripts/cards/SWSH4/Celebi_9.py @@ -1,5 +1,25 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.attacks_common import damage_per, count_energy + + +async def _amazing_bloom(ctx): + bench = ctx.my_bench() + if not bench: + return + for pokemon in bench: + logic_name = pokemon.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) + if not logic_name: + continue + picks = await ctx.search_deck( + lambda c, name=logic_name: c.get_attribute(AttrID.EVOLUTION_LOGIC_FROM) == name, + count=1, minimum=0, + prompt="Choose a card that evolves from that Pokémon.", + ) + if picks: + await ctx.evolve_pokemon(pokemon, picks[0]) + await ctx.shuffle_deck() + card = PokemonCardDef( guid="36e1ec87-a2da-5747-8ca3-fb237186af7f", @@ -24,13 +44,13 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1}, damage=30, damage_operator="x", - effect=unimplemented, + effect=damage_per(count_energy("defender"), 30), ), Attack( title="Amazing Bloom", game_text="For each of your Benched Pok\u00e9mon, search your deck for a card that evolves from that Pok\u00e9mon and put it onto that Pok\u00e9mon to evolve it. Then, shuffle your deck.", cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=_amazing_bloom, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Charizard_25.py b/spirit/game/scripts/cards/SWSH4/Charizard_25.py index 488e39d..df30367 100644 --- a/spirit/game/scripts/cards/SWSH4/Charizard_25.py +++ b/spirit/game/scripts/cards/SWSH4/Charizard_25.py @@ -1,5 +1,28 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations, def_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_discard + + +def _is_leon(card): + definition = def_for(card.archetype_id) + return bool(definition) and getattr(definition, "display_name", "") == "Leon" + + +async def _battle_sense(ctx): + if not await ctx.ask_yes_no("Look at the top 3 cards of your deck?"): + return + top = ctx.deck_top(3) + if not top: + return + picks = await ctx.choose_cards( + top, 1, minimum=1, + prompt="Put 1 card into your hand. Discard the other cards.", + ) + await ctx.put_in_hand(picks, reveal=False) + rest = [c for c in top if c not in picks] + if rest: + await ctx.discard_cards(rest) + card = PokemonCardDef( guid="b5b1a323-c6ec-5e9b-8867-be373de5a922", @@ -22,7 +45,8 @@ card = PokemonCardDef( Ability( title="Battle Sense", game_text="Once during your turn, you may look at the top 3 cards of your deck and put 1 of them into your hand. Discard the other cards.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + effect=_battle_sense, ), Attack( title="Royal Blaze", @@ -30,7 +54,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 2}, damage=100, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_discard("mine", pred=_is_leon), 50, base=100), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Charmander_23.py b/spirit/game/scripts/cards/SWSH4/Charmander_23.py index 83622cc..3518470 100644 --- a/spirit/game/scripts/cards/SWSH4/Charmander_23.py +++ b/spirit/game/scripts/cards/SWSH4/Charmander_23.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import draw_attack card = PokemonCardDef( guid="28c6c478-c877-5135-bfde-38681bd099ae", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Collect", game_text="Draw a card.", cost={PokemonTypes.FIRE: 1}, - effect=unimplemented, + effect=draw_attack(1), ), Attack( title="Flare", diff --git a/spirit/game/scripts/cards/SWSH4/Charmeleon_24.py b/spirit/game/scripts/cards/SWSH4/Charmeleon_24.py index 540dcb7..d6a012a 100644 --- a/spirit/game/scripts/cards/SWSH4/Charmeleon_24.py +++ b/spirit/game/scripts/cards/SWSH4/Charmeleon_24.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import mill_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -29,7 +30,7 @@ card = PokemonCardDef( game_text="Discard the top 3 cards of your deck.", cost={PokemonTypes.FIRE: 2}, damage=60, - effect=unimplemented, + effect=mill_attack(3, opponent=False), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Chatot_139.py b/spirit/game/scripts/cards/SWSH4/Chatot_139.py index c3c537b..e4b2321 100644 --- a/spirit/game/scripts/cards/SWSH4/Chatot_139.py +++ b/spirit/game/scripts/cards/SWSH4/Chatot_139.py @@ -1,4 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -23,7 +25,10 @@ card = PokemonCardDef( title="Minor Errand-Running", game_text="Search your deck for up to 2 basic Energy cards, reveal them, and put them into your hand. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_to_hand( + is_basic_energy_card, count=2, reveal=True, + prompt="Choose up to 2 basic Energy cards.", + ), ), Attack( title="Peck", diff --git a/spirit/game/scripts/cards/SWSH4/Chewtle_38.py b/spirit/game/scripts/cards/SWSH4/Chewtle_38.py index cf7abc0..94ec5c7 100644 --- a/spirit/game/scripts/cards/SWSH4/Chewtle_38.py +++ b/spirit/game/scripts/cards/SWSH4/Chewtle_38.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="3099d0be-88fe-5beb-bcb8-fa8ef0ed401c", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, the Defending Pok\u00e9mon can't retreat.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 1}, damage=40, - effect=unimplemented, + effect=condition_attack(no_retreat=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Chimecho_72.py b/spirit/game/scripts/cards/SWSH4/Chimecho_72.py index ecb6562..d8c7574 100644 --- a/spirit/game/scripts/cards/SWSH4/Chimecho_72.py +++ b/spirit/game/scripts/cards/SWSH4/Chimecho_72.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.session.effects import is_pokemon_card, is_supporter_card + + +async def auspicious_tone(ctx): + """Search your deck for a Pokémon and a Supporter card, reveal them, and + put them into your hand. Then, shuffle your deck.""" + pokemon, supporters = await ctx.search_deck_groups( + [ + (is_pokemon_card, 1, "Pokémon"), + (is_supporter_card, 1, "Supporter card"), + ], + prompt="Choose a Pokémon and a Supporter card", + ) + await ctx.put_in_hand(pokemon + supporters, reveal=True) + await ctx.shuffle_deck() + card = PokemonCardDef( guid="d63e3e93-7b34-58e7-bb0d-584e7881382e", @@ -23,14 +40,14 @@ card = PokemonCardDef( title="Auspicious Tone", game_text="Search your deck for a Pok\u00e9mon and a Supporter card, reveal them, and put them into your hand. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=auspicious_tone, ), Attack( title="Hypnoblast", game_text="Your opponent's Active Pok\u00e9mon is now Asleep.", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=condition_attack(SpecialConditions.ASLEEP), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/CirchesterBath_150.py b/spirit/game/scripts/cards/SWSH4/CirchesterBath_150.py index caf6432..aaf38ae 100644 --- a/spirit/game/scripts/cards/SWSH4/CirchesterBath_150.py +++ b/spirit/game/scripts/cards/SWSH4/CirchesterBath_150.py @@ -1,7 +1,10 @@ -from spirit.game.data_utils import StadiumCardDef, unimplemented +from spirit.game.data_utils import StadiumCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.passives_common import takes_less_passive +from spirit.game.session.effects import is_basic_pokemon card = StadiumCardDef( + passive=takes_less_passive(20, protects=lambda target, carrier: is_basic_pokemon(target)), guid="289a9041-9356-56a0-9fbe-d229afa1462f", key="SWSH4", name="com.direwolfdigital.cake.data.archetypes.trainer.CirchesterBath.Name", @@ -11,5 +14,4 @@ card = StadiumCardDef( collector_number=150, set_code="SWSH4", rarity=Rarities.Uncommon, - effect=unimplemented ) diff --git a/spirit/game/scripts/cards/SWSH4/Clefable_64.py b/spirit/game/scripts/cards/SWSH4/Clefable_64.py index 8967302..2eabf2f 100644 --- a/spirit/game/scripts/cards/SWSH4/Clefable_64.py +++ b/spirit/game/scripts/cards/SWSH4/Clefable_64.py @@ -1,5 +1,29 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import energy_provides_type + + +def lunar_blessing_condition(board, player_id, pokemon): + active = board.active_pokemon(player_id) + if active is None: + return False + return any(energy_provides_type(e, PokemonTypes.PSYCHIC.value) + for e in board.attached_energies(active)) + + +async def lunar_blessing(ctx): + """Once during your turn, if your Active has Psychic Energy attached, + you may heal 20 damage from it and have it recover from a Special Condition.""" + active = ctx.my_active() + if active is None: + return + if not await ctx.ask_yes_no( + "Heal 20 damage from your Active Pokémon and have it recover from a Special Condition?" + ): + return + await ctx.heal(20, active) + await ctx.cure_all_conditions(active) + card = PokemonCardDef( guid="e5976ae1-72b6-5c7e-8f2e-d3c99f7a5d65", @@ -22,7 +46,9 @@ card = PokemonCardDef( Ability( title="Lunar Blessing", game_text="Once during your turn, if your Active Pok\u00e9mon has any Psychic Energy attached, you may heal 20 damage from it, and it recovers from a Special Condition.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=lunar_blessing_condition, + effect=lunar_blessing, ), Attack( title="Magical Shot", diff --git a/spirit/game/scripts/cards/SWSH4/Clefairy_63.py b/spirit/game/scripts/cards/SWSH4/Clefairy_63.py index 49ee215..6923cb7 100644 --- a/spirit/game/scripts/cards/SWSH4/Clefairy_63.py +++ b/spirit/game/scripts/cards/SWSH4/Clefairy_63.py @@ -1,5 +1,33 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, def_for +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities + + +async def mini_metronome(ctx): + """Flip a coin. If heads, choose 1 of your opponent's Active Pokemon's attacks and use it as this attack.""" + heads = await ctx.flip_coins(1, ctx.ability.title) + if not heads[0]: + return + defender = ctx.opponent_active() + if defender is None: + return + definition = def_for(defender.archetype_id) + candidates = [ + (defender, ability) for ability in getattr(definition, "abilities", []) + if isinstance(ability, Attack) + ] + if not candidates: + return + picked = await ctx.choose_attack_to_copy(candidates, "Choose an attack to copy") + if picked is None: + return + _, chosen = picked + if not await ctx.use_attack(chosen): + return + if getattr(chosen, "locks_next_turn", False): + for entry in ctx.attacker.get_attribute(AttrID.PIE_ABILITIES) or []: + if isinstance(entry, dict) and entry.get("abilityType") == "Attack": + ctx.session.turn_state.lock_attack(ctx.attacker.entity_id, entry["abilityID"]) + card = PokemonCardDef( guid="6ecad46c-12c9-5c16-adfc-5fa09893f0b6", @@ -27,7 +55,7 @@ card = PokemonCardDef( title="Mini-Metronome", game_text="Flip a coin. If heads, choose 1 of your opponent's Active Pok\u00e9mon's attacks and use it as this attack.", cost={PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + effect=mini_metronome, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/CoalossalVMAX_189.py b/spirit/game/scripts/cards/SWSH4/CoalossalVMAX_189.py index db60778..9587eda 100644 --- a/spirit/game/scripts/cards/SWSH4/CoalossalVMAX_189.py +++ b/spirit/game/scripts/cards/SWSH4/CoalossalVMAX_189.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import is_energy_card + + +async def eruption_shot(ctx): + top = ctx.deck_top(1) + card = top[0] if top else None + if card is not None and is_energy_card(card): + await ctx.attach_energy(card, ctx.attacker) + await ctx.deal_damage(130) + return + if card is not None: + await ctx.discard_cards([card]) + await ctx.deal_damage() + card = PokemonCardDef( guid="d2de967b-8d0f-5712-974c-36c138194fb2", @@ -25,7 +39,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1}, damage=40, damage_operator="+", - effect=unimplemented, + effect=eruption_shot, ), Attack( title="G-Max Boulder", diff --git a/spirit/game/scripts/cards/SWSH4/CoalossalVMAX_99.py b/spirit/game/scripts/cards/SWSH4/CoalossalVMAX_99.py index d14be81..4fbf032 100644 --- a/spirit/game/scripts/cards/SWSH4/CoalossalVMAX_99.py +++ b/spirit/game/scripts/cards/SWSH4/CoalossalVMAX_99.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import is_energy_card + + +async def eruption_shot(ctx): + top = ctx.deck_top(1) + card = top[0] if top else None + if card is not None and is_energy_card(card): + await ctx.attach_energy(card, ctx.attacker) + await ctx.deal_damage(130) + return + if card is not None: + await ctx.discard_cards([card]) + await ctx.deal_damage() + card = PokemonCardDef( guid="5a593a19-bc43-5a7e-a1e7-1d243b7a7a40", @@ -25,7 +39,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1}, damage=40, damage_operator="+", - effect=unimplemented, + effect=eruption_shot, ), Attack( title="G-Max Boulder", diff --git a/spirit/game/scripts/cards/SWSH4/CoalossalV_173.py b/spirit/game/scripts/cards/SWSH4/CoalossalV_173.py index f57d5b4..d916a2a 100644 --- a/spirit/game/scripts/cards/SWSH4/CoalossalV_173.py +++ b/spirit/game/scripts/cards/SWSH4/CoalossalV_173.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="b1988f57-0828-5c4f-801f-1b5e703918e9", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Burned.", cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=90, - effect=unimplemented, + effect=condition_attack(SpecialConditions.BURNED), ), Attack( title="Boulder Crush", diff --git a/spirit/game/scripts/cards/SWSH4/CoalossalV_98.py b/spirit/game/scripts/cards/SWSH4/CoalossalV_98.py index 5720b48..c15a231 100644 --- a/spirit/game/scripts/cards/SWSH4/CoalossalV_98.py +++ b/spirit/game/scripts/cards/SWSH4/CoalossalV_98.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="48c3cadc-785c-57a6-a17b-3d33b36f512f", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Burned.", cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=90, - effect=unimplemented, + effect=condition_attack(SpecialConditions.BURNED), ), Attack( title="Boulder Crush", diff --git a/spirit/game/scripts/cards/SWSH4/Cramorant_40.py b/spirit/game/scripts/cards/SWSH4/Cramorant_40.py index 2772f78..8c37154 100644 --- a/spirit/game/scripts/cards/SWSH4/Cramorant_40.py +++ b/spirit/game/scripts/cards/SWSH4/Cramorant_40.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.session.effects import full_stack + + +async def continuous_gulp_missile(ctx): + """Discard any number of Benched Arrokuda; 60 damage for each discarded.""" + arrokuda = [p for p in ctx.my_bench() + if p.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) == "Arrokuda"] + picks = [] + if arrokuda: + picks = await ctx.choose_cards( + arrokuda, len(arrokuda), minimum=0, + prompt="Choose Arrokuda to discard from your Bench", + ) + for pokemon in picks: + await ctx.discard_cards(full_stack(pokemon)) + await ctx.deal_damage(60 * len(picks)) + card = PokemonCardDef( guid="7752b7b3-69e9-52ab-932f-c48d09aedd76", @@ -25,7 +42,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=60, damage_operator="x", - effect=unimplemented, + effect=continuous_gulp_missile, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH4/Dedenne_77.py b/spirit/game/scripts/cards/SWSH4/Dedenne_77.py index a0d000d..367c3c9 100644 --- a/spirit/game/scripts/cards/SWSH4/Dedenne_77.py +++ b/spirit/game/scripts/cards/SWSH4/Dedenne_77.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="677f304e-26e9-5e8a-a054-f25962a7b0e0", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Confused.", cost={PokemonTypes.PSYCHIC: 1}, damage=10, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Dewott_34.py b/spirit/game/scripts/cards/SWSH4/Dewott_34.py index 6ad81ae..fb8d2d0 100644 --- a/spirit/game/scripts/cards/SWSH4/Dewott_34.py +++ b/spirit/game/scripts/cards/SWSH4/Dewott_34.py @@ -1,6 +1,26 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def aqua_wash(ctx): + await ctx.deal_damage() + defender = ctx.defender + if defender is None or ctx.effects_blocked(defender): + return + energies = ctx.attached_energies(defender) + if not energies: + return + if not await ctx.ask_yes_no( + "Put an Energy attached to your opponent's Active Pokémon into their hand?" + ): + return + picked = await ctx.choose_cards( + energies, 1, minimum=1, + prompt="Choose an Energy card to put into your opponent's hand", + ) + await ctx.put_in_hand(picked, reveal=False) + + card = PokemonCardDef( guid="37a6adec-00e3-5d14-87fa-6231f7bb4477", key="SWSH4", @@ -29,7 +49,7 @@ card = PokemonCardDef( game_text="You may put an Energy attached to your opponent's Active Pok\u00e9mon into their hand.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=50, - effect=unimplemented, + effect=aqua_wash, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Dhelmise_19.py b/spirit/game/scripts/cards/SWSH4/Dhelmise_19.py index b3ab510..96790cd 100644 --- a/spirit/game/scripts/cards/SWSH4/Dhelmise_19.py +++ b/spirit/game/scripts/cards/SWSH4/Dhelmise_19.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if +from spirit.game.session.effects import is_special_energy + + +def _has_special_energy(ctx): + return any(is_special_energy(c) for c in ctx.attached_energies(ctx.attacker)) + card = PokemonCardDef( guid="6245e6f0-7380-5b1b-9cad-ad19b4858b75", @@ -29,7 +36,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 2}, damage=60, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_has_special_energy, 60), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Dialga_121.py b/spirit/game/scripts/cards/SWSH4/Dialga_121.py index 96b0752..445caac 100644 --- a/spirit/game/scripts/cards/SWSH4/Dialga_121.py +++ b/spirit/game/scripts/cards/SWSH4/Dialga_121.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import attach_from_discard +from spirit.game.card_effects.trainers import is_metal_energy_card +from spirit.game.card_effects.attacks_common import self_energy_discard_attack card = PokemonCardDef( guid="da2623da-5a2d-59bd-a04a-0350509f6da7", @@ -21,16 +24,19 @@ card = PokemonCardDef( abilities=[ Attack( title="Rewind Time", - game_text="Attach up to 2 Metal Energy cards from your discard pile to 1 of your Pok\u00e9mon.", + game_text="Attach up to 2 Metal Energy cards from your discard pile to 1 of your Pokémon.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=attach_from_discard( + predicate=is_metal_energy_card, count=2, minimum=0, target="choice", + prompt="Choose up to 2 Metal Energy cards to attach", + ), ), Attack( title="Flash of Destruction", - game_text="Discard 2 Energy from this Pok\u00e9mon.", + game_text="Discard 2 Energy from this Pokémon.", cost={PokemonTypes.METAL: 2, PokemonTypes.COLORLESS: 1}, damage=130, - effect=unimplemented, + effect=self_energy_discard_attack(count=2), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH4/Diancie_79.py b/spirit/game/scripts/cards/SWSH4/Diancie_79.py index 1460b8a..7abeaba 100644 --- a/spirit/game/scripts/cards/SWSH4/Diancie_79.py +++ b/spirit/game/scripts/cards/SWSH4/Diancie_79.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if +from spirit.game.card_effects.passives_common import takes_less_passive, is_in_active_spot + + +def _sparkle_veil_protects(target, carrier): + return target.owning_player_id == carrier.owning_player_id and is_in_active_spot(carrier) + + +def _supporter_played_this_turn(ctx): + return ctx.supporters_played_this_turn() > 0 + card = PokemonCardDef( guid="a89e5e42-398b-5c49-ba16-a24d79af8079", @@ -21,7 +32,7 @@ card = PokemonCardDef( Ability( title="Sparkle Veil", game_text="As long as this Pok\u00e9mon is your Active Pok\u00e9mon, any damage done to your Pok\u00e9mon by an opponent's attack is reduced by 30 (after applying Weakness and Resistance).", - effect=unimplemented, + passive=takes_less_passive(30, protects=_sparkle_veil_protects), ), Attack( title="Sensitive Ray", @@ -29,7 +40,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 2}, damage=50, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_supporter_played_this_turn, 70), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Donphan_87.py b/spirit/game/scripts/cards/SWSH4/Donphan_87.py index dc5ed10..4f33e9f 100644 --- a/spirit/game/scripts/cards/SWSH4/Donphan_87.py +++ b/spirit/game/scripts/cards/SWSH4/Donphan_87.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import spread_damage card = PokemonCardDef( guid="8fb72856-75f4-5a91-b22c-3758422a022d", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="This attack also does 20 damage to each of your Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.FIGHTING: 1}, damage=120, - effect=unimplemented, + effect=spread_damage(20, side="mine", also_base=True), ), Attack( title="Heavy Impact", diff --git a/spirit/game/scripts/cards/SWSH4/DrapionV_106.py b/spirit/game/scripts/cards/SWSH4/DrapionV_106.py index d3ecab0..f5fe903 100644 --- a/spirit/game/scripts/cards/SWSH4/DrapionV_106.py +++ b/spirit/game/scripts/cards/SWSH4/DrapionV_106.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions + + +async def hazardous_claws(ctx): + await ctx.discard_energy_from(ctx.attacker, 2) + await ctx.deal_damage() + await ctx.apply_special_condition(ctx.defender, SpecialConditions.PARALYZED) + await ctx.apply_special_condition(ctx.defender, SpecialConditions.POISONED) + card = PokemonCardDef( guid="a3c8239a-9cab-5b89-8e23-57b72bd6ccfc", @@ -28,7 +36,7 @@ card = PokemonCardDef( game_text="Discard 2 Energy from this Pok\u00e9mon. Your opponent's Active Pok\u00e9mon is now Paralyzed and Poisoned.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 3}, damage=130, - effect=unimplemented, + effect=hazardous_claws, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/DrapionV_175.py b/spirit/game/scripts/cards/SWSH4/DrapionV_175.py index ef4ff9e..476bebc 100644 --- a/spirit/game/scripts/cards/SWSH4/DrapionV_175.py +++ b/spirit/game/scripts/cards/SWSH4/DrapionV_175.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions + + +async def hazardous_claws(ctx): + await ctx.discard_energy_from(ctx.attacker, 2) + await ctx.deal_damage() + await ctx.apply_special_condition(ctx.defender, SpecialConditions.PARALYZED) + await ctx.apply_special_condition(ctx.defender, SpecialConditions.POISONED) + card = PokemonCardDef( guid="6280135f-4c64-50c3-928b-298210e5fc62", @@ -28,7 +36,7 @@ card = PokemonCardDef( game_text="Discard 2 Energy from this Pok\u00e9mon. Your opponent's Active Pok\u00e9mon is now Paralyzed and Poisoned.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 3}, damage=130, - effect=unimplemented, + effect=hazardous_claws, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Drednaw_39.py b/spirit/game/scripts/cards/SWSH4/Drednaw_39.py index 14f1ff7..a2f7b87 100644 --- a/spirit/game/scripts/cards/SWSH4/Drednaw_39.py +++ b/spirit/game/scripts/cards/SWSH4/Drednaw_39.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions + + +async def vise_wave(ctx): + await ctx.deal_damage() + if ctx.played_trainer_this_turn("Nessa") > 0: + await ctx.apply_special_condition(ctx.defender, SpecialConditions.PARALYZED) + card = PokemonCardDef( guid="8211a077-8f07-5888-bb8a-b48536041281", @@ -24,7 +31,7 @@ card = PokemonCardDef( game_text="If you played Nessa from your hand during this turn, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=80, - effect=unimplemented, + effect=vise_wave, ), Attack( title="Surf", diff --git a/spirit/game/scripts/cards/SWSH4/Drilbur_91.py b/spirit/game/scripts/cards/SWSH4/Drilbur_91.py index 6888d47..2f588a2 100644 --- a/spirit/game/scripts/cards/SWSH4/Drilbur_91.py +++ b/spirit/game/scripts/cards/SWSH4/Drilbur_91.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_or_nothing card = PokemonCardDef( guid="880b66c1-9645-5169-a2d0-4758aff920a7", @@ -28,7 +29,7 @@ card = PokemonCardDef( game_text="Flip a coin. If tails, this attack does nothing.", cost={PokemonTypes.COLORLESS: 2}, damage=50, - effect=unimplemented, + effect=flip_or_nothing(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/DroneRotom_151.py b/spirit/game/scripts/cards/SWSH4/DroneRotom_151.py index 7fafefa..e6437f9 100644 --- a/spirit/game/scripts/cards/SWSH4/DroneRotom_151.py +++ b/spirit/game/scripts/cards/SWSH4/DroneRotom_151.py @@ -1,6 +1,17 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef from spirit.game.attributes import Rarities + +async def drone_rotom(ctx): + """Opponent reveals their hand. Then look at the top card of their deck.""" + await ctx.reveal_hand(of_player=ctx.opponent_id) + top = ctx.deck_top(1, player_id=ctx.opponent_id) + if not top: + return + await ctx.present_card_choice( + top[0], "Top card of your opponent's deck", ["OK"]) + + card = ItemCardDef( guid="67bc7ac0-8aff-5e1c-a677-d7cb85bc72e9", key="SWSH4", @@ -11,5 +22,5 @@ card = ItemCardDef( collector_number=151, set_code="SWSH4", rarity=Rarities.Uncommon, - effect=unimplemented + effect=drone_rotom, ) diff --git a/spirit/game/scripts/cards/SWSH4/Duraludon_129.py b/spirit/game/scripts/cards/SWSH4/Duraludon_129.py index 4240ccf..e1a2e81 100644 --- a/spirit/game/scripts/cards/SWSH4/Duraludon_129.py +++ b/spirit/game/scripts/cards/SWSH4/Duraludon_129.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, damage_counters_on, \ + self_energy_discard_attack card = PokemonCardDef( guid="28e21c44-68bc-5109-9a3f-94811a9cf560", @@ -25,14 +27,14 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=20, damage_operator="+", - effect=unimplemented, + effect=damage_per(damage_counters_on("self"), 10, base=20), ), Attack( title="Power Blast", game_text="Discard an Energy from this Pok\u00e9mon.", cost={PokemonTypes.METAL: 2, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + effect=self_energy_discard_attack(count=1, before_damage=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Dusclops_70.py b/spirit/game/scripts/cards/SWSH4/Dusclops_70.py index b54fb9c..2bb9cc9 100644 --- a/spirit/game/scripts/cards/SWSH4/Dusclops_70.py +++ b/spirit/game/scripts/cards/SWSH4/Dusclops_70.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="0440dcba-be1f-5fef-b2f9-1bf6af765d09", @@ -24,7 +25,7 @@ card = PokemonCardDef( title="Confuse Ray", game_text="Your opponent's Active Pok\u00e9mon is now Confused.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED), ), Attack( title="Psypunch", diff --git a/spirit/game/scripts/cards/SWSH4/Dusknoir_71.py b/spirit/game/scripts/cards/SWSH4/Dusknoir_71.py index a2f7242..02ca3f3 100644 --- a/spirit/game/scripts/cards/SWSH4/Dusknoir_71.py +++ b/spirit/game/scripts/cards/SWSH4/Dusknoir_71.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.passives import Passive + + +class SpectralBreachPassive(Passive): + """All Special Energy (both players') provide only Colorless Energy.""" + + def suppresses_special_energy(self, energy, carrier) -> bool: + return True + card = PokemonCardDef( guid="2547c701-72a5-5fd2-9cee-3552a1f4da1c", @@ -22,8 +31,8 @@ card = PokemonCardDef( abilities=[ Ability( title="Spectral Breach", - game_text="All Special Energy attached to Pok\u00e9mon (both yours and your opponent's) provide Colorless Energy and have no other effect.", - effect=unimplemented, + game_text="All Special Energy attached to Pokémon (both yours and your opponent's) provide Colorless Energy and have no other effect.", + passive=SpectralBreachPassive(), ), Attack( title="Spooky Shot", @@ -31,4 +40,4 @@ card = PokemonCardDef( damage=120, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH4/Duskull_69.py b/spirit/game/scripts/cards/SWSH4/Duskull_69.py index 7963b39..9875628 100644 --- a/spirit/game/scripts/cards/SWSH4/Duskull_69.py +++ b/spirit/game/scripts/cards/SWSH4/Duskull_69.py @@ -1,6 +1,31 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def future_sight(ctx): + """Look at the top 4 cards of either player's deck, put back in any order.""" + choice = await ctx.choose( + "Look at the top 4 cards of which deck?", + ["Your deck", "Opponent's deck"], use_panel=False, + ) + target_pid = ctx.player_id if choice == 0 else ctx.opponent_id + top = ctx.deck_top(4, target_pid) + if len(top) <= 1: + return + order = await ctx.choose_cards( + top, len(top), minimum=len(top), ordered=True, player_id=ctx.player_id, + prompt="Put the cards back in any order", + ) + if not order: + order = top + deck = ctx.board.find_player_area(target_pid, "deck") + for card in order: + if card in deck.children: + deck.children.remove(card) + for card in reversed(order): + deck.children.append(card) + + card = PokemonCardDef( guid="3e179b37-39e9-5932-8757-b756be4eba0a", key="SWSH4", @@ -23,7 +48,7 @@ card = PokemonCardDef( title="Future Sight", game_text="Look at the top 4 cards of either player's deck and put them back in any order.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=future_sight, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Eelektrik_58.py b/spirit/game/scripts/cards/SWSH4/Eelektrik_58.py index 8c5c101..755c447 100644 --- a/spirit/game/scripts/cards/SWSH4/Eelektrik_58.py +++ b/spirit/game/scripts/cards/SWSH4/Eelektrik_58.py @@ -1,6 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def shocking_smash(ctx): + """Flip a coin. If heads, discard an Energy from 1 of the opponent's Pokémon.""" + results = await ctx.flip_coins(1, "Shocking Smash") + if not results or not results[0]: + return + candidates = [p for p in ctx.opponent_pokemon_in_play() + if not ctx.effects_blocked(p) and ctx.attached_energies(p)] + if not candidates: + return + target = await ctx.choose_pokemon( + candidates, "Choose 1 of your opponent's Pokémon to discard an Energy from") + if target is not None: + await ctx.discard_energy_from(target, 1) + card = PokemonCardDef( guid="aebb42ba-5d2b-5752-aebd-791d8d2a0308", key="SWSH4", @@ -23,7 +38,7 @@ card = PokemonCardDef( title="Shocking Smash", game_text="Flip a coin. If heads, discard an Energy from 1 of your opponent's Pok\u00e9mon.", cost={PokemonTypes.LIGHTNING: 1}, - effect=unimplemented, + effect=shocking_smash, ), Attack( title="Head Bolt", diff --git a/spirit/game/scripts/cards/SWSH4/Eelektross_59.py b/spirit/game/scripts/cards/SWSH4/Eelektross_59.py index 4ab9075..370d02e 100644 --- a/spirit/game/scripts/cards/SWSH4/Eelektross_59.py +++ b/spirit/game/scripts/cards/SWSH4/Eelektross_59.py @@ -1,6 +1,41 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def electrified_bite_mark(ctx): + """60 damage.""" + await ctx.deal_damage() + + +async def electrified_bite_mark_watch(ctx): + """During opponent's next turn, if they attach an Energy card from + hand to the Defending Pokémon, put 6 damage counters on it.""" + if not ctx.attack_used_last_turn(title="Electrified Bite Mark", entity=ctx.source): + return + if ctx.attaching_player_id != ctx.opponent_id: + return + receiver = ctx.energy_receiver + if receiver is None or receiver is not ctx.opponent_active(): + return + await ctx.deal_damage(60, target=receiver, apply_modifiers=False, as_counters=True) + + +async def electro_sprinkler(ctx): + await ctx.deal_damage() + my_bench = ctx.my_bench() + if my_bench: + target = await ctx.choose_pokemon( + my_bench, "Choose 1 of your Benched Pokémon") + if target is not None: + await ctx.deal_damage(30, target=target, apply_modifiers=False) + opp_bench = ctx.opponent_bench() + if opp_bench: + target2 = await ctx.choose_pokemon( + opp_bench, "Choose 1 of your opponent's Benched Pokémon") + if target2 is not None: + await ctx.deal_damage(30, target=target2, apply_modifiers=False) + + card = PokemonCardDef( guid="685ebc6c-a630-52be-85f4-698bb2b2960b", key="SWSH4", @@ -21,17 +56,23 @@ card = PokemonCardDef( abilities=[ Attack( title="Electrified Bite Mark", - game_text="During your opponent's next turn, if they attach an Energy card from their hand to the Defending Pok\u00e9mon, put 6 damage counters on that Pok\u00e9mon.", + game_text="During your opponent's next turn, if they attach an Energy card from their hand to the Defending Pokémon, put 6 damage counters on that Pokémon.", cost={PokemonTypes.LIGHTNING: 1}, damage=60, - effect=unimplemented, + effect=electrified_bite_mark, + ), + Ability( + title="Electrified Bite Mark", + game_text="During your opponent's next turn, if they attach an Energy card from their hand to the Defending Pokémon, put 6 damage counters on that Pokémon.", + trigger=Triggers.ON_ENERGY_ATTACHED, + effect=electrified_bite_mark_watch, ), Attack( title="Electro Sprinkler", - game_text="This attack also does 30 damage to 1 of your Benched Pok\u00e9mon and 30 damage to 1 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", + game_text="This attack also does 30 damage to 1 of your Benched Pokémon and 30 damage to 1 of your opponent's Benched Pokémon. (Don't apply Weakness and Resistance for Benched Pokémon.)", cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 2}, damage=120, - effect=unimplemented, + effect=electro_sprinkler, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH4/Electrike_51.py b/spirit/game/scripts/cards/SWSH4/Electrike_51.py index fbccb00..6d85257 100644 --- a/spirit/game/scripts/cards/SWSH4/Electrike_51.py +++ b/spirit/game/scripts/cards/SWSH4/Electrike_51.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import draw_attack card = PokemonCardDef( guid="09ea778a-ae93-54d6-b25a-df1c9ee55200", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Collect", game_text="Draw a card.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=draw_attack(1), ), Attack( title="Bite", diff --git a/spirit/game/scripts/cards/SWSH4/Electrode_46.py b/spirit/game/scripts/cards/SWSH4/Electrode_46.py index cb1d2ea..db9fbcf 100644 --- a/spirit/game/scripts/cards/SWSH4/Electrode_46.py +++ b/spirit/game/scripts/cards/SWSH4/Electrode_46.py @@ -1,5 +1,35 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import is_lightning_energy +from spirit.game.card_effects.support_common import distribute_energy + + +def _is_lightning_pokemon(p): + return PokemonTypes.LIGHTNING.value in (p.get_attribute(AttrID.POKEMON_TYPES) or []) + + +def _on_bench(board, player_id, pokemon): + return pokemon is not board.active_pokemon(player_id) + + +async def buzzap_generator(ctx): + """Once per turn, if benched: you may search up to 2 Lightning Energy + cards and attach them freely, then shuffle. If you did, this Pokémon is + Knocked Out.""" + if not await ctx.ask_yes_no( + "Search your deck for up to 2 Lightning Energy cards to attach? " + "This Pokémon will be Knocked Out." + ): + return + picks = await ctx.search_deck( + is_lightning_energy, count=2, minimum=0, + prompt="Choose up to 2 Lightning Energy cards to attach.", + ) + candidates = [p for p in ctx.my_pokemon_in_play() if _is_lightning_pokemon(p)] + if picks and candidates: + await distribute_energy(ctx, picks, candidates) + await ctx.shuffle_deck() + await ctx.knock_out(ctx.source) card = PokemonCardDef( guid="a4d2a52e-1f63-5225-8a8d-90393421ff2f", @@ -22,7 +52,9 @@ card = PokemonCardDef( Ability( title="Buzzap Generator", game_text="Once during your turn, if this Pok\u00e9mon is on your Bench, you may search your deck for up to 2 Lightning Energy cards and attach them to your Lightning Pok\u00e9mon in any way you like. Then, shuffle your deck. If you searched your deck in this way, this Pok\u00e9mon is Knocked Out.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=_on_bench, + effect=buzzap_generator, ), Attack( title="Electric Ball", diff --git a/spirit/game/scripts/cards/SWSH4/Excadrill_122.py b/spirit/game/scripts/cards/SWSH4/Excadrill_122.py index 7b09164..c9d1578 100644 --- a/spirit/game/scripts/cards/SWSH4/Excadrill_122.py +++ b/spirit/game/scripts/cards/SWSH4/Excadrill_122.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import discard_opponent_energy_attack card = PokemonCardDef( guid="20d5cf49-039f-516e-9e96-118de79077ac", @@ -25,7 +26,7 @@ card = PokemonCardDef( game_text="Discard an Energy from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=discard_opponent_energy_attack(), ), Attack( title="Slashing Claw", diff --git a/spirit/game/scripts/cards/SWSH4/Exeggutor_5.py b/spirit/game/scripts/cards/SWSH4/Exeggutor_5.py index 2d142bb..e8a5299 100644 --- a/spirit/game/scripts/cards/SWSH4/Exeggutor_5.py +++ b/spirit/game/scripts/cards/SWSH4/Exeggutor_5.py @@ -1,5 +1,25 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities + + +async def head_crack(ctx): + """Choose 1 of the opponent's Active Pokemon's attacks; it can't be used + during their next turn.""" + await ctx.deal_damage() + defender = ctx.defender + if defender is None or ctx.effects_blocked(defender): + return + entries = [e for e in (defender.get_attribute(AttrID.PIE_ABILITIES) or []) + if isinstance(e, dict) and e.get("abilityType") == "Attack" + and e.get("abilityID")] + if not entries: + return + titles = [e.get("title", {}).get("id", "Attack") for e in entries] + idx = await ctx.choose("Choose an attack to lock", titles) + entry = entries[idx] + ctx.session.turn_state.attack_locks[(defender.entity_id, entry["abilityID"])] = \ + ctx.session.turn_state.turn_number + 1 + card = PokemonCardDef( guid="143bdca5-77c6-5544-a0b5-bb646905f463", @@ -24,7 +44,7 @@ card = PokemonCardDef( game_text="Choose 1 of your opponent's Active Pok\u00e9mon's attacks. During your opponent's next turn, that Pok\u00e9mon can't use that attack.", cost={PokemonTypes.GRASS: 1}, damage=30, - effect=unimplemented, + effect=head_crack, ), Attack( title="Solar Beam", diff --git a/spirit/game/scripts/cards/SWSH4/Exploud_137.py b/spirit/game/scripts/cards/SWSH4/Exploud_137.py index 51070f2..f9bb89e 100644 --- a/spirit/game/scripts/cards/SWSH4/Exploud_137.py +++ b/spirit/game/scripts/cards/SWSH4/Exploud_137.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import damage_per, count_in_play, has_attack_titled +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -25,7 +26,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=50, damage_operator="x", - effect=unimplemented, + effect=damage_per(count_in_play("mine", has_attack_titled("Round")), 50), ), Attack( title="Hyper Voice", diff --git a/spirit/game/scripts/cards/SWSH4/Ferroseed_123.py b/spirit/game/scripts/cards/SWSH4/Ferroseed_123.py index ba79212..5f8ebf4 100644 --- a/spirit/game/scripts/cards/SWSH4/Ferroseed_123.py +++ b/spirit/game/scripts/cards/SWSH4/Ferroseed_123.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.support_common import switch_self_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Switch this Pok\u00e9mon with 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.METAL: 1}, damage=10, - effect=unimplemented, + effect=switch_self_attack(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Ferrothorn_124.py b/spirit/game/scripts/cards/SWSH4/Ferrothorn_124.py index 81d3758..5b7929a 100644 --- a/spirit/game/scripts/cards/SWSH4/Ferrothorn_124.py +++ b/spirit/game/scripts/cards/SWSH4/Ferrothorn_124.py @@ -1,6 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import damage_per, count_energy +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def _switch_self(ctx): + bench = ctx.my_bench() + if not bench: + return + target = await ctx.choose_pokemon(bench, "Choose your new Active Pokémon") + if target is not None: + await ctx.switch_active(ctx.player_id, target) + + card = PokemonCardDef( guid="4eb67323-529d-5bd2-8a9e-fee7f93ec5a3", key="SWSH4", @@ -26,7 +37,8 @@ card = PokemonCardDef( cost={PokemonTypes.METAL: 1}, damage=30, damage_operator="x", - effect=unimplemented, + effect=damage_per(count_energy("self", energy_type=PokemonTypes.METAL), 30, + also=_switch_self), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Flareon_26.py b/spirit/game/scripts/cards/SWSH4/Flareon_26.py index 4cb5d80..3958a74 100644 --- a/spirit/game/scripts/cards/SWSH4/Flareon_26.py +++ b/spirit/game/scripts/cards/SWSH4/Flareon_26.py @@ -1,5 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, def_for +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.passives_common import ability_lock_passive + + +def _has_memory_capsule(pokemon): + return any( + getattr(def_for(child.archetype_id), "display_name", None) == "Memory Capsule" + for child in pokemon.children + ) + + +def _grass_lock(pokemon, carrier): + if not _has_memory_capsule(carrier): + return False + return PokemonTypes.GRASS.value in (pokemon.get_attribute(AttrID.POKEMON_TYPES) or []) + card = PokemonCardDef( guid="c75b1074-f75a-5694-9302-e606ffa98718", @@ -22,7 +37,7 @@ card = PokemonCardDef( Ability( title="Incandescent Awakening", game_text="If this Pok\u00e9mon has a Memory Capsule attached, Grass Pok\u00e9mon in play (both yours and your opponent's) have no Abilities.", - effect=unimplemented, + passive=ability_lock_passive(_grass_lock), ), Attack( title="Fire Mane", diff --git a/spirit/game/scripts/cards/SWSH4/Forretress_114.py b/spirit/game/scripts/cards/SWSH4/Forretress_114.py index 961bef6..58a105d 100644 --- a/spirit/game/scripts/cards/SWSH4/Forretress_114.py +++ b/spirit/game/scripts/cards/SWSH4/Forretress_114.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack + + +async def double_ko(ctx): + """Both Active Pokemon are Knocked Out.""" + await ctx.knock_out(ctx.defender) + await ctx.knock_out(ctx.attacker) + card = PokemonCardDef( guid="e86a0894-8d25-5489-bbeb-71107950358f", @@ -25,13 +33,13 @@ card = PokemonCardDef( game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 2}, damage=120, - effect=unimplemented, + effect=recoil_attack(30), ), Attack( title="Double KO", game_text="Both Active Pok\u00e9mon are Knocked Out.", cost={PokemonTypes.METAL: 2, PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + effect=double_ko, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/GalarianDarmanitanVMAX_187.py b/spirit/game/scripts/cards/SWSH4/GalarianDarmanitanVMAX_187.py index 6358ead..179bc09 100644 --- a/spirit/game/scripts/cards/SWSH4/GalarianDarmanitanVMAX_187.py +++ b/spirit/game/scripts/cards/SWSH4/GalarianDarmanitanVMAX_187.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import spread_damage card = PokemonCardDef( guid="dd46d0f3-cbe1-5248-99ac-4f5e4fbf8584", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="This attack also does 30 damage to each of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.WATER: 4}, damage=200, - effect=unimplemented, + effect=spread_damage(30, also_base=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/GalarianDarmanitanVMAX_37.py b/spirit/game/scripts/cards/SWSH4/GalarianDarmanitanVMAX_37.py index ea7b701..10dcc32 100644 --- a/spirit/game/scripts/cards/SWSH4/GalarianDarmanitanVMAX_37.py +++ b/spirit/game/scripts/cards/SWSH4/GalarianDarmanitanVMAX_37.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import spread_damage card = PokemonCardDef( guid="28180ee4-5247-514d-94fe-c1a3f9b523ed", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="This attack also does 30 damage to each of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.WATER: 4}, damage=200, - effect=unimplemented, + effect=spread_damage(30, also_base=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/GalarianDarmanitanV_169.py b/spirit/game/scripts/cards/SWSH4/GalarianDarmanitanV_169.py index eeaa75b..e704e87 100644 --- a/spirit/game/scripts/cards/SWSH4/GalarianDarmanitanV_169.py +++ b/spirit/game/scripts/cards/SWSH4/GalarianDarmanitanV_169.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack, recoil_attack card = PokemonCardDef( guid="e657b7fe-75a8-50c7-85ed-6423f7d04aa2", @@ -23,14 +24,14 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 1}, damage=50, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), Attack( title="Frozen Slice", game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, damage=190, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/GalarianDarmanitanV_36.py b/spirit/game/scripts/cards/SWSH4/GalarianDarmanitanV_36.py index adb6fc2..2dc0910 100644 --- a/spirit/game/scripts/cards/SWSH4/GalarianDarmanitanV_36.py +++ b/spirit/game/scripts/cards/SWSH4/GalarianDarmanitanV_36.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack, recoil_attack card = PokemonCardDef( guid="d3cc6a83-56bc-5557-84c0-b00ce99d0bd2", @@ -23,14 +24,14 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 1}, damage=50, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), Attack( title="Frozen Slice", game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, damage=190, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/GalarianMeowth_112.py b/spirit/game/scripts/cards/SWSH4/GalarianMeowth_112.py index dc9b31c..35c269f 100644 --- a/spirit/game/scripts/cards/SWSH4/GalarianMeowth_112.py +++ b/spirit/game/scripts/cards/SWSH4/GalarianMeowth_112.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="b84cff1b-7fa2-5a91-aba0-4e6cc38f1320", @@ -30,7 +31,7 @@ card = PokemonCardDef( cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 1}, damage=20, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=3, per_heads=20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/GalarianObstagoon_198.py b/spirit/game/scripts/cards/SWSH4/GalarianObstagoon_198.py index 57a3e97..da1615a 100644 --- a/spirit/game/scripts/cards/SWSH4/GalarianObstagoon_198.py +++ b/spirit/game/scripts/cards/SWSH4/GalarianObstagoon_198.py @@ -1,5 +1,31 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.passives_common import prevent_damage_when + + +async def untamed_shout(ctx): + if not await ctx.ask_yes_no( + "Put 3 damage counters on 1 of your opponent's Pokémon?"): + return + candidates = ctx.opponent_pokemon_in_play() + if not candidates: + return + target = await ctx.choose_pokemon( + candidates, "Choose 1 of your opponent's Pokémon") + if target is not None: + await ctx.deal_damage(30, target=target, as_counters=True) + + +def _attacker_is_basic(calc, carrier) -> bool: + attacker = calc.attacker + return attacker is not None \ + and attacker.get_attribute(AttrID.STAGE) == PokemonStage.BASIC.value + + +async def obstruct(ctx): + await ctx.deal_damage() + ctx.add_passive_through_opponents_turn( + ctx.attacker, prevent_damage_when(_attacker_is_basic)) card = PokemonCardDef( guid="15e9f35b-30c3-5065-a08d-040cc3351bd1", @@ -22,14 +48,15 @@ card = PokemonCardDef( Ability( title="Untamed Shout", game_text="When you play this Pok\u00e9mon from your hand to evolve 1 of your Pok\u00e9mon during your turn, you may put 3 damage counters on 1 of your opponent's Pok\u00e9mon.", - effect=unimplemented, + trigger=Triggers.ON_EVOLVE, + effect=untamed_shout, ), Attack( title="Obstruct", game_text="During your opponent's next turn, prevent all damage done to this Pok\u00e9mon by attacks from Basic Pok\u00e9mon.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 1}, damage=90, - effect=unimplemented, + effect=obstruct, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/GalarianPerrserker_113.py b/spirit/game/scripts/cards/SWSH4/GalarianPerrserker_113.py index 016ceb7..694260f 100644 --- a/spirit/game/scripts/cards/SWSH4/GalarianPerrserker_113.py +++ b/spirit/game/scripts/cards/SWSH4/GalarianPerrserker_113.py @@ -1,5 +1,23 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_trainer_card + + +async def stealy_claws(ctx): + """Flip 3 coins. If any of them are heads, your opponent reveals their + hand. Then, for each heads, discard a Trainer card from your opponent's + hand.""" + await ctx.deal_damage() + results = await ctx.flip_coins(3, "Stealy Claws") + heads = sum(1 for r in results if r) + if heads == 0: + return + await ctx.reveal_hand(of_player=ctx.opponent_id) + await ctx.discard_from_hand( + heads, prompt="Discard a Trainer card", + player_id=ctx.opponent_id, predicate=is_trainer_card, + ) + card = PokemonCardDef( guid="40123d30-321a-5050-a020-004e97d97f12", @@ -25,7 +43,7 @@ card = PokemonCardDef( game_text="Flip 3 coins. If any of them are heads, your opponent reveals their hand. Then, for each heads, discard a Trainer card from your opponent's hand.", cost={PokemonTypes.METAL: 1}, damage=20, - effect=unimplemented, + effect=stealy_claws, ), Attack( title="Claw Slash", @@ -33,4 +51,4 @@ card = PokemonCardDef( damage=90, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH4/GalarianSirfetchdV_174.py b/spirit/game/scripts/cards/SWSH4/GalarianSirfetchdV_174.py index e239181..17a395e 100644 --- a/spirit/game/scripts/cards/SWSH4/GalarianSirfetchdV_174.py +++ b/spirit/game/scripts/cards/SWSH4/GalarianSirfetchdV_174.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import energy_provides_type + + +async def resolute_spear(ctx): + """Once per turn, on move to Active: you may move any amount of Fighting Energy from your other Pokemon to it.""" + others = [p for p in ctx.my_pokemon_in_play() if p is not ctx.source] + if not any(energy_provides_type(e, PokemonTypes.FIGHTING.value) + for p in others for e in ctx.attached_energies(p)): + return + if not await ctx.ask_yes_no("Move any amount of Fighting Energy from your other Pokémon to this Pokémon?"): + return + await ctx.move_energy_freely( + others, [ctx.source], + predicate=lambda e: energy_provides_type(e, PokemonTypes.FIGHTING.value), + ) + card = PokemonCardDef( guid="af771144-e039-53ec-9841-3786dfdee175", @@ -21,14 +37,15 @@ card = PokemonCardDef( Ability( title="Resolute Spear", game_text="Once during your turn, when this Pok\u00e9mon moves from your Bench to the Active Spot, you may move any amount of Fighting Energy from your other Pok\u00e9mon to it.", - effect=unimplemented, + trigger=Triggers.ON_MOVE_TO_ACTIVE, + effect=resolute_spear, ), Attack( title="Meteor Smash", game_text="During your next turn, this Pok\u00e9mon can't attack.", cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=200, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/GalarianStunfisk_125.py b/spirit/game/scripts/cards/SWSH4/GalarianStunfisk_125.py index 40aff4a..e0c1f43 100644 --- a/spirit/game/scripts/cards/SWSH4/GalarianStunfisk_125.py +++ b/spirit/game/scripts/cards/SWSH4/GalarianStunfisk_125.py @@ -1,5 +1,27 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import in_active_spot + + +async def counterattack(ctx): + """Active Spot only: if damaged by an opponent's attack (even if + Knocked Out), put 3 damage counters on the Attacking Pokemon.""" + if not in_active_spot(ctx.board, ctx.player_id, ctx.source): + return + attacker = ctx.damaged_by + if attacker is None: + return + await ctx.deal_damage(30, target=attacker, apply_modifiers=False, as_counters=True) + + +async def grip_and_squeeze(ctx): + """90. During the opponent's next turn, the Defending Pokemon can't + retreat.""" + await ctx.deal_damage() + defender = ctx.defender + if defender is not None and not ctx.effects_blocked(defender): + ctx.lock_retreat(defender) + card = PokemonCardDef( guid="1b2d10fb-ccb4-5ded-9437-d7299e3a2c3d", @@ -22,14 +44,15 @@ card = PokemonCardDef( Ability( title="Counterattack", game_text="If this Pok\u00e9mon is in the Active Spot and is damaged by an attack from your opponent's Pok\u00e9mon (even if this Pok\u00e9mon is Knocked Out), put 3 damage counters on the Attacking Pok\u00e9mon.", - effect=unimplemented, + trigger=Triggers.ON_DAMAGED_BY_ATTACK, + effect=counterattack, ), Attack( title="Grip and Squeeze", game_text="During your opponent's next turn, the Defending Pok\u00e9mon can't retreat.", cost={PokemonTypes.COLORLESS: 3}, damage=90, - effect=unimplemented, + effect=grip_and_squeeze, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Galvantula_56.py b/spirit/game/scripts/cards/SWSH4/Galvantula_56.py index 2f2bb60..fded8b4 100644 --- a/spirit/game/scripts/cards/SWSH4/Galvantula_56.py +++ b/spirit/game/scripts/cards/SWSH4/Galvantula_56.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack, damage_per, damage_counters_on card = PokemonCardDef( guid="3424d5f2-18d3-5036-8e28-375004d96090", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.LIGHTNING: 1}, damage=20, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), Attack( title="Shocking Pursuit", @@ -32,7 +33,7 @@ card = PokemonCardDef( cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 1}, damage=20, damage_operator="x", - effect=unimplemented, + effect=damage_per(damage_counters_on("defender"), 20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Garbodor_111.py b/spirit/game/scripts/cards/SWSH4/Garbodor_111.py index 8a2298f..03c5260 100644 --- a/spirit/game/scripts/cards/SWSH4/Garbodor_111.py +++ b/spirit/game/scripts/cards/SWSH4/Garbodor_111.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions, AttrID, TrainerType +from spirit.game.card_effects.attacks_common import condition_attack + +_TOOL_TYPES = (TrainerType.POKEMON_TOOL.value, TrainerType.POKEMON_TOOL_F.value) + + +def _is_tool_card(card): + return card.get_attribute(AttrID.TRAINER_TYPE) in _TOOL_TYPES + + +async def trash_cyclone(ctx): + """30 damage for each Pokemon Tool card in your discard pile. Then, + shuffle those cards into your deck.""" + tools = [c for c in ctx.discard_pile() if _is_tool_card(c)] + await ctx.deal_damage(30 * len(tools)) + if tools: + await ctx.shuffle_into_deck(tools, ctx.player_id) + card = PokemonCardDef( guid="95fe0850-6bb7-5d27-9e62-83d87f517693", @@ -21,18 +38,18 @@ card = PokemonCardDef( abilities=[ Attack( title="Trash Cyclone", - game_text="This attack does 30 damage for each Pok\u00e9mon Tool card in your discard pile. Then, shuffle those cards into your deck.", + game_text="This attack does 30 damage for each Pokémon Tool card in your discard pile. Then, shuffle those cards into your deck.", cost={PokemonTypes.COLORLESS: 2}, damage=30, damage_operator="x", - effect=unimplemented, + effect=trash_cyclone, ), Attack( title="Poison Spray", - game_text="Your opponent's Active Pok\u00e9mon is now Poisoned.", + game_text="Your opponent's Active Pokémon is now Poisoned.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 2}, damage=80, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH4/Genesect_16.py b/spirit/game/scripts/cards/SWSH4/Genesect_16.py index 3f7f37c..49f5a3a 100644 --- a/spirit/game/scripts/cards/SWSH4/Genesect_16.py +++ b/spirit/game/scripts/cards/SWSH4/Genesect_16.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import snipe_attack, lock_all_attacks + + +async def techno_blast(ctx): + """120. During your next turn, this Pokemon can't attack.""" + await ctx.deal_damage() + lock_all_attacks(ctx, ctx.attacker) + card = PokemonCardDef( guid="fcb48792-c8f7-5253-b388-737a74ddccfc", @@ -22,14 +30,14 @@ card = PokemonCardDef( title="Linear Attack", game_text="This attack does 50 damage to 1 of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=snipe_attack(50, pool="any", count=1), ), Attack( title="Techno Blast", game_text="During your next turn, this Pok\u00e9mon can't attack.", cost={PokemonTypes.GRASS: 2, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + effect=techno_blast, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Girafarig_65.py b/spirit/game/scripts/cards/SWSH4/Girafarig_65.py index d803d14..a8ef5f0 100644 --- a/spirit/game/scripts/cards/SWSH4/Girafarig_65.py +++ b/spirit/game/scripts/cards/SWSH4/Girafarig_65.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import place_counters + + +async def commanding_tail(ctx): + """30. You may have your opponent shuffle their hand into their deck. If you do, your opponent draws 4 cards.""" + await ctx.deal_damage() + if await ctx.ask_yes_no( + "Have your opponent shuffle their hand into their deck? If you do, your opponent draws 4 cards." + ): + await ctx.shuffle_into_deck(ctx.hand(ctx.opponent_id), ctx.opponent_id) + await ctx.draw_cards(4, player_id=ctx.opponent_id) + card = PokemonCardDef( guid="7940396c-6459-5205-aa08-2e7740c3d027", @@ -23,14 +35,14 @@ card = PokemonCardDef( title="Psypower", game_text="Put 2 damage counters on your opponent's Pok\u00e9mon in any way you like.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=place_counters(2, "choose_any_opponent"), ), Attack( title="Commanding Tail", game_text="You may have your opponent shuffle their hand into their deck. If you do, your opponent draws 4 cards.", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=commanding_tail, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Gogoat_18.py b/spirit/game/scripts/cards/SWSH4/Gogoat_18.py index 5865c8a..bb03430 100644 --- a/spirit/game/scripts/cards/SWSH4/Gogoat_18.py +++ b/spirit/game/scripts/cards/SWSH4/Gogoat_18.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack card = PokemonCardDef( guid="4574209f-5c17-5a26-8af9-1237b0da4230", @@ -29,7 +30,7 @@ card = PokemonCardDef( game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.GRASS: 2, PokemonTypes.COLORLESS: 1}, damage=160, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Grapploct_101.py b/spirit/game/scripts/cards/SWSH4/Grapploct_101.py index f272cc9..12b7955 100644 --- a/spirit/game/scripts/cards/SWSH4/Grapploct_101.py +++ b/spirit/game/scripts/cards/SWSH4/Grapploct_101.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if + + +async def full_nelson(ctx): + await ctx.deal_damage() + defender = ctx.defender + if defender is not None and not ctx.effects_blocked(defender): + ctx.lock_retreat(defender) + + +def _used_full_nelson_last_turn(ctx): + return ctx.attack_used_last_turn(title="Full Nelson", entity=ctx.attacker) + card = PokemonCardDef( guid="4df97f55-6696-5c4a-a300-7cc776e9658f", @@ -24,7 +37,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, the Defending Pok\u00e9mon can't retreat.", cost={PokemonTypes.FIGHTING: 1}, damage=30, - effect=unimplemented, + effect=full_nelson, ), Attack( title="Tentacle Buster", @@ -32,7 +45,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 1}, damage=50, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_used_full_nelson_last_turn, 120), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/HerosMedal_152.py b/spirit/game/scripts/cards/SWSH4/HerosMedal_152.py index e75d856..99c4ed1 100644 --- a/spirit/game/scripts/cards/SWSH4/HerosMedal_152.py +++ b/spirit/game/scripts/cards/SWSH4/HerosMedal_152.py @@ -1,5 +1,26 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import PokemonToolCardDef +from spirit.game.attributes import Rarities, AttrID +from spirit.game.session.passives import Passive, carrier_pokemon +from spirit.game.card_effects.pokemon import is_pokemon_vmax + + +class HerosMedalPassive(Passive): + def max_hp_bonus(self, pokemon, carrier): + if carrier_pokemon(carrier) is not pokemon: + return 0 + return -100 + + def modify_prizes_for_knockout(self, pokemon, ctx, count, carrier): + if carrier_pokemon(carrier) is not pokemon: + return count + if not ctx.is_attack_effect() or ctx.player_id == pokemon.owning_player_id: + return count + return max(0, count - 1) + + +def _heros_medal_attach_to(pokemon): + return is_pokemon_vmax(pokemon.archetype_id) and pokemon.get_attribute(AttrID.HP, 0) > 100 + card = PokemonToolCardDef( guid="4f30f3df-b7f8-53ae-b4a4-f7a65f63978f", @@ -11,5 +32,6 @@ card = PokemonToolCardDef( collector_number=152, set_code="SWSH4", rarity=Rarities.Uncommon, - effect=unimplemented + attach_to=_heros_medal_attach_to, + passive=HerosMedalPassive(), ) diff --git a/spirit/game/scripts/cards/SWSH4/HerosMedal_201.py b/spirit/game/scripts/cards/SWSH4/HerosMedal_201.py index c9e6bfa..f8d75b6 100644 --- a/spirit/game/scripts/cards/SWSH4/HerosMedal_201.py +++ b/spirit/game/scripts/cards/SWSH4/HerosMedal_201.py @@ -1,5 +1,26 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import PokemonToolCardDef +from spirit.game.attributes import Rarities, AttrID +from spirit.game.session.passives import Passive, carrier_pokemon +from spirit.game.card_effects.pokemon import is_pokemon_vmax + + +class HerosMedalPassive(Passive): + def max_hp_bonus(self, pokemon, carrier): + if carrier_pokemon(carrier) is not pokemon: + return 0 + return -100 + + def modify_prizes_for_knockout(self, pokemon, ctx, count, carrier): + if carrier_pokemon(carrier) is not pokemon: + return count + if not ctx.is_attack_effect() or ctx.player_id == pokemon.owning_player_id: + return count + return max(0, count - 1) + + +def _heros_medal_attach_to(pokemon): + return is_pokemon_vmax(pokemon.archetype_id) and pokemon.get_attribute(AttrID.HP, 0) > 100 + card = PokemonToolCardDef( guid="c2ea0f80-cb4b-5772-b86e-1f7490bbe80a", @@ -11,5 +32,6 @@ card = PokemonToolCardDef( collector_number=201, set_code="SWSH4", rarity=Rarities.RareSecret, - effect=unimplemented + attach_to=_heros_medal_attach_to, + passive=HerosMedalPassive(), ) diff --git a/spirit/game/scripts/cards/SWSH4/Hitmontop_88.py b/spirit/game/scripts/cards/SWSH4/Hitmontop_88.py index 47b8d90..f124718 100644 --- a/spirit/game/scripts/cards/SWSH4/Hitmontop_88.py +++ b/spirit/game/scripts/cards/SWSH4/Hitmontop_88.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import discard_then_draw +from spirit.game.card_effects.attacks_common import bonus_if + + +def _played_bea(ctx): + return ctx.played_trainer_this_turn("Bea") > 0 + card = PokemonCardDef( guid="f6517430-0c8a-5700-831b-68aa8b3020b8", @@ -22,7 +29,7 @@ card = PokemonCardDef( title="Cycle Draw", game_text="Discard a card from your hand. If you do, draw 3 cards.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=discard_then_draw(1, 3), ), Attack( title="Tornado Kick", @@ -30,7 +37,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 2}, damage=50, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_played_bea, 80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Jirachi_119.py b/spirit/game/scripts/cards/SWSH4/Jirachi_119.py index 3f75c96..d50a501 100644 --- a/spirit/game/scripts/cards/SWSH4/Jirachi_119.py +++ b/spirit/game/scripts/cards/SWSH4/Jirachi_119.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import look_at_top, search_attach_energy +from spirit.game.card_effects.pokemon import in_active_spot +from spirit.game.card_effects.trainers import is_basic_energy_card card = PokemonCardDef( guid="920411e1-3b6e-5bc9-89db-966fe258f489", @@ -22,13 +25,16 @@ card = PokemonCardDef( Ability( title="Dreamy Revelation", game_text="Once during your turn, if this Pok\u00e9mon is in the Active Spot, you may look at the top 2 cards of your deck and put 1 of them into your hand. Put the other card back on top of your deck.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=in_active_spot, + effect=look_at_top(2, take=1, rest="back", + prompt="Choose 1 of the top 2 cards to put into your hand."), ), Attack( title="Amazing Star", game_text="Search your deck for up to 7 basic Energy cards and attach them to your Pok\u00e9mon in any way you like. Then, shuffle your deck.", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.FIGHTING: 1, PokemonTypes.METAL: 1}, - effect=unimplemented, + effect=search_attach_energy(predicate=is_basic_energy_card, count=7), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Jolteon_47.py b/spirit/game/scripts/cards/SWSH4/Jolteon_47.py index 7d3fc3e..1163dda 100644 --- a/spirit/game/scripts/cards/SWSH4/Jolteon_47.py +++ b/spirit/game/scripts/cards/SWSH4/Jolteon_47.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.passives_common import ability_lock_passive +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, def_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_water_pokemon + + +def _has_memory_capsule(carrier): + for child in carrier.children: + d = def_for(child.archetype_id) + if d and d.display_name == "Memory Capsule": + return True + return False + + +def _water_locked(pokemon, carrier): + return _has_memory_capsule(carrier) and is_water_pokemon(pokemon) card = PokemonCardDef( guid="ef51ea27-9a73-5ffa-8faf-1cb81b807524", @@ -22,7 +36,7 @@ card = PokemonCardDef( Ability( title="Thunderous Awakening", game_text="If this Pok\u00e9mon has a Memory Capsule attached, Water Pok\u00e9mon in play (both yours and your opponent's) have no Abilities.", - effect=unimplemented, + passive=ability_lock_passive(_water_locked), ), Attack( title="Electric Ball", diff --git a/spirit/game/scripts/cards/SWSH4/Kakuna_2.py b/spirit/game/scripts/cards/SWSH4/Kakuna_2.py index 41d3f0e..4429299 100644 --- a/spirit/game/scripts/cards/SWSH4/Kakuna_2.py +++ b/spirit/game/scripts/cards/SWSH4/Kakuna_2.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import heal_attack card = PokemonCardDef( guid="a42155c0-2b1a-5f25-bf76-8f73eea78d4d", @@ -23,7 +24,7 @@ card = PokemonCardDef( title="Shed Skin", game_text="Heal 30 damage from this Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=heal_attack(30, target="self"), ), Attack( title="Bug Bite", diff --git a/spirit/game/scripts/cards/SWSH4/Krokorok_108.py b/spirit/game/scripts/cards/SWSH4/Krokorok_108.py index 61e1fea..c90a0b3 100644 --- a/spirit/game/scripts/cards/SWSH4/Krokorok_108.py +++ b/spirit/game/scripts/cards/SWSH4/Krokorok_108.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import mill_attack card = PokemonCardDef( guid="1577316d-c926-5fe6-a794-1654aaef0e0c", @@ -28,7 +29,7 @@ card = PokemonCardDef( title="Dredge Up", game_text="Discard the top 3 cards of your opponent's deck.", cost={PokemonTypes.COLORLESS: 3}, - effect=unimplemented, + effect=mill_attack(3), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Krookodile_109.py b/spirit/game/scripts/cards/SWSH4/Krookodile_109.py index f007d15..e30401a 100644 --- a/spirit/game/scripts/cards/SWSH4/Krookodile_109.py +++ b/spirit/game/scripts/cards/SWSH4/Krookodile_109.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack, mill_attack card = PokemonCardDef( guid="edc2cf65-caed-53cf-a65c-76816870adc7", @@ -23,14 +24,14 @@ card = PokemonCardDef( title="Dredge Up", game_text="Discard the top 3 cards of your opponent's deck.", cost={PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + effect=mill_attack(3), ), Attack( title="Tantrum", - game_text="This Pok\u00e9mon is now Confused.", + game_text="This Pokémon is now Confused.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 3}, damage=180, - effect=unimplemented, + effect=condition_attack(self_conditions=(SpecialConditions.CONFUSED,)), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH4/LeagueStaff_153.py b/spirit/game/scripts/cards/SWSH4/LeagueStaff_153.py index f74d034..6f4bb5a 100644 --- a/spirit/game/scripts/cards/SWSH4/LeagueStaff_153.py +++ b/spirit/game/scripts/cards/SWSH4/LeagueStaff_153.py @@ -1,6 +1,22 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef, def_for from spirit.game.attributes import Rarities + +def _wyndon_stadium_in_play(ctx) -> bool: + stadium = ctx.stadium_in_play() + if stadium is None: + return False + definition = def_for(getattr(stadium, "archetype_id", None) or "") + return definition is not None and definition.display_name == "Wyndon Stadium" + + +async def league_staff(ctx): + """Draw 2 cards. If Wyndon Stadium is in play, draw 2 more cards.""" + await ctx.draw_cards(2) + if _wyndon_stadium_in_play(ctx): + await ctx.draw_cards(2) + + card = SupporterCardDef( guid="04da4d56-d324-5262-bad7-8599986b5234", key="SWSH4", @@ -11,5 +27,5 @@ card = SupporterCardDef( collector_number=153, set_code="SWSH4", rarity=Rarities.Uncommon, - effect=unimplemented + effect=league_staff ) diff --git a/spirit/game/scripts/cards/SWSH4/Leon_154.py b/spirit/game/scripts/cards/SWSH4/Leon_154.py index 1966483..bf809ca 100644 --- a/spirit/game/scripts/cards/SWSH4/Leon_154.py +++ b/spirit/game/scripts/cards/SWSH4/Leon_154.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.session.passives import TurnDamageModifier + + +async def leon_effect(ctx): + """This turn, your Pokemon's attacks do 30 more damage to the opponent's Active (before W/R).""" + ctx.add_turn_damage_modifier(TurnDamageModifier(30, ctx.player_id)) + for pokemon in ctx.my_pokemon_in_play(): + await ctx.add_stat_visualization( + pokemon, "Positive", "DamageDealtIncreased", card_text="+30 damage" + ) + card = SupporterCardDef( guid="d28b60f1-638d-53c1-8722-06d86822b9f0", @@ -11,5 +22,5 @@ card = SupporterCardDef( collector_number=154, set_code="SWSH4", rarity=Rarities.RareHolo, - effect=unimplemented + effect=leon_effect ) diff --git a/spirit/game/scripts/cards/SWSH4/Leon_182.py b/spirit/game/scripts/cards/SWSH4/Leon_182.py index e22fffb..0d369f7 100644 --- a/spirit/game/scripts/cards/SWSH4/Leon_182.py +++ b/spirit/game/scripts/cards/SWSH4/Leon_182.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.session.passives import TurnDamageModifier + + +async def leon_effect(ctx): + """This turn, your Pokemon's attacks do 30 more damage to the opponent's Active (before W/R).""" + ctx.add_turn_damage_modifier(TurnDamageModifier(30, ctx.player_id)) + for pokemon in ctx.my_pokemon_in_play(): + await ctx.add_stat_visualization( + pokemon, "Positive", "DamageDealtIncreased", card_text="+30 damage" + ) + card = SupporterCardDef( guid="a84d3fe7-8701-51c3-84be-f92481804587", @@ -11,5 +22,5 @@ card = SupporterCardDef( collector_number=182, set_code="SWSH4", rarity=Rarities.RareUltra, - effect=unimplemented + effect=leon_effect ) diff --git a/spirit/game/scripts/cards/SWSH4/Leon_195.py b/spirit/game/scripts/cards/SWSH4/Leon_195.py index 9e35f06..e4143aa 100644 --- a/spirit/game/scripts/cards/SWSH4/Leon_195.py +++ b/spirit/game/scripts/cards/SWSH4/Leon_195.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.session.passives import TurnDamageModifier + + +async def leon_effect(ctx): + """This turn, your Pokemon's attacks do 30 more damage to the opponent's Active (before W/R).""" + ctx.add_turn_damage_modifier(TurnDamageModifier(30, ctx.player_id)) + for pokemon in ctx.my_pokemon_in_play(): + await ctx.add_stat_visualization( + pokemon, "Positive", "DamageDealtIncreased", card_text="+30 damage" + ) + card = SupporterCardDef( guid="02ccdfeb-de31-52a4-88b5-3f88fd9295ff", @@ -11,5 +22,5 @@ card = SupporterCardDef( collector_number=195, set_code="SWSH4", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=leon_effect ) diff --git a/spirit/game/scripts/cards/SWSH4/Loudred_136.py b/spirit/game/scripts/cards/SWSH4/Loudred_136.py index 632ced6..1e63929 100644 --- a/spirit/game/scripts/cards/SWSH4/Loudred_136.py +++ b/spirit/game/scripts/cards/SWSH4/Loudred_136.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_in_play, has_attack_titled card = PokemonCardDef( guid="f383d6f4-4bd2-53d9-88fb-c43793654013", @@ -25,7 +26,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=20, damage_operator="x", - effect=unimplemented, + effect=damage_per(count_in_play("mine", has_attack_titled("Round")), 20), ), Attack( title="Hyper Voice", diff --git a/spirit/game/scripts/cards/SWSH4/Lucario_120.py b/spirit/game/scripts/cards/SWSH4/Lucario_120.py index c05c263..b649d71 100644 --- a/spirit/game/scripts/cards/SWSH4/Lucario_120.py +++ b/spirit/game/scripts/cards/SWSH4/Lucario_120.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import draw_attack card = PokemonCardDef( guid="772259d0-0a1f-598a-b274-222791fff26e", @@ -25,14 +26,14 @@ card = PokemonCardDef( game_text="Draw a card.", cost={PokemonTypes.METAL: 1}, damage=40, - effect=unimplemented, + effect=draw_attack(1), ), Attack( title="Knuckle Impact", game_text="During your next turn, this Pok\u00e9mon can't attack.", cost={PokemonTypes.METAL: 2, PokemonTypes.COLORLESS: 1}, damage=160, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Lugia_132.py b/spirit/game/scripts/cards/SWSH4/Lugia_132.py index ba96d1e..58c1385 100644 --- a/spirit/game/scripts/cards/SWSH4/Lugia_132.py +++ b/spirit/game/scripts/cards/SWSH4/Lugia_132.py @@ -1,6 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def wind_pressure(ctx): + """If the opponent has 5 or fewer cards in hand, this attack does nothing.""" + if ctx.hand_size(ctx.opponent_id) <= 5: + return + await ctx.deal_damage() + card = PokemonCardDef( guid="7cdebca0-68b2-5f6e-95f8-eeb18af13a79", key="SWSH4", @@ -29,7 +36,7 @@ card = PokemonCardDef( game_text="If your opponent has 5 or fewer cards in their hand, this attack does nothing.", cost={PokemonTypes.COLORLESS: 4}, damage=250, - effect=unimplemented, + effect=wind_pressure, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Magcargo_28.py b/spirit/game/scripts/cards/SWSH4/Magcargo_28.py index 2fb3925..0b0c7b1 100644 --- a/spirit/game/scripts/cards/SWSH4/Magcargo_28.py +++ b/spirit/game/scripts/cards/SWSH4/Magcargo_28.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import self_energy_discard_attack card = PokemonCardDef( guid="a4919470-65ed-59ed-8fc1-69de657f4dbf", @@ -29,7 +30,7 @@ card = PokemonCardDef( game_text="Discard 2 Energy from this Pok\u00e9mon.", cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 2}, damage=180, - effect=unimplemented, + effect=self_energy_discard_attack(count=2), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Magearna_128.py b/spirit/game/scripts/cards/SWSH4/Magearna_128.py index 7e29d47..3db539b 100644 --- a/spirit/game/scripts/cards/SWSH4/Magearna_128.py +++ b/spirit/game/scripts/cards/SWSH4/Magearna_128.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import shuffle_hand_into_deck_draw +from spirit.game.card_effects.attacks_common import damage_per, count_bench card = PokemonCardDef( guid="37ab4f04-a57e-5d9d-8cf4-54a6ea3f6675", @@ -23,7 +25,7 @@ card = PokemonCardDef( title="Overhaul", game_text="Shuffle your hand into your deck. Then, draw 6 cards.", cost={PokemonTypes.METAL: 1}, - effect=unimplemented, + effect=shuffle_hand_into_deck_draw(6), ), Attack( title="Windup Cannon", @@ -31,7 +33,7 @@ card = PokemonCardDef( cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 1}, damage=10, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_bench("opponent"), 20, base=10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Manectric_52.py b/spirit/game/scripts/cards/SWSH4/Manectric_52.py index e7828b3..d75c724 100644 --- a/spirit/game/scripts/cards/SWSH4/Manectric_52.py +++ b/spirit/game/scripts/cards/SWSH4/Manectric_52.py @@ -1,6 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def high_speed(ctx): + """When played from hand to evolve during your turn, you may draw 3 cards.""" + if await ctx.ask_yes_no("Draw 3 cards?"): + await ctx.draw_cards(3) + + card = PokemonCardDef( guid="f606c93f-8709-56ad-8966-013c0fd556f3", key="SWSH4", @@ -22,7 +29,8 @@ card = PokemonCardDef( Ability( title="High Speed", game_text="When you play this Pok\u00e9mon from your hand to evolve 1 of your Pok\u00e9mon during your turn, you may draw 3 cards.", - effect=unimplemented, + trigger=Triggers.ON_EVOLVE, + effect=high_speed, ), Attack( title="Electric Ball", diff --git a/spirit/game/scripts/cards/SWSH4/MemoryCapsule_155.py b/spirit/game/scripts/cards/SWSH4/MemoryCapsule_155.py index 9638359..3fac1c4 100644 --- a/spirit/game/scripts/cards/SWSH4/MemoryCapsule_155.py +++ b/spirit/game/scripts/cards/SWSH4/MemoryCapsule_155.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented +from spirit.game.data_utils import PokemonToolCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import MemoryCapsulePassive card = PokemonToolCardDef( guid="b891cd7c-3b67-56ea-a84e-a0065e194be6", @@ -11,5 +12,5 @@ card = PokemonToolCardDef( collector_number=155, set_code="SWSH4", rarity=Rarities.Uncommon, - effect=unimplemented + passive=MemoryCapsulePassive(), ) diff --git a/spirit/game/scripts/cards/SWSH4/MemoryCapsule_202.py b/spirit/game/scripts/cards/SWSH4/MemoryCapsule_202.py index d3c7d8f..2271db0 100644 --- a/spirit/game/scripts/cards/SWSH4/MemoryCapsule_202.py +++ b/spirit/game/scripts/cards/SWSH4/MemoryCapsule_202.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented +from spirit.game.data_utils import PokemonToolCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import MemoryCapsulePassive card = PokemonToolCardDef( guid="1174ee9f-e037-5cbe-9c50-91e08a863f91", @@ -11,5 +12,5 @@ card = PokemonToolCardDef( collector_number=202, set_code="SWSH4", rarity=Rarities.RareSecret, - effect=unimplemented + passive=MemoryCapsulePassive(), ) diff --git a/spirit/game/scripts/cards/SWSH4/Metagross_118.py b/spirit/game/scripts/cards/SWSH4/Metagross_118.py index a6ca5ca..3645424 100644 --- a/spirit/game/scripts/cards/SWSH4/Metagross_118.py +++ b/spirit/game/scripts/cards/SWSH4/Metagross_118.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import lock_defender_attacks +from spirit.game.card_effects.passives_common import retreat_free_when +from spirit.game.session.effects import is_evolution_pokemon + + +async def leg_quake(ctx): + """100. If the Defending Pokemon is an Evolution Pokemon, it can't + attack during your opponent's next turn.""" + await ctx.deal_damage() + defender = ctx.defender + if defender is not None and is_evolution_pokemon(defender): + lock_defender_attacks(ctx) + card = PokemonCardDef( guid="ab517795-5f1e-5a54-aca9-c3bbd687d09f", @@ -22,15 +35,15 @@ card = PokemonCardDef( abilities=[ Ability( title="Levitation Field", - game_text="Your Pok\u00e9mon in play have no Retreat Cost.", - effect=unimplemented, + game_text="Your Pokémon in play have no Retreat Cost.", + passive=retreat_free_when(lambda p, c: p.owning_player_id == c.owning_player_id), ), Attack( title="Leg Quake", - game_text="If the Defending Pok\u00e9mon is an Evolution Pok\u00e9mon, it can't attack during your opponent's next turn.", + game_text="If the Defending Pokémon is an Evolution Pokémon, it can't attack during your opponent's next turn.", cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 2}, damage=100, - effect=unimplemented, + effect=leg_quake, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH4/Mightyena_104.py b/spirit/game/scripts/cards/SWSH4/Mightyena_104.py index f7d5b70..604fa02 100644 --- a/spirit/game/scripts/cards/SWSH4/Mightyena_104.py +++ b/spirit/game/scripts/cards/SWSH4/Mightyena_104.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import debuff_defender_attacks card = PokemonCardDef( guid="e1c08f94-8227-5a9b-aa37-9abe7ab6b329", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, the Defending Pok\u00e9mon's attacks do 50 less damage (before applying Weakness and Resistance).", cost={PokemonTypes.DARKNESS: 1}, damage=20, - effect=unimplemented, + effect=debuff_defender_attacks(50), ), Attack( title="Pitch-Black Fangs", diff --git a/spirit/game/scripts/cards/SWSH4/Milcery_80.py b/spirit/game/scripts/cards/SWSH4/Milcery_80.py index 862a429..0d6c7e7 100644 --- a/spirit/game/scripts/cards/SWSH4/Milcery_80.py +++ b/spirit/game/scripts/cards/SWSH4/Milcery_80.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import heal_targets card = PokemonCardDef( guid="3d3ae574-fc82-51eb-8085-9b75ed33f20f", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Sweet Scent", game_text="Heal 20 damage from 1 of your Pok\u00e9mon.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=heal_targets(20, "choice"), ), Attack( title="Tackle", diff --git a/spirit/game/scripts/cards/SWSH4/MoomooCheese_156.py b/spirit/game/scripts/cards/SWSH4/MoomooCheese_156.py index 14618d7..51b5568 100644 --- a/spirit/game/scripts/cards/SWSH4/MoomooCheese_156.py +++ b/spirit/game/scripts/cards/SWSH4/MoomooCheese_156.py @@ -1,6 +1,24 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef from spirit.game.attributes import Rarities + +def _moomoo_condition(board, player_id): + return any(board.attached_energies(p) for p in board.pokemon_in_play(player_id)) + + +async def moomoo_cheese(ctx): + """Heal 30 damage from up to 2 of your Pokemon that have Energy attached.""" + candidates = [p for p in ctx.my_pokemon_in_play() if ctx.attached_energies(p)] + if not candidates: + return + picks = await ctx.choose_cards( + candidates, 2, minimum=0, + prompt="Choose up to 2 Pokémon with Energy attached to heal", + ) + for pokemon in picks: + await ctx.heal(30, pokemon) + + card = ItemCardDef( guid="4e064c92-770c-58fb-905d-647375971e8b", key="SWSH4", @@ -11,5 +29,6 @@ card = ItemCardDef( collector_number=156, set_code="SWSH4", rarity=Rarities.Uncommon, - effect=unimplemented + effect=moomoo_cheese, + condition=_moomoo_condition, ) diff --git a/spirit/game/scripts/cards/SWSH4/Mudsdale_97.py b/spirit/game/scripts/cards/SWSH4/Mudsdale_97.py index 995fc23..5958c5e 100644 --- a/spirit/game/scripts/cards/SWSH4/Mudsdale_97.py +++ b/spirit/game/scripts/cards/SWSH4/Mudsdale_97.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per + + +def _defender_retreat_cost(ctx) -> int: + defender = ctx.defender + return defender.get_attribute(AttrID.RETREAT_COST, 0) if defender else 0 + card = PokemonCardDef( guid="c73632dd-a3fc-55ae-8772-5aca7198a956", @@ -30,7 +37,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=180, damage_operator="-", - effect=unimplemented, + effect=damage_per(_defender_retreat_cost, -30, base=180), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Nessa_157.py b/spirit/game/scripts/cards/SWSH4/Nessa_157.py index 11a54b7..0b53e7b 100644 --- a/spirit/game/scripts/cards/SWSH4/Nessa_157.py +++ b/spirit/game/scripts/cards/SWSH4/Nessa_157.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import SupporterCardDef +from spirit.game.attributes import PokemonTypes, Rarities, AttrID +from spirit.game.card_effects.support_common import recover_from_discard, requires_discard, is_energy +from spirit.game.session.effects import is_water_pokemon + + +def is_water_energy_card(card) -> bool: + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_energy(card) and PokemonTypes.WATER.value in types + + +def _nessa_predicate(card) -> bool: + return is_water_pokemon(card) or is_water_energy_card(card) + card = SupporterCardDef( guid="ce33e499-3931-555b-a071-7aeac98ee575", @@ -11,5 +23,9 @@ card = SupporterCardDef( collector_number=157, set_code="SWSH4", rarity=Rarities.Uncommon, - effect=unimplemented + effect=recover_from_discard( + _nessa_predicate, count=4, minimum=1, reveal=False, to="hand", + prompt="Choose up to 4 Water Pokémon and Water Energy cards", + ), + condition=requires_discard(_nessa_predicate), ) diff --git a/spirit/game/scripts/cards/SWSH4/Nessa_183.py b/spirit/game/scripts/cards/SWSH4/Nessa_183.py index af5651b..6067eeb 100644 --- a/spirit/game/scripts/cards/SWSH4/Nessa_183.py +++ b/spirit/game/scripts/cards/SWSH4/Nessa_183.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import SupporterCardDef +from spirit.game.attributes import PokemonTypes, Rarities, AttrID +from spirit.game.card_effects.support_common import recover_from_discard, requires_discard, is_energy +from spirit.game.session.effects import is_water_pokemon + + +def is_water_energy_card(card) -> bool: + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_energy(card) and PokemonTypes.WATER.value in types + + +def _nessa_predicate(card) -> bool: + return is_water_pokemon(card) or is_water_energy_card(card) + card = SupporterCardDef( guid="baa29f88-0652-5957-bbee-7a5fe31933b6", @@ -11,5 +23,9 @@ card = SupporterCardDef( collector_number=183, set_code="SWSH4", rarity=Rarities.RareUltra, - effect=unimplemented + effect=recover_from_discard( + _nessa_predicate, count=4, minimum=1, reveal=False, to="hand", + prompt="Choose up to 4 Water Pokémon and Water Energy cards", + ), + condition=requires_discard(_nessa_predicate), ) diff --git a/spirit/game/scripts/cards/SWSH4/Nessa_196.py b/spirit/game/scripts/cards/SWSH4/Nessa_196.py index 5563a90..6eed62a 100644 --- a/spirit/game/scripts/cards/SWSH4/Nessa_196.py +++ b/spirit/game/scripts/cards/SWSH4/Nessa_196.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import SupporterCardDef +from spirit.game.attributes import PokemonTypes, Rarities, AttrID +from spirit.game.card_effects.support_common import recover_from_discard, requires_discard, is_energy +from spirit.game.session.effects import is_water_pokemon + + +def is_water_energy_card(card) -> bool: + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_energy(card) and PokemonTypes.WATER.value in types + + +def _nessa_predicate(card) -> bool: + return is_water_pokemon(card) or is_water_energy_card(card) + card = SupporterCardDef( guid="ddf92bd5-7b1b-5e1c-ac80-523d31eafe75", @@ -11,5 +23,9 @@ card = SupporterCardDef( collector_number=196, set_code="SWSH4", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=recover_from_discard( + _nessa_predicate, count=4, minimum=1, reveal=False, to="hand", + prompt="Choose up to 4 Water Pokémon and Water Energy cards", + ), + condition=requires_discard(_nessa_predicate), ) diff --git a/spirit/game/scripts/cards/SWSH4/Nincada_13.py b/spirit/game/scripts/cards/SWSH4/Nincada_13.py index e92edd9..a1f1e3d 100644 --- a/spirit/game/scripts/cards/SWSH4/Nincada_13.py +++ b/spirit/game/scripts/cards/SWSH4/Nincada_13.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import heal_attack card = PokemonCardDef( guid="80ec2684-9d73-5fbb-a274-d243ed732493", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Heal 10 damage from this Pok\u00e9mon.", cost={PokemonTypes.GRASS: 1}, damage=10, - effect=unimplemented, + effect=heal_attack(10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Ninjask_14.py b/spirit/game/scripts/cards/SWSH4/Ninjask_14.py index b161c47..89fa184 100644 --- a/spirit/game/scripts/cards/SWSH4/Ninjask_14.py +++ b/spirit/game/scripts/cards/SWSH4/Ninjask_14.py @@ -1,5 +1,24 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers, def_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import heal_attack + + +def _is_shedinja(card) -> bool: + d = def_for(card.archetype_id) + return bool(d) and d.display_name == "Shedinja" + + +async def cast_off_shell(ctx): + if not await ctx.ask_yes_no("Search your deck for Shedinja and put it onto your Bench?"): + return + picks = await ctx.search_deck( + _is_shedinja, count=1, minimum=0, + prompt="Choose Shedinja to put onto your Bench.", + ) + for card in picks: + await ctx.bench_pokemon(card) + await ctx.shuffle_deck() + card = PokemonCardDef( guid="f10e4d9e-cf0d-59cd-bb18-d99a717b4db3", @@ -22,14 +41,15 @@ card = PokemonCardDef( Ability( title="Cast-off Shell", game_text="When you play this Pok\u00e9mon from your hand to evolve 1 of your Pok\u00e9mon, you may search your deck for Shedinja and put it onto your Bench. Shuffle your deck afterward.", - effect=unimplemented, + trigger=Triggers.ON_EVOLVE, + effect=cast_off_shell, ), Attack( title="Absorb", game_text="Heal 30 damage from this Pok\u00e9mon.", cost={PokemonTypes.GRASS: 1}, damage=30, - effect=unimplemented, + effect=heal_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Opal_158.py b/spirit/game/scripts/cards/SWSH4/Opal_158.py index 516e6fa..06fab55 100644 --- a/spirit/game/scripts/cards/SWSH4/Opal_158.py +++ b/spirit/game/scripts/cards/SWSH4/Opal_158.py @@ -1,6 +1,20 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities + +async def opal_effect(ctx): + """Flip 2 coins. Search your deck for a number of cards up to the + number of heads, put them into your hand, and shuffle your deck.""" + heads = await ctx.flip_coins(2, "Opal") + count = heads.count(True) + if count > 0: + picks = await ctx.search_deck( + count=count, minimum=0, prompt=f"Choose up to {count} cards", + ) + await ctx.put_in_hand(picks, reveal=False) + await ctx.shuffle_deck() + + card = SupporterCardDef( guid="4dbeb162-8609-5bc0-9660-a434fe723a81", key="SWSH4", @@ -11,5 +25,5 @@ card = SupporterCardDef( collector_number=158, set_code="SWSH4", rarity=Rarities.Uncommon, - effect=unimplemented + effect=opal_effect, ) diff --git a/spirit/game/scripts/cards/SWSH4/Opal_184.py b/spirit/game/scripts/cards/SWSH4/Opal_184.py index 36ecc9d..e61993e 100644 --- a/spirit/game/scripts/cards/SWSH4/Opal_184.py +++ b/spirit/game/scripts/cards/SWSH4/Opal_184.py @@ -1,6 +1,20 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities + +async def opal_effect(ctx): + """Flip 2 coins. Search your deck for a number of cards up to the + number of heads, put them into your hand, and shuffle your deck.""" + heads = await ctx.flip_coins(2, "Opal") + count = heads.count(True) + if count > 0: + picks = await ctx.search_deck( + count=count, minimum=0, prompt=f"Choose up to {count} cards", + ) + await ctx.put_in_hand(picks, reveal=False) + await ctx.shuffle_deck() + + card = SupporterCardDef( guid="12ed9205-82f1-5923-b1a9-b9e5005e891a", key="SWSH4", @@ -11,5 +25,5 @@ card = SupporterCardDef( collector_number=184, set_code="SWSH4", rarity=Rarities.RareUltra, - effect=unimplemented + effect=opal_effect, ) diff --git a/spirit/game/scripts/cards/SWSH4/Opal_197.py b/spirit/game/scripts/cards/SWSH4/Opal_197.py index ee3501e..eb2ad0c 100644 --- a/spirit/game/scripts/cards/SWSH4/Opal_197.py +++ b/spirit/game/scripts/cards/SWSH4/Opal_197.py @@ -1,6 +1,20 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities + +async def opal_effect(ctx): + """Flip 2 coins. Search your deck for a number of cards up to the + number of heads, put them into your hand, and shuffle your deck.""" + heads = await ctx.flip_coins(2, "Opal") + count = heads.count(True) + if count > 0: + picks = await ctx.search_deck( + count=count, minimum=0, prompt=f"Choose up to {count} cards", + ) + await ctx.put_in_hand(picks, reveal=False) + await ctx.shuffle_deck() + + card = SupporterCardDef( guid="492d90a6-8ce4-58cc-9391-1ae466724108", key="SWSH4", @@ -11,5 +25,5 @@ card = SupporterCardDef( collector_number=197, set_code="SWSH4", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=opal_effect, ) diff --git a/spirit/game/scripts/cards/SWSH4/Oranguru_199.py b/spirit/game/scripts/cards/SWSH4/Oranguru_199.py index 32784ac..5e0b3a0 100644 --- a/spirit/game/scripts/cards/SWSH4/Oranguru_199.py +++ b/spirit/game/scripts/cards/SWSH4/Oranguru_199.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.pokemon import primate_wisdom +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -21,7 +22,8 @@ card = PokemonCardDef( Ability( title="Primate Wisdom", game_text="Once during your turn, you may switch a card from your hand with the top card of your deck.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + effect=primate_wisdom, ), Attack( title="Whap Down", diff --git a/spirit/game/scripts/cards/SWSH4/OrbeetleVMAX_186.py b/spirit/game/scripts/cards/SWSH4/OrbeetleVMAX_186.py index 0ee3ea3..cbea2e1 100644 --- a/spirit/game/scripts/cards/SWSH4/OrbeetleVMAX_186.py +++ b/spirit/game/scripts/cards/SWSH4/OrbeetleVMAX_186.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import in_active_spot +from spirit.game.card_effects.attacks_common import damage_per, count_energy + + +async def eerie_beam(ctx): + """Put 1 damage counter on each of your opponent's Pokemon.""" + for pokemon in ctx.opponent_pokemon_in_play(): + await ctx.deal_damage(10, target=pokemon, as_counters=True) + card = PokemonCardDef( guid="e6f2053a-06c1-5223-a47b-b59a87d1a325", @@ -22,7 +31,9 @@ card = PokemonCardDef( Ability( title="Eerie Beam", game_text="Once during your turn, if this Pok\u00e9mon is in the Active Spot, you may put 1 damage counter on each of your opponent's Pok\u00e9mon.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=in_active_spot, + effect=eerie_beam, ), Attack( title="G-Max Wave", @@ -30,7 +41,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=50, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("defender"), 50, base=50), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/OrbeetleVMAX_21.py b/spirit/game/scripts/cards/SWSH4/OrbeetleVMAX_21.py index fc16fc1..88854f1 100644 --- a/spirit/game/scripts/cards/SWSH4/OrbeetleVMAX_21.py +++ b/spirit/game/scripts/cards/SWSH4/OrbeetleVMAX_21.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import in_active_spot +from spirit.game.card_effects.attacks_common import damage_per, count_energy + + +async def eerie_beam(ctx): + """Put 1 damage counter on each of your opponent's Pokemon.""" + for pokemon in ctx.opponent_pokemon_in_play(): + await ctx.deal_damage(10, target=pokemon, as_counters=True) + card = PokemonCardDef( guid="b21e232b-f85b-590f-9982-adeb5cad7d25", @@ -22,7 +31,9 @@ card = PokemonCardDef( Ability( title="Eerie Beam", game_text="Once during your turn, if this Pok\u00e9mon is in the Active Spot, you may put 1 damage counter on each of your opponent's Pok\u00e9mon.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=in_active_spot, + effect=eerie_beam, ), Attack( title="G-Max Wave", @@ -30,7 +41,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=50, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("defender"), 50, base=50), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/OrbeetleV_166.py b/spirit/game/scripts/cards/SWSH4/OrbeetleV_166.py index 4fe78c3..e68ae1b 100644 --- a/spirit/game/scripts/cards/SWSH4/OrbeetleV_166.py +++ b/spirit/game/scripts/cards/SWSH4/OrbeetleV_166.py @@ -1,4 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import count_energy, damage_per +from spirit.game.card_effects.support_common import switch_self_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -23,7 +25,7 @@ card = PokemonCardDef( game_text="You may switch this Pok\u00e9mon with 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.GRASS: 1}, damage=20, - effect=unimplemented, + effect=switch_self_attack(optional=True), ), Attack( title="Mysterious Wave", @@ -31,7 +33,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=50, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("defender"), 30, base=50), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/OrbeetleV_20.py b/spirit/game/scripts/cards/SWSH4/OrbeetleV_20.py index 07e4a96..fc5a656 100644 --- a/spirit/game/scripts/cards/SWSH4/OrbeetleV_20.py +++ b/spirit/game/scripts/cards/SWSH4/OrbeetleV_20.py @@ -1,4 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import count_energy, damage_per +from spirit.game.card_effects.support_common import switch_self_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -23,7 +25,7 @@ card = PokemonCardDef( game_text="You may switch this Pok\u00e9mon with 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.GRASS: 1}, damage=20, - effect=unimplemented, + effect=switch_self_attack(optional=True), ), Attack( title="Mysterious Wave", @@ -31,7 +33,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=50, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("defender"), 30, base=50), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Phanpy_86.py b/spirit/game/scripts/cards/SWSH4/Phanpy_86.py index 4ade954..0ed4e37 100644 --- a/spirit/game/scripts/cards/SWSH4/Phanpy_86.py +++ b/spirit/game/scripts/cards/SWSH4/Phanpy_86.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, damage_counters_on card = PokemonCardDef( guid="094b3a84-1358-5f2e-bb47-1981f96634b0", @@ -29,7 +30,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 1}, damage=30, damage_operator="x", - effect=unimplemented, + effect=damage_per(damage_counters_on("self"), 30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/PikachuVMAX_188.py b/spirit/game/scripts/cards/SWSH4/PikachuVMAX_188.py index 8f38572..20f1287 100644 --- a/spirit/game/scripts/cards/SWSH4/PikachuVMAX_188.py +++ b/spirit/game/scripts/cards/SWSH4/PikachuVMAX_188.py @@ -1,6 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def g_max_volt_tackle(ctx): + energies = ctx.attached_energies(ctx.attacker) + if energies and await ctx.ask_yes_no( + "Discard all Energy from this Pokémon? If you do, this attack does 150 more damage." + ): + await ctx.discard_energy_from(ctx.attacker, 99) + await ctx.deal_damage(270) + else: + await ctx.deal_damage(120) + + card = PokemonCardDef( guid="d329c902-ea8a-567e-98ba-e38f005a42cc", key="SWSH4", @@ -25,7 +37,7 @@ card = PokemonCardDef( cost={PokemonTypes.LIGHTNING: 3}, damage=120, damage_operator="+", - effect=unimplemented, + effect=g_max_volt_tackle, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/PikachuVMAX_44.py b/spirit/game/scripts/cards/SWSH4/PikachuVMAX_44.py index 81c4c67..661f800 100644 --- a/spirit/game/scripts/cards/SWSH4/PikachuVMAX_44.py +++ b/spirit/game/scripts/cards/SWSH4/PikachuVMAX_44.py @@ -1,6 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def g_max_volt_tackle(ctx): + energies = ctx.attached_energies(ctx.attacker) + if energies and await ctx.ask_yes_no( + "Discard all Energy from this Pokémon? If you do, this attack does 150 more damage." + ): + await ctx.discard_energy_from(ctx.attacker, 99) + await ctx.deal_damage(270) + else: + await ctx.deal_damage(120) + + card = PokemonCardDef( guid="941b5fbc-9f1f-5760-8b8f-601aadf968a8", key="SWSH4", @@ -25,7 +37,7 @@ card = PokemonCardDef( cost={PokemonTypes.LIGHTNING: 3}, damage=120, damage_operator="+", - effect=unimplemented, + effect=g_max_volt_tackle, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/PikachuV_170.py b/spirit/game/scripts/cards/SWSH4/PikachuV_170.py index b86397b..0595a97 100644 --- a/spirit/game/scripts/cards/SWSH4/PikachuV_170.py +++ b/spirit/game/scripts/cards/SWSH4/PikachuV_170.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import is_lightning_energy +from spirit.game.card_effects.support_common import search_attach_energy +from spirit.game.card_effects.attacks_common import self_energy_discard_attack card = PokemonCardDef( guid="cd9ffbf2-f0d0-5f0b-8ed7-f0768558a63d", @@ -20,16 +23,16 @@ card = PokemonCardDef( abilities=[ Attack( title="Charge", - game_text="Search your deck for up to 2 Lightning Energy cards and attach them to this Pok\u00e9mon. Then, shuffle your deck.", + game_text="Search your deck for up to 2 Lightning Energy cards and attach them to this Pokémon. Then, shuffle your deck.", cost={PokemonTypes.LIGHTNING: 1}, - effect=unimplemented, + effect=search_attach_energy(predicate=is_lightning_energy, count=2, to_self=True), ), Attack( title="Thunderbolt", - game_text="Discard all Energy from this Pok\u00e9mon.", + game_text="Discard all Energy from this Pokémon.", cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 1}, damage=200, - effect=unimplemented, + effect=self_energy_discard_attack(all_energy=True), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH4/PikachuV_43.py b/spirit/game/scripts/cards/SWSH4/PikachuV_43.py index f8cb041..2a0d019 100644 --- a/spirit/game/scripts/cards/SWSH4/PikachuV_43.py +++ b/spirit/game/scripts/cards/SWSH4/PikachuV_43.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import is_lightning_energy +from spirit.game.card_effects.support_common import search_attach_energy +from spirit.game.card_effects.attacks_common import self_energy_discard_attack card = PokemonCardDef( guid="e7fe2c2e-4286-5b79-a956-392b78dcea66", @@ -20,16 +23,16 @@ card = PokemonCardDef( abilities=[ Attack( title="Charge", - game_text="Search your deck for up to 2 Lightning Energy cards and attach them to this Pok\u00e9mon. Then, shuffle your deck.", + game_text="Search your deck for up to 2 Lightning Energy cards and attach them to this Pokémon. Then, shuffle your deck.", cost={PokemonTypes.LIGHTNING: 1}, - effect=unimplemented, + effect=search_attach_energy(predicate=is_lightning_energy, count=2, to_self=True), ), Attack( title="Thunderbolt", - game_text="Discard all Energy from this Pok\u00e9mon.", + game_text="Discard all Energy from this Pokémon.", cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 1}, damage=200, - effect=unimplemented, + effect=self_energy_discard_attack(all_energy=True), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH4/Pincurchin_62.py b/spirit/game/scripts/cards/SWSH4/Pincurchin_62.py index e72d200..caa4b88 100644 --- a/spirit/game/scripts/cards/SWSH4/Pincurchin_62.py +++ b/spirit/game/scripts/cards/SWSH4/Pincurchin_62.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_all_opponents card = PokemonCardDef( guid="810e5a63-b8c5-535b-bb44-d3be6e35cdea", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Spinning Fan", game_text="This attack does 10 damage to each of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.LIGHTNING: 1}, - effect=unimplemented, + effect=damage_all_opponents(10), ), Attack( title="Peck", diff --git a/spirit/game/scripts/cards/SWSH4/Pineco_8.py b/spirit/game/scripts/cards/SWSH4/Pineco_8.py index ffb905b..06ad7a0 100644 --- a/spirit/game/scripts/cards/SWSH4/Pineco_8.py +++ b/spirit/game/scripts/cards/SWSH4/Pineco_8.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import flip_protection card = PokemonCardDef( guid="c2c9e53a-6400-5021-b4bc-d638d49774e1", @@ -27,7 +28,7 @@ card = PokemonCardDef( title="Iron Defense", game_text="Flip a coin. If heads, during your opponent's next turn, prevent all damage done to this Pok\u00e9mon by attacks.", cost={PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + effect=flip_protection(prevent=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Quagsire_84.py b/spirit/game/scripts/cards/SWSH4/Quagsire_84.py index 1e02a06..518fea1 100644 --- a/spirit/game/scripts/cards/SWSH4/Quagsire_84.py +++ b/spirit/game/scripts/cards/SWSH4/Quagsire_84.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import count_prizes_remaining + + +async def fickle_impact(ctx): + """180. Does nothing if you have exactly 2, 4, or 6 Prize cards remaining.""" + if count_prizes_remaining("mine")(ctx) in (2, 4, 6): + return + await ctx.deal_damage() + card = PokemonCardDef( guid="94ddfcca-a01b-52d2-bd80-5db0c2500c03", @@ -29,7 +38,7 @@ card = PokemonCardDef( game_text="If you have exactly 2, 4, or 6 Prize cards remaining, this attack does nothing.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 2}, damage=180, - effect=unimplemented, + effect=fickle_impact, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Rayquaza_138.py b/spirit/game/scripts/cards/SWSH4/Rayquaza_138.py index e1d6333..d877c14 100644 --- a/spirit/game/scripts/cards/SWSH4/Rayquaza_138.py +++ b/spirit/game/scripts/cards/SWSH4/Rayquaza_138.py @@ -1,5 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.trainers import is_basic_energy_card + + +async def amazing_burst(ctx): + """Discard all basic Energy from this Pokemon; 80 damage per TYPE discarded.""" + picks = await ctx.discard_energy_from( + ctx.attacker, 99, predicate=is_basic_energy_card, + prompt="Discard all Basic Energy from this Pokémon.", + ) + types = set() + for card in picks: + for t in (card.get_attribute(AttrID.POKEMON_TYPES) or []): + types.add(t) + await ctx.deal_damage(80 * len(types)) + card = PokemonCardDef( guid="69e23074-b5cb-5986-b844-6de666c4c606", @@ -25,7 +40,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.LIGHTNING: 1, PokemonTypes.FIGHTING: 1}, damage=80, damage_operator="x", - effect=unimplemented, + effect=amazing_burst, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Regirock_89.py b/spirit/game/scripts/cards/SWSH4/Regirock_89.py index c5cda03..b537ef1 100644 --- a/spirit/game/scripts/cards/SWSH4/Regirock_89.py +++ b/spirit/game/scripts/cards/SWSH4/Regirock_89.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack + + +async def _rock_tumble(ctx): + """This attack's damage isn't affected by Resistance.""" + await ctx.deal_damage(ignore_resistance=True) + card = PokemonCardDef( guid="06702ba8-a614-5283-8576-81a88ce7166e", @@ -23,14 +30,14 @@ card = PokemonCardDef( game_text="This attack's damage isn't affected by Resistance.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 1}, damage=40, - effect=unimplemented, + effect=_rock_tumble, ), Attack( title="Megaton Fall", game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=130, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Riolu_90.py b/spirit/game/scripts/cards/SWSH4/Riolu_90.py index 3586d6c..ebee43b 100644 --- a/spirit/game/scripts/cards/SWSH4/Riolu_90.py +++ b/spirit/game/scripts/cards/SWSH4/Riolu_90.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_or_nothing card = PokemonCardDef( guid="7d431f3b-f1d3-5968-8a0f-7717292134e6", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Flip a coin. If tails, this attack does nothing.", cost={PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=flip_or_nothing(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/RockyHelmet_159.py b/spirit/game/scripts/cards/SWSH4/RockyHelmet_159.py index 686ea26..75d85c4 100644 --- a/spirit/game/scripts/cards/SWSH4/RockyHelmet_159.py +++ b/spirit/game/scripts/cards/SWSH4/RockyHelmet_159.py @@ -1,15 +1,34 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented +from spirit.game.data_utils import PokemonToolCardDef, Ability, Triggers from spirit.game.attributes import Rarities +from spirit.game.card_effects.passives_common import is_in_active_spot + + +async def _rocky_helmet_trigger(ctx): + pokemon = ctx.source + if not is_in_active_spot(pokemon): + return + attacker = ctx.damaged_by + if attacker is None or attacker.owning_player_id == pokemon.owning_player_id: + return + await ctx.deal_damage(20, target=attacker, apply_modifiers=False, as_counters=True) + card = PokemonToolCardDef( guid="0bd49787-742b-52b6-ae1c-a3bc958c2f34", key="SWSH4", name="com.direwolfdigital.cake.data.archetypes.trainer.RockyHelmet.Name", display_name="Rocky Helmet", - searchable_by=["Rocky Helmet", "Pok\u00c3\u00a9mon Tool"], - subtypes=["Pok\u00e9mon Tool"], + searchable_by=["Rocky Helmet", "Pokémon Tool"], + subtypes=["Pokémon Tool"], collector_number=159, set_code="SWSH4", rarity=Rarities.Uncommon, - effect=unimplemented + granted_abilities=[ + Ability( + title="Rocky Helmet", + game_text="If the Pokémon this card is attached to is in the Active Spot and is damaged by an attack from your opponent's Pokémon (even if it is Knocked Out), put 2 damage counters on the Attacking Pokémon.", + trigger=Triggers.ON_DAMAGED_BY_ATTACK, + effect=_rocky_helmet_trigger, + ), + ], ) diff --git a/spirit/game/scripts/cards/SWSH4/Sableye_105.py b/spirit/game/scripts/cards/SWSH4/Sableye_105.py index 19ae295..dde1644 100644 --- a/spirit/game/scripts/cards/SWSH4/Sableye_105.py +++ b/spirit/game/scripts/cards/SWSH4/Sableye_105.py @@ -1,5 +1,26 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import draw_attack + + +async def torment(ctx): + """30 damage. Choose 1 of the opponent's Active Pokemon's attacks; it + can't be used during their next turn.""" + await ctx.deal_damage() + defender = ctx.defender + if defender is None or ctx.effects_blocked(defender): + return + entries = [e for e in (defender.get_attribute(AttrID.PIE_ABILITIES) or []) + if isinstance(e, dict) and e.get("abilityType") == "Attack" + and e.get("abilityID")] + if not entries: + return + titles = [e.get("title", {}).get("id", "Attack") for e in entries] + idx = await ctx.choose("Choose an attack to lock", titles) + entry = entries[idx] + ctx.session.turn_state.attack_locks[(defender.entity_id, entry["abilityID"])] = \ + ctx.session.turn_state.turn_number + 1 + card = PokemonCardDef( guid="52861551-aac3-5659-a5f6-df39484b10d8", @@ -22,14 +43,14 @@ card = PokemonCardDef( title="Filch", game_text="Draw a card.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=draw_attack(1), ), Attack( title="Torment", game_text="Choose 1 of your opponent's Active Pok\u00e9mon's attacks. During your opponent's next turn, that Pok\u00e9mon can't use that attack.", cost={PokemonTypes.COLORLESS: 2}, damage=30, - effect=unimplemented, + effect=torment, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Samurott_35.py b/spirit/game/scripts/cards/SWSH4/Samurott_35.py index 3cd2bc2..32a9e2c 100644 --- a/spirit/game/scripts/cards/SWSH4/Samurott_35.py +++ b/spirit/game/scripts/cards/SWSH4/Samurott_35.py @@ -1,6 +1,28 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.passives_common import takes_less_passive +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def aqua_wash(ctx): + """120 damage; you may put 2 Energy from the opponent's Active into their hand.""" + await ctx.deal_damage() + defender = ctx.defender + if defender is None or ctx.effects_blocked(defender): + return + energies = ctx.attached_energies(defender) + if not energies: + return + if not await ctx.ask_yes_no( + "Put 2 Energy attached to your opponent's Active Pokémon into their hand?" + ): + return + picks = await ctx.choose_cards( + energies, min(2, len(energies)), minimum=1, + prompt="Choose Energy to put into your opponent's hand.", + ) + await ctx.put_in_hand(picks, reveal=False) + + card = PokemonCardDef( guid="5e510fa4-522a-553f-906f-2eb5ff40ca67", key="SWSH4", @@ -22,14 +44,14 @@ card = PokemonCardDef( Ability( title="Swaddling Leaves", game_text="This Pok\u00e9mon takes 30 less damage from attacks (after applying Weakness and Resistance).", - effect=unimplemented, + passive=takes_less_passive(30), ), Attack( title="Aqua Wash", game_text="You may put 2 Energy attached to your opponent's Active Pok\u00e9mon into their hand.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=120, - effect=unimplemented, + effect=aqua_wash, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Sandile_107.py b/spirit/game/scripts/cards/SWSH4/Sandile_107.py index a28e01b..df6f6b2 100644 --- a/spirit/game/scripts/cards/SWSH4/Sandile_107.py +++ b/spirit/game/scripts/cards/SWSH4/Sandile_107.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import mill_attack card = PokemonCardDef( guid="3a7dce38-afce-55cf-a1b4-23e337fab2ba", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Dredge Up", game_text="Discard the top 3 cards of your opponent's deck.", cost={PokemonTypes.COLORLESS: 4}, - effect=unimplemented, + effect=mill_attack(3), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Shaymin_15.py b/spirit/game/scripts/cards/SWSH4/Shaymin_15.py index 893e0ad..fb9a33f 100644 --- a/spirit/game/scripts/cards/SWSH4/Shaymin_15.py +++ b/spirit/game/scripts/cards/SWSH4/Shaymin_15.py @@ -1,5 +1,24 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import heal_attack +from spirit.game.session.effects import full_stack + + +async def flower_bearing(ctx): + heads = (await ctx.flip_coins(1, "Flower Bearing"))[0] + if not heads: + return + target = ctx.opponent_active() + if target is None or ctx.effects_blocked(target): + return + await ctx.shuffle_into_deck(full_stack(target), ctx.opponent_id) + + async def _promote(): + if not await ctx.session._promote_new_active(ctx.opponent_id): + screen_name = ctx.session.players[ctx.opponent_id].screen_name + await ctx.session.end_game( + ctx.player_id, f"{screen_name} has no Pokémon left") + ctx.deferred_actions.append(_promote) card = PokemonCardDef( guid="92f69263-b590-5caf-96ce-aeeedec15a9c", @@ -23,13 +42,13 @@ card = PokemonCardDef( game_text="Heal 20 damage from this Pok\u00e9mon.", cost={PokemonTypes.GRASS: 1}, damage=20, - effect=unimplemented, + effect=heal_attack(20), ), Attack( title="Flower Bearing", game_text="Flip a coin. If heads, your opponent shuffles their Active Pok\u00e9mon and all attached cards and puts them on the bottom of their deck.", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=flower_bearing, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Shedinja_66.py b/spirit/game/scripts/cards/SWSH4/Shedinja_66.py index 8305730..1763695 100644 --- a/spirit/game/scripts/cards/SWSH4/Shedinja_66.py +++ b/spirit/game/scripts/cards/SWSH4/Shedinja_66.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID + + +async def life_squeeze(ctx): + """Put damage counters on the opponent's Active until its remaining HP is 10.""" + target = ctx.defender + if target is None: + return + current = target.get_attribute(AttrID.HP, 0) + amount = current - 10 + if amount > 0: + await ctx.deal_damage(amount, target=target, as_counters=True) + card = PokemonCardDef( guid="436e4ef7-954e-5bec-b1ab-763ac896275e", @@ -16,17 +28,17 @@ card = PokemonCardDef( stage=PokemonStage.BASIC, retreat_cost=1, family_id=292, + unplayable_from_hand=True, abilities=[ Ability( title="Shell Survival", game_text="Put this Pok\u00e9mon into play only with the effect of Ninjask's Cast-Off Shell Ability. (When you are setting up to play, you cannot put it face down as your Active Pok\u00e9mon or on your Bench.)", - effect=unimplemented, ), Attack( title="Life Squeeze", game_text="Put damage counters on your opponent's Active Pok\u00e9mon until its remaining HP is 10.", cost={PokemonTypes.COLORLESS: 3}, - effect=unimplemented, + effect=life_squeeze, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Shiftry_12.py b/spirit/game/scripts/cards/SWSH4/Shiftry_12.py index 7d1758d..49090b8 100644 --- a/spirit/game/scripts/cards/SWSH4/Shiftry_12.py +++ b/spirit/game/scripts/cards/SWSH4/Shiftry_12.py @@ -1,5 +1,23 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import opponent_switches +from spirit.game.card_effects.passives_common import replace_opponent_supporters + + +async def fan_tornado(ctx): + """110. You may have your opponent switch their Active with 1 of their Benched Pokemon.""" + await ctx.deal_damage() + if not ctx.opponent_bench(): + return + if await ctx.ask_yes_no( + "Have your opponent switch their Active Pokémon with 1 of their Benched Pokémon?"): + await opponent_switches(ctx) + + +async def _draw_3(ctx): + """Replacement effect: opponent's Supporters draw 3 cards instead.""" + await ctx.draw_cards(3) + card = PokemonCardDef( guid="9a8136c0-646b-53cf-a5eb-b5805dc2fbc6", @@ -22,14 +40,14 @@ card = PokemonCardDef( Ability( title="Shifty Substitution", game_text="As long as this Pok\u00e9mon is in the Active Spot, each Supporter card in your opponent's hand has the effect \"Draw 3 cards.\" (This happens instead of the card's usual effect.)", - effect=unimplemented, + passive=replace_opponent_supporters(_draw_3), ), Attack( title="Fan Tornado", game_text="You may have your opponent switch their Active Pok\u00e9mon with 1 of their Benched Pok\u00e9mon.", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=110, - effect=unimplemented, + effect=fan_tornado, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Shuckle_85.py b/spirit/game/scripts/cards/SWSH4/Shuckle_85.py index ee1559d..470d5da 100644 --- a/spirit/game/scripts/cards/SWSH4/Shuckle_85.py +++ b/spirit/game/scripts/cards/SWSH4/Shuckle_85.py @@ -1,6 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def deck_distiller(ctx): + heads = await ctx.flip_until_tails("Deck Distiller") + if heads <= 0: + return + top_cards = ctx.deck_top(heads, ctx.opponent_id) + if top_cards: + await ctx.discard_cards(top_cards) + card = PokemonCardDef( guid="227f3371-a80d-5801-9dd9-7fce3191efe5", key="SWSH4", @@ -22,7 +31,7 @@ card = PokemonCardDef( title="Deck Distiller", game_text="Flip a coin until you get tails. For each heads, discard the top card of your opponent's deck.", cost={PokemonTypes.FIGHTING: 1}, - effect=unimplemented, + effect=deck_distiller, ), Attack( title="Rock Throw", diff --git a/spirit/game/scripts/cards/SWSH4/Shuppet_67.py b/spirit/game/scripts/cards/SWSH4/Shuppet_67.py index b88778e..2047c07 100644 --- a/spirit/game/scripts/cards/SWSH4/Shuppet_67.py +++ b/spirit/game/scripts/cards/SWSH4/Shuppet_67.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import place_counters card = PokemonCardDef( guid="3032b436-9061-5c24-97b4-4a14fbb3d6cd", @@ -23,7 +24,7 @@ card = PokemonCardDef( title="Haunt", game_text="Put 1 damage counter on your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=place_counters(1, "opponent_active"), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Skiddo_17.py b/spirit/game/scripts/cards/SWSH4/Skiddo_17.py index 4fc7f6b..db59dbb 100644 --- a/spirit/game/scripts/cards/SWSH4/Skiddo_17.py +++ b/spirit/game/scripts/cards/SWSH4/Skiddo_17.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_attach_energy +from spirit.game.card_effects.pokemon import is_energy_card, energy_provides_type + + +def _is_grass_energy(card): + return is_energy_card(card) and energy_provides_type(card, PokemonTypes.GRASS.value) + + +synthesis = search_attach_energy( + predicate=_is_grass_energy, count=1, to_self=False, + prompt="Choose a Grass Energy card to attach.", +) + card = PokemonCardDef( guid="7cd86cd1-62e0-5af1-b4c7-208ed4449b12", @@ -22,7 +35,7 @@ card = PokemonCardDef( title="Synthesis", game_text="Search your deck for a Grass Energy card and attach it to 1 of your Pok\u00e9mon. Then, shuffle your deck.", cost={PokemonTypes.GRASS: 1}, - effect=unimplemented, + effect=synthesis, ), Attack( title="Razor Leaf", diff --git a/spirit/game/scripts/cards/SWSH4/Slugma_27.py b/spirit/game/scripts/cards/SWSH4/Slugma_27.py index 6ae4928..bc141d6 100644 --- a/spirit/game/scripts/cards/SWSH4/Slugma_27.py +++ b/spirit/game/scripts/cards/SWSH4/Slugma_27.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="52160c94-c018-5a54-aefc-a8bfd69afd86", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Yawn", game_text="Your opponent's Active Pok\u00e9mon is now Asleep.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=condition_attack(SpecialConditions.ASLEEP), ), Attack( title="Flare", diff --git a/spirit/game/scripts/cards/SWSH4/Snorlax_131.py b/spirit/game/scripts/cards/SWSH4/Snorlax_131.py index 928ff58..3f63801 100644 --- a/spirit/game/scripts/cards/SWSH4/Snorlax_131.py +++ b/spirit/game/scripts/cards/SWSH4/Snorlax_131.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.pokemon import in_active_spot + + +async def gormandize(ctx): + if await ctx.ask_yes_no("Draw cards until you have 7 cards in your hand?"): + await ctx.draw_until(7) card = PokemonCardDef( guid="0a8d2e58-3006-5938-9f0e-5e3ff22b3185", @@ -21,14 +28,17 @@ card = PokemonCardDef( Ability( title="Gormandize", game_text="Once during your turn, if this Pok\u00e9mon is in the Active Spot, you may draw cards until you have 7 cards in your hand. If you use this Ability, your turn ends.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=in_active_spot, + ends_turn=True, + effect=gormandize, ), Attack( title="Body Slam", game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.COLORLESS: 4}, damage=100, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/SteelixV_115.py b/spirit/game/scripts/cards/SWSH4/SteelixV_115.py index 5a7b8f2..1cb6f49 100644 --- a/spirit/game/scripts/cards/SWSH4/SteelixV_115.py +++ b/spirit/game/scripts/cards/SWSH4/SteelixV_115.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, damage_counters_on, recoil_attack card = PokemonCardDef( guid="48c48a83-8d29-5153-abdb-309f4776602e", @@ -25,14 +26,14 @@ card = PokemonCardDef( cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 1}, damage=30, damage_operator="+", - effect=unimplemented, + effect=damage_per(damage_counters_on("self"), 10, base=30), ), Attack( title="Iron Tackle", game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 4}, damage=210, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/SteelixV_176.py b/spirit/game/scripts/cards/SWSH4/SteelixV_176.py index 19079f6..6e37950 100644 --- a/spirit/game/scripts/cards/SWSH4/SteelixV_176.py +++ b/spirit/game/scripts/cards/SWSH4/SteelixV_176.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, damage_counters_on, recoil_attack card = PokemonCardDef( guid="747509a6-bb64-564b-8ca4-8cc89a2de072", @@ -25,14 +26,14 @@ card = PokemonCardDef( cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 1}, damage=30, damage_operator="+", - effect=unimplemented, + effect=damage_per(damage_counters_on("self"), 10, base=30), ), Attack( title="Iron Tackle", game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 4}, damage=210, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Swellow_134.py b/spirit/game/scripts/cards/SWSH4/Swellow_134.py index 4e89ded..2e289f1 100644 --- a/spirit/game/scripts/cards/SWSH4/Swellow_134.py +++ b/spirit/game/scripts/cards/SWSH4/Swellow_134.py @@ -1,5 +1,9 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus +from spirit.game.card_effects.support_common import attach_from_discard +from spirit.game.card_effects.passives_common import is_in_active_spot +from spirit.game.card_effects.trainers import is_basic_energy_card card = PokemonCardDef( guid="c078095e-a649-5b18-b509-924d756ea6e1", @@ -26,14 +30,18 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(40), ), Attack( title="Energy Assist", game_text="Attach up to 2 basic Energy cards from your discard pile to 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 2}, damage=40, - effect=unimplemented, + effect=attach_from_discard( + predicate=is_basic_energy_card, count=2, minimum=0, + target=lambda p: not is_in_active_spot(p), + prompt="Choose up to 2 basic Energy cards to attach", + ), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Swoobat_74.py b/spirit/game/scripts/cards/SWSH4/Swoobat_74.py index 2a38512..889ecfa 100644 --- a/spirit/game/scripts/cards/SWSH4/Swoobat_74.py +++ b/spirit/game/scripts/cards/SWSH4/Swoobat_74.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import attack_effect_shield_passive card = PokemonCardDef( guid="084dbd4e-a047-5b48-af4e-1d83ac9a5721", @@ -23,7 +24,7 @@ card = PokemonCardDef( Ability( title="Unaware", game_text="Prevent all effects of your opponent's attacks, except damage, done to this Pok\u00e9mon.", - effect=unimplemented, + passive=attack_effect_shield_passive(), ), Attack( title="Heart Stamp", diff --git a/spirit/game/scripts/cards/SWSH4/Taillow_133.py b/spirit/game/scripts/cards/SWSH4/Taillow_133.py index e09910b..48c5b94 100644 --- a/spirit/game/scripts/cards/SWSH4/Taillow_133.py +++ b/spirit/game/scripts/cards/SWSH4/Taillow_133.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="da28882e-2779-5210-bd40-30b24cdd664e", @@ -25,7 +26,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=20, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=2, per_heads=20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/TalonflameV_168.py b/spirit/game/scripts/cards/SWSH4/TalonflameV_168.py index 5750ecb..b349af9 100644 --- a/spirit/game/scripts/cards/SWSH4/TalonflameV_168.py +++ b/spirit/game/scripts/cards/SWSH4/TalonflameV_168.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import discard_then_draw +from spirit.game.card_effects.attacks_common import self_energy_discard_attack card = PokemonCardDef( guid="c033b410-8ffe-587e-9ff0-60d5858dbf6e", @@ -23,14 +25,15 @@ card = PokemonCardDef( title="Fast Flight", game_text="If you go first, you can use this attack during your first turn. Discard your hand and draw 6 cards.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + usable_first_turn=True, + effect=discard_then_draw(None, 6, whole_hand=True), ), Attack( title="Bright Wing", game_text="Discard an Energy from this Pok\u00e9mon.", cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=160, - effect=unimplemented, + effect=self_energy_discard_attack(count=1), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/TalonflameV_29.py b/spirit/game/scripts/cards/SWSH4/TalonflameV_29.py index 71e4ab2..68baa36 100644 --- a/spirit/game/scripts/cards/SWSH4/TalonflameV_29.py +++ b/spirit/game/scripts/cards/SWSH4/TalonflameV_29.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import discard_then_draw +from spirit.game.card_effects.attacks_common import self_energy_discard_attack card = PokemonCardDef( guid="b5b4b149-6391-53d1-b89d-f438b14da123", @@ -23,14 +25,15 @@ card = PokemonCardDef( title="Fast Flight", game_text="If you go first, you can use this attack during your first turn. Discard your hand and draw 6 cards.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + usable_first_turn=True, + effect=discard_then_draw(None, 6, whole_hand=True), ), Attack( title="Bright Wing", game_text="Discard an Energy from this Pok\u00e9mon.", cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=160, - effect=unimplemented, + effect=self_energy_discard_attack(count=1), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/TelescopicSight_160.py b/spirit/game/scripts/cards/SWSH4/TelescopicSight_160.py index b754ef5..7638355 100644 --- a/spirit/game/scripts/cards/SWSH4/TelescopicSight_160.py +++ b/spirit/game/scripts/cards/SWSH4/TelescopicSight_160.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented +from spirit.game.data_utils import PokemonToolCardDef from spirit.game.attributes import Rarities +from spirit.game.session.passives import Passive, carrier_pokemon +from spirit.game.card_effects.trainers import is_v_or_gx + + +class TelescopicSightPassive(Passive): + """The holder's attacks do +30 to the opponent's Benched Pokemon V/GX.""" + + def modify_damage_dealt(self, calc, carrier): + if not (calc.is_attack and calc.is_opposing): + return + if carrier_pokemon(carrier) is not calc.attacker: + return + if calc.to_active: + return + if is_v_or_gx(calc.target.archetype_id): + calc.amount += 30 + card = PokemonToolCardDef( guid="a4225101-ea08-598f-9213-31cf21195a45", @@ -11,5 +28,5 @@ card = PokemonToolCardDef( collector_number=160, set_code="SWSH4", rarity=Rarities.Uncommon, - effect=unimplemented + passive=TelescopicSightPassive(), ) diff --git a/spirit/game/scripts/cards/SWSH4/TelescopicSight_203.py b/spirit/game/scripts/cards/SWSH4/TelescopicSight_203.py index e6f2974..ab6052f 100644 --- a/spirit/game/scripts/cards/SWSH4/TelescopicSight_203.py +++ b/spirit/game/scripts/cards/SWSH4/TelescopicSight_203.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented +from spirit.game.data_utils import PokemonToolCardDef from spirit.game.attributes import Rarities +from spirit.game.session.passives import Passive, carrier_pokemon +from spirit.game.card_effects.trainers import is_v_or_gx + + +class TelescopicSightPassive(Passive): + """The holder's attacks do +30 to the opponent's Benched Pokemon V/GX.""" + + def modify_damage_dealt(self, calc, carrier): + if not (calc.is_attack and calc.is_opposing): + return + if carrier_pokemon(carrier) is not calc.attacker: + return + if calc.to_active: + return + if is_v_or_gx(calc.target.archetype_id): + calc.amount += 30 + card = PokemonToolCardDef( guid="4ce1fc5e-e61e-576b-afe4-096179ffcad6", @@ -11,5 +28,5 @@ card = PokemonToolCardDef( collector_number=203, set_code="SWSH4", rarity=Rarities.RareSecret, - effect=unimplemented + passive=TelescopicSightPassive(), ) diff --git a/spirit/game/scripts/cards/SWSH4/Terrakion_92.py b/spirit/game/scripts/cards/SWSH4/Terrakion_92.py index 994c8d3..2b15cb8 100644 --- a/spirit/game/scripts/cards/SWSH4/Terrakion_92.py +++ b/spirit/game/scripts/cards/SWSH4/Terrakion_92.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if +from spirit.game.card_effects.passives_common import protect_next_turn card = PokemonCardDef( guid="b79ce289-339a-5d1a-bf74-fd30c6da22ba", @@ -23,7 +25,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, this Pok\u00e9mon takes 30 less damage from attacks (after applying Weakness and Resistance).", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=protect_next_turn(reduce=30), ), Attack( title="Earthen Power", @@ -31,7 +33,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=80, damage_operator="+", - effect=unimplemented, + effect=bonus_if(lambda ctx: ctx.stadium_in_play() is not None, 80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/TogekissVMAX_141.py b/spirit/game/scripts/cards/SWSH4/TogekissVMAX_141.py index 2078593..114c27f 100644 --- a/spirit/game/scripts/cards/SWSH4/TogekissVMAX_141.py +++ b/spirit/game/scripts/cards/SWSH4/TogekissVMAX_141.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_hand card = PokemonCardDef( guid="78588e27-cef4-52b1-b04d-2c3474ffb2a7", @@ -25,7 +26,7 @@ card = PokemonCardDef( game_text="You may search your deck for up to 2 cards and put them into your hand. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 2}, damage=120, - effect=unimplemented, + effect=search_to_hand(count=2, minimum=0, reveal=False), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/TogekissVMAX_191.py b/spirit/game/scripts/cards/SWSH4/TogekissVMAX_191.py index 752701b..0380711 100644 --- a/spirit/game/scripts/cards/SWSH4/TogekissVMAX_191.py +++ b/spirit/game/scripts/cards/SWSH4/TogekissVMAX_191.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_hand card = PokemonCardDef( guid="bb7bec72-6fbc-58a7-b6f8-8f6810e360a8", @@ -25,7 +26,7 @@ card = PokemonCardDef( game_text="You may search your deck for up to 2 cards and put them into your hand. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 2}, damage=120, - effect=unimplemented, + effect=search_to_hand(count=2, minimum=0, reveal=False), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/TogekissV_140.py b/spirit/game/scripts/cards/SWSH4/TogekissV_140.py index 5c05d75..20fcd09 100644 --- a/spirit/game/scripts/cards/SWSH4/TogekissV_140.py +++ b/spirit/game/scripts/cards/SWSH4/TogekissV_140.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, active_is +from spirit.game.session.effects import is_evolution_pokemon card = PokemonCardDef( guid="4a6f95d4-106c-5327-a59b-915801c50f58", @@ -25,7 +27,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=bonus_if(active_is(is_evolution_pokemon), 70), ), Attack( title="Speed Wing", diff --git a/spirit/game/scripts/cards/SWSH4/TogekissV_178.py b/spirit/game/scripts/cards/SWSH4/TogekissV_178.py index 7ac0944..d2f364f 100644 --- a/spirit/game/scripts/cards/SWSH4/TogekissV_178.py +++ b/spirit/game/scripts/cards/SWSH4/TogekissV_178.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, active_is +from spirit.game.session.effects import is_evolution_pokemon card = PokemonCardDef( guid="86c5fa2d-cac0-5a6b-b3ae-cda204e66c6b", @@ -25,7 +27,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=bonus_if(active_is(is_evolution_pokemon), 70), ), Attack( title="Speed Wing", diff --git a/spirit/game/scripts/cards/SWSH4/Tornadus_142.py b/spirit/game/scripts/cards/SWSH4/Tornadus_142.py index fdce501..0e3db70 100644 --- a/spirit/game/scripts/cards/SWSH4/Tornadus_142.py +++ b/spirit/game/scripts/cards/SWSH4/Tornadus_142.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import ( + discard_opponent_energy_attack, self_energy_discard_attack, +) + +jet_draft = discard_opponent_energy_attack(count=1, special_only=True) +air_slash = self_energy_discard_attack(count=1) card = PokemonCardDef( guid="fc885401-bf16-559d-8499-c7859f532971", @@ -24,14 +30,14 @@ card = PokemonCardDef( game_text="Discard a Special Energy from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=jet_draft, ), Attack( title="Air Slash", game_text="Discard an Energy from this Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 3}, damage=120, - effect=unimplemented, + effect=air_slash, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Toucannon_145.py b/spirit/game/scripts/cards/SWSH4/Toucannon_145.py index 9b098db..1cf3e54 100644 --- a/spirit/game/scripts/cards/SWSH4/Toucannon_145.py +++ b/spirit/game/scripts/cards/SWSH4/Toucannon_145.py @@ -1,5 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import discard_opponent_energy_attack + + +async def loop_cannon(ctx): + """160 damage. Put 2 Energy attached to this Pokémon into your hand.""" + await ctx.deal_damage() + attached = ctx.attached_energies(ctx.attacker) + if not attached: + return + picks = await ctx.choose_cards( + attached, min(2, len(attached)), + prompt="Choose Energy to put into your hand.", + ) + await ctx.put_in_hand(picks, reveal=False) + card = PokemonCardDef( guid="04604f09-8442-5980-bb14-52a847738fb7", @@ -25,14 +40,14 @@ card = PokemonCardDef( game_text="Discard an Energy from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 2}, damage=60, - effect=unimplemented, + effect=discard_opponent_energy_attack(count=1), ), Attack( title="Loop Cannon", game_text="Put 2 Energy attached to this Pok\u00e9mon into your hand.", cost={PokemonTypes.COLORLESS: 3}, damage=160, - effect=unimplemented, + effect=loop_cannon, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Trubbish_110.py b/spirit/game/scripts/cards/SWSH4/Trubbish_110.py index 1f88831..08a2819 100644 --- a/spirit/game/scripts/cards/SWSH4/Trubbish_110.py +++ b/spirit/game/scripts/cards/SWSH4/Trubbish_110.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.session.effects import is_item_card card = PokemonCardDef( guid="2ab639a5-3bf7-55de-a5b6-f8ca783b6b7a", @@ -22,7 +24,7 @@ card = PokemonCardDef( title="Lucky Find", game_text="Search your deck for an Item card, reveal it, and put it into your hand. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + effect=search_to_hand(is_item_card, count=1, minimum=0, reveal=True), ), Attack( title="Sludge Toss", diff --git a/spirit/game/scripts/cards/SWSH4/Trumbeak_144.py b/spirit/game/scripts/cards/SWSH4/Trumbeak_144.py index 930d2cb..e64cca3 100644 --- a/spirit/game/scripts/cards/SWSH4/Trumbeak_144.py +++ b/spirit/game/scripts/cards/SWSH4/Trumbeak_144.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import look_top_attach_energy +from spirit.game.card_effects.trainers import is_basic_energy_card + +charging_trumpet = look_top_attach_energy( + 3, predicate=is_basic_energy_card, rest="shuffle", distribute=True, minimum=0 +) card = PokemonCardDef( guid="81affd0a-3e81-56d8-acc0-3a0eb67c3c9f", @@ -23,7 +29,8 @@ card = PokemonCardDef( Ability( title="Charging Trumpet", game_text="When you play this Pok\u00e9mon from your hand to evolve 1 of your Pok\u00e9mon during your turn, you may look at the top 3 cards of your deck and attach any number of basic Energy cards you find there to your Pok\u00e9mon in any way you like. Shuffle the other cards back into your deck.", - effect=unimplemented, + trigger=Triggers.ON_EVOLVE, + effect=charging_trumpet, ), Attack( title="Drill Peck", diff --git a/spirit/game/scripts/cards/SWSH4/Tynamo_57.py b/spirit/game/scripts/cards/SWSH4/Tynamo_57.py index 66f4294..d53fc31 100644 --- a/spirit/game/scripts/cards/SWSH4/Tynamo_57.py +++ b/spirit/game/scripts/cards/SWSH4/Tynamo_57.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import retreat_free_when +from spirit.game.models.board import BoardState + + +def _has_any_energy(pokemon, carrier): + return pokemon is carrier and bool(BoardState.attached_energies(pokemon)) + card = PokemonCardDef( guid="3cc16986-3a72-5156-9262-6ee2d092a4a1", @@ -21,7 +28,7 @@ card = PokemonCardDef( Ability( title="Levitate", game_text="If this Pok\u00e9mon has any Energy attached, it has no Retreat Cost.", - effect=unimplemented, + passive=retreat_free_when(_has_any_energy), ), Attack( title="Tiny Charge", diff --git a/spirit/game/scripts/cards/SWSH4/Vaporeon_30.py b/spirit/game/scripts/cards/SWSH4/Vaporeon_30.py index 78314fb..1a1d117 100644 --- a/spirit/game/scripts/cards/SWSH4/Vaporeon_30.py +++ b/spirit/game/scripts/cards/SWSH4/Vaporeon_30.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, def_for +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.passives_common import ability_lock_passive + + +def _has_memory_capsule(carrier): + for child in carrier.children: + d = def_for(child.archetype_id) + if d and d.display_name == "Memory Capsule": + return True + return False + + +def _fire_locked(pokemon, carrier): + if not _has_memory_capsule(carrier): + return False + return PokemonTypes.FIRE.value in (pokemon.get_attribute(AttrID.POKEMON_TYPES) or []) + card = PokemonCardDef( guid="738a3b45-dda7-5662-8021-555fc2101196", @@ -22,7 +38,7 @@ card = PokemonCardDef( Ability( title="Torrential Awakening", game_text="If this Pok\u00e9mon has a Memory Capsule attached, Fire Pok\u00e9mon in play (both yours and your opponent's) have no Abilities.", - effect=unimplemented, + passive=ability_lock_passive(_fire_locked), ), Attack( title="Aurora Beam", diff --git a/spirit/game/scripts/cards/SWSH4/Wailmer_31.py b/spirit/game/scripts/cards/SWSH4/Wailmer_31.py index d285ea4..2c06969 100644 --- a/spirit/game/scripts/cards/SWSH4/Wailmer_31.py +++ b/spirit/game/scripts/cards/SWSH4/Wailmer_31.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy card = PokemonCardDef( guid="7a91550d-e39b-5399-943e-fff5ff2fe21c", @@ -24,7 +25,9 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=10, damage_operator="+", - effect=unimplemented, + effect=damage_per( + count_energy("self", energy_type=PokemonTypes.WATER), 20, base=10 + ), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Wailord_32.py b/spirit/game/scripts/cards/SWSH4/Wailord_32.py index a30cd90..b2c7b79 100644 --- a/spirit/game/scripts/cards/SWSH4/Wailord_32.py +++ b/spirit/game/scripts/cards/SWSH4/Wailord_32.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy + + +async def water_veil(ctx): + """Whenever you attach an Energy card from your hand to this Pokémon, remove all Special Conditions from it.""" + if ctx.attaching_player_id != ctx.player_id or ctx.energy_receiver is not ctx.source: + return + await ctx.cure_all_conditions(ctx.source) card = PokemonCardDef( guid="7029b0f0-63c1-5d4d-99da-3e67d170fd0c", @@ -22,7 +30,8 @@ card = PokemonCardDef( Ability( title="Water Veil", game_text="Whenever you attach an Energy card from your hand to this Pok\u00e9mon, remove all Special Conditions from it.", - effect=unimplemented, + trigger=Triggers.ON_ENERGY_ATTACHED, + effect=water_veil, ), Attack( title="Hydro Pump", @@ -30,7 +39,9 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 4}, damage=10, damage_operator="+", - effect=unimplemented, + effect=damage_per( + count_energy("self", energy_type=PokemonTypes.WATER), 40, base=10 + ), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Weedle_1.py b/spirit/game/scripts/cards/SWSH4/Weedle_1.py index b66d737..5e5b100 100644 --- a/spirit/game/scripts/cards/SWSH4/Weedle_1.py +++ b/spirit/game/scripts/cards/SWSH4/Weedle_1.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.session.effects import is_pokemon_card +from spirit.game.card_effects.support_common import search_to_hand + + +def _is_grass_pokemon(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_pokemon_card(card) and PokemonTypes.GRASS.value in types card = PokemonCardDef( guid="ac384dda-8e50-5fbf-a731-a51f04ba9688", @@ -22,7 +29,7 @@ card = PokemonCardDef( title="Bug Hunch", game_text="Search your deck for up to 2 Grass Pok\u00e9mon, reveal them, and put them into your hand. Then, shuffle your deck.", cost={PokemonTypes.GRASS: 1}, - effect=unimplemented, + effect=search_to_hand(_is_grass_pokemon, count=2, minimum=0, reveal=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Whimsicott_76.py b/spirit/game/scripts/cards/SWSH4/Whimsicott_76.py index cfdb647..e461876 100644 --- a/spirit/game/scripts/cards/SWSH4/Whimsicott_76.py +++ b/spirit/game/scripts/cards/SWSH4/Whimsicott_76.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import draw_attack + + +async def flying_fury(ctx): + """Before damage, discard any number of your Pokémon Tools; +40 damage each.""" + my_tools = [tool for tool, pokemon in ctx.tools_in_play() + if pokemon.owning_player_id == ctx.player_id] + picks = [] + if my_tools: + picks = await ctx.choose_cards( + my_tools, len(my_tools), minimum=0, + prompt="Discard any number of Pokémon Tools from your Pokémon.", + ) + if picks: + await ctx.discard_cards(picks) + await ctx.deal_damage(10 + 40 * len(picks)) + card = PokemonCardDef( guid="ac206174-5d5f-5c35-b0ce-2a9753f9c347", @@ -23,7 +40,7 @@ card = PokemonCardDef( title="Triple Draw", game_text="Draw 3 cards.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=draw_attack(3), ), Attack( title="Flying Fury", @@ -31,7 +48,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 1}, damage=10, damage_operator="+", - effect=unimplemented, + effect=flying_fury, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Whismur_135.py b/spirit/game/scripts/cards/SWSH4/Whismur_135.py index f1e3a43..944a8d2 100644 --- a/spirit/game/scripts/cards/SWSH4/Whismur_135.py +++ b/spirit/game/scripts/cards/SWSH4/Whismur_135.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="6a351d4f-4b69-5ca9-b95a-13f90c252fbe", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=40, damage_operator="x", - effect=unimplemented, + effect=flip_damage(until_tails=True, per_heads=40), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Woobat_73.py b/spirit/game/scripts/cards/SWSH4/Woobat_73.py index 3fb7a94..d6e91c9 100644 --- a/spirit/game/scripts/cards/SWSH4/Woobat_73.py +++ b/spirit/game/scripts/cards/SWSH4/Woobat_73.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_all_opponents + + +async def unamplified_soundwave(ctx): + """30 to each of your opponent's Pokemon (no W/R on the Bench). Does + nothing if you have any cards in your hand.""" + if ctx.hand_size(): + return + await damage_all_opponents(30)(ctx) + card = PokemonCardDef( guid="21552761-0ded-5e64-989a-9e6871c81489", @@ -21,9 +31,9 @@ card = PokemonCardDef( abilities=[ Attack( title="Unamplified Soundwave", - game_text="This attack does 30 damage to each of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.) If you have any cards in your hand, this attack does nothing.", + game_text="This attack does 30 damage to each of your opponent's Pokémon. (Don't apply Weakness and Resistance for Benched Pokémon.) If you have any cards in your hand, this attack does nothing.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=unamplified_soundwave, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH4/WyndonStadium_161.py b/spirit/game/scripts/cards/SWSH4/WyndonStadium_161.py index a6bfaad..c7bde11 100644 --- a/spirit/game/scripts/cards/SWSH4/WyndonStadium_161.py +++ b/spirit/game/scripts/cards/SWSH4/WyndonStadium_161.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import StadiumCardDef, unimplemented +from spirit.game.data_utils import StadiumCardDef, subtypes_for from spirit.game.attributes import Rarities +from spirit.game.session.passives import Passive + + +class _WyndonStadiumPassive(Passive): + """Whenever either player plays a Pokemon VMAX from their hand to evolve + a Pokemon V during their turn, heal 100 damage from that Pokemon.""" + + def heal_on_evolve(self, evolved, pre_evolution, player_id, carrier): + if "VMAX" not in subtypes_for(evolved.archetype_id): + return 0 + if "V" not in subtypes_for(pre_evolution.archetype_id): + return 0 + return 100 + card = StadiumCardDef( guid="b2e2dcc5-2de2-5a8c-98b1-bca51266cad4", @@ -11,5 +25,5 @@ card = StadiumCardDef( collector_number=161, set_code="SWSH4", rarity=Rarities.Uncommon, - effect=unimplemented + passive=_WyndonStadiumPassive(), ) diff --git a/spirit/game/scripts/cards/SWSH4/Xerneas_78.py b/spirit/game/scripts/cards/SWSH4/Xerneas_78.py index 5492e60..435eb99 100644 --- a/spirit/game/scripts/cards/SWSH4/Xerneas_78.py +++ b/spirit/game/scripts/cards/SWSH4/Xerneas_78.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import heal_attack, recover_from_discard, requires_discard card = PokemonCardDef( guid="7936d21c-1bb5-5bb9-897c-44df53e0335e", @@ -22,14 +23,15 @@ card = PokemonCardDef( title="Geo Hunt", game_text="Put a card from your discard pile into your hand.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + condition=requires_discard(), + effect=recover_from_discard(count=1, minimum=1, reveal=False, to="hand"), ), Attack( title="Aurora Gain", game_text="Heal 30 damage from this Pok\u00e9mon.", cost={PokemonTypes.PSYCHIC: 2, PokemonTypes.COLORLESS: 1}, damage=100, - effect=unimplemented, + effect=heal_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Yanma_6.py b/spirit/game/scripts/cards/SWSH4/Yanma_6.py index a373730..f63ea59 100644 --- a/spirit/game/scripts/cards/SWSH4/Yanma_6.py +++ b/spirit/game/scripts/cards/SWSH4/Yanma_6.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import switch_self_attack card = PokemonCardDef( guid="3b182fe8-957c-5b00-9fc8-3cf96abcd2f7", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="You may switch this Pok\u00e9mon with 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, damage=10, - effect=unimplemented, + effect=switch_self_attack(optional=True), ), Attack( title="Cutting Wind", diff --git a/spirit/game/scripts/cards/SWSH4/Yanmega_7.py b/spirit/game/scripts/cards/SWSH4/Yanmega_7.py index 7d1ce36..59e38b4 100644 --- a/spirit/game/scripts/cards/SWSH4/Yanmega_7.py +++ b/spirit/game/scripts/cards/SWSH4/Yanmega_7.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import switch_self_attack card = PokemonCardDef( guid="e7424d9a-9d34-544c-9075-f50bdd23e9ff", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="You may switch this Pok\u00e9mon with 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 2}, damage=50, - effect=unimplemented, + effect=switch_self_attack(optional=True), ), Attack( title="Cutting Wind", diff --git a/spirit/game/scripts/cards/SWSH4/Zacian_82.py b/spirit/game/scripts/cards/SWSH4/Zacian_82.py index adef5ca..4199228 100644 --- a/spirit/game/scripts/cards/SWSH4/Zacian_82.py +++ b/spirit/game/scripts/cards/SWSH4/Zacian_82.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if +from spirit.game.card_effects.support_common import attach_from_discard +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.card_effects.pokemon import is_pokemon_vmax + +_opponent_has_vmax = lambda ctx: any( + is_pokemon_vmax(p.archetype_id) for p in ctx.opponent_pokemon_in_play() +) card = PokemonCardDef( guid="894c5061-8e44-5472-9611-2af49a1aaed7", @@ -23,7 +31,7 @@ card = PokemonCardDef( game_text="Attach a basic Energy card from your discard pile to this Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=attach_from_discard(predicate=is_basic_energy_card, count=1, target="self"), ), Attack( title="Amazing Sword", @@ -31,7 +39,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.PSYCHIC: 1, PokemonTypes.METAL: 1}, damage=150, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_opponent_has_vmax, 150), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Zamazenta_102.py b/spirit/game/scripts/cards/SWSH4/Zamazenta_102.py index 8a739cb..016cba1 100644 --- a/spirit/game/scripts/cards/SWSH4/Zamazenta_102.py +++ b/spirit/game/scripts/cards/SWSH4/Zamazenta_102.py @@ -1,5 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import attach_from_discard +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.card_effects.passives_common import prevent_damage_when +from spirit.game.card_effects.pokemon import is_pokemon_vmax + + +async def amazing_shield(ctx): + """180; next turn, prevent all damage done to this Pokemon by VMAX attacks.""" + await ctx.deal_damage() + shield = prevent_damage_when( + lambda calc, c: calc.target is c and calc.attacker is not None + and is_pokemon_vmax(calc.attacker.archetype_id) + ) + ctx.add_passive_through_opponents_turn(ctx.attacker, shield) + card = PokemonCardDef( guid="2137a21d-8c55-54ef-a688-dc8748e37904", @@ -23,14 +38,17 @@ card = PokemonCardDef( game_text="Attach a basic Energy card from your discard pile to this Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=attach_from_discard( + predicate=is_basic_energy_card, count=1, target="self", + prompt="Choose a basic Energy card from your discard pile to attach to this Pok\u00e9mon.", + ), ), Attack( title="Amazing Shield", game_text="During your opponent's next turn, prevent all damage done to this Pok\u00e9mon by attacks from Pok\u00e9mon VMAX.", cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.FIGHTING: 1, PokemonTypes.METAL: 1}, damage=180, - effect=unimplemented, + effect=amazing_shield, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Zapdos_48.py b/spirit/game/scripts/cards/SWSH4/Zapdos_48.py index 8f64057..df49ed8 100644 --- a/spirit/game/scripts/cards/SWSH4/Zapdos_48.py +++ b/spirit/game/scripts/cards/SWSH4/Zapdos_48.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, is_pokemon_v from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import is_pokemon_gx + + +async def thunder_snipe(ctx): + """Discard all Energy from this Pokemon; 160 to 1 opposing V/GX (no W/R off Active).""" + await ctx.discard_energy_from(ctx.attacker, 99) + candidates = [p for p in ctx.opponent_pokemon_in_play() + if is_pokemon_v(p.archetype_id) or is_pokemon_gx(p.archetype_id)] + if not candidates: + return + target = await ctx.choose_pokemon( + candidates, "Choose 1 of your opponent's Pokémon V or Pokémon-GX" + ) + if target is not None: + await ctx.deal_damage(160, target=target) + card = PokemonCardDef( guid="b719dc8a-c260-5ef3-aa33-e36572c72412", @@ -28,7 +44,7 @@ card = PokemonCardDef( title="Thunder Snipe", game_text="Discard all Energy from this Pok\u00e9mon, and this attack does 160 damage to 1 of your opponent's Pok\u00e9mon V or Pok\u00e9mon-GX. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=thunder_snipe, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/ZarudeV_167.py b/spirit/game/scripts/cards/SWSH4/ZarudeV_167.py index 40aabad..7c09250 100644 --- a/spirit/game/scripts/cards/SWSH4/ZarudeV_167.py +++ b/spirit/game/scripts/cards/SWSH4/ZarudeV_167.py @@ -1,5 +1,37 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.support_common import distribute_energy +from spirit.game.card_effects.trainers import is_basic_energy_card + + +async def jungle_rising(ctx): + """100. You may attach up to 2 basic Energy cards from your hand to your + Benched Pokémon in any way you like. If you attached Energy to a Pokémon + this way, heal all damage from that Pokémon.""" + await ctx.deal_damage() + bench = ctx.my_bench() + energies = [c for c in ctx.hand() if is_basic_energy_card(c)] + if not bench or not energies: + return + if not await ctx.ask_yes_no( + "Attach up to 2 basic Energy cards from your hand to your Benched Pokémon?" + ): + return + picks = await ctx.choose_cards( + energies, 2, minimum=0, prompt="Choose up to 2 basic Energy cards to attach" + ) + if not picks: + return + attached = await distribute_energy(ctx, picks, bench) + healed = set() + for _, target in attached: + if target.entity_id in healed: + continue + healed.add(target.entity_id) + heal_amount = ctx.max_hp(target) - target.get_attribute(AttrID.HP, 0) + if heal_amount > 0: + await ctx.heal(heal_amount, target) card = PokemonCardDef( guid="99a969ba-e5ee-5e63-a00e-4335f4d5008b", @@ -23,14 +55,14 @@ card = PokemonCardDef( game_text="During your opponent's next turn, the Defending Pok\u00e9mon can't retreat.", cost={PokemonTypes.COLORLESS: 2}, damage=50, - effect=unimplemented, + effect=condition_attack(no_retreat=True), ), Attack( title="Jungle Rising", game_text="You may attach up to 2 basic Energy cards from your hand to your Benched Pok\u00e9mon in any way you like. If you attached Energy to a Pok\u00e9mon in this way, heal all damage from that Pok\u00e9mon.", cost={PokemonTypes.GRASS: 2}, damage=100, - effect=unimplemented, + effect=jungle_rising, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/ZarudeV_22.py b/spirit/game/scripts/cards/SWSH4/ZarudeV_22.py index 512ca3f..dca2560 100644 --- a/spirit/game/scripts/cards/SWSH4/ZarudeV_22.py +++ b/spirit/game/scripts/cards/SWSH4/ZarudeV_22.py @@ -1,5 +1,37 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.support_common import distribute_energy +from spirit.game.card_effects.trainers import is_basic_energy_card + + +async def jungle_rising(ctx): + """100. You may attach up to 2 basic Energy cards from your hand to your + Benched Pokémon in any way you like. If you attached Energy to a Pokémon + this way, heal all damage from that Pokémon.""" + await ctx.deal_damage() + bench = ctx.my_bench() + energies = [c for c in ctx.hand() if is_basic_energy_card(c)] + if not bench or not energies: + return + if not await ctx.ask_yes_no( + "Attach up to 2 basic Energy cards from your hand to your Benched Pokémon?" + ): + return + picks = await ctx.choose_cards( + energies, 2, minimum=0, prompt="Choose up to 2 basic Energy cards to attach" + ) + if not picks: + return + attached = await distribute_energy(ctx, picks, bench) + healed = set() + for _, target in attached: + if target.entity_id in healed: + continue + healed.add(target.entity_id) + heal_amount = ctx.max_hp(target) - target.get_attribute(AttrID.HP, 0) + if heal_amount > 0: + await ctx.heal(heal_amount, target) card = PokemonCardDef( guid="1f23e53c-ce53-53d6-8371-61532a6f51f0", @@ -23,14 +55,14 @@ card = PokemonCardDef( game_text="During your opponent's next turn, the Defending Pok\u00e9mon can't retreat.", cost={PokemonTypes.COLORLESS: 2}, damage=50, - effect=unimplemented, + effect=condition_attack(no_retreat=True), ), Attack( title="Jungle Rising", game_text="You may attach up to 2 basic Energy cards from your hand to your Benched Pok\u00e9mon in any way you like. If you attached Energy to a Pok\u00e9mon in this way, heal all damage from that Pok\u00e9mon.", cost={PokemonTypes.GRASS: 2}, damage=100, - effect=unimplemented, + effect=jungle_rising, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Zekrom_60.py b/spirit/game/scripts/cards/SWSH4/Zekrom_60.py index 9195ac1..cb06b5e 100644 --- a/spirit/game/scripts/cards/SWSH4/Zekrom_60.py +++ b/spirit/game/scripts/cards/SWSH4/Zekrom_60.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions + + +async def wild_shock(ctx): + """130 damage; this Pokémon also does 60 damage to itself; opponent's Active is now Paralyzed.""" + await ctx.deal_damage() + await ctx.deal_damage(60, target=ctx.attacker, apply_modifiers=False) + await ctx.apply_special_condition(ctx.defender, SpecialConditions.PARALYZED) + card = PokemonCardDef( guid="70ede475-23db-51bc-a897-b94c4620a72a", @@ -28,7 +36,7 @@ card = PokemonCardDef( game_text="This Pok\u00e9mon also does 60 damage to itself. Your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 1}, damage=130, - effect=unimplemented, + effect=wild_shock, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH4/Zygarde_93.py b/spirit/game/scripts/cards/SWSH4/Zygarde_93.py index 739e143..b49ab9f 100644 --- a/spirit/game/scripts/cards/SWSH4/Zygarde_93.py +++ b/spirit/game/scripts/cards/SWSH4/Zygarde_93.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, count_prizes_remaining + +_more_prizes_remaining = lambda ctx: count_prizes_remaining("mine")(ctx) > count_prizes_remaining("opponent")(ctx) card = PokemonCardDef( guid="da227e9e-0736-52e9-bfff-8a8e8d1c0473", @@ -29,7 +32,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 2}, damage=80, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_more_prizes_remaining, 80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH45/AlcremieVMAX_73.py b/spirit/game/scripts/cards/SWSH45/AlcremieVMAX_73.py index 0adf9f7..8bcc80e 100644 --- a/spirit/game/scripts/cards/SWSH45/AlcremieVMAX_73.py +++ b/spirit/game/scripts/cards/SWSH45/AlcremieVMAX_73.py @@ -1,6 +1,45 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.pokemon import energy_provides_type +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, def_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +def _is_psychic_energy(card): + return is_basic_energy_card(card) and \ + energy_provides_type(card, PokemonTypes.PSYCHIC.value) + + +async def adornment(ctx): + """For each Benched Pokemon, search a Psychic Energy onto it; shuffle.""" + for pokemon in list(ctx.my_bench()): + definition = def_for(pokemon.archetype_id) + name = getattr(definition, "display_name", None) or "your Benched Pokémon" + picks = await ctx.search_deck( + _is_psychic_energy, count=1, minimum=0, + prompt=f"Choose a Psychic Energy card to attach to {name}.", + ) + if picks: + if pokemon.entity_id not in ctx.visual_targets: + ctx.visual_targets.append(pokemon.entity_id) + await ctx.attach_energy(picks[0], pokemon) + await ctx.shuffle_deck() + + +async def g_max_whisk(ctx): + """Discard any amount of Energy from your Pokemon; 60 per card discarded.""" + energies = [e for p in ctx.my_pokemon_in_play() + for e in ctx.attached_energies(p)] + picks = [] + if energies: + picks = await ctx.choose_cards( + energies, len(energies), minimum=0, + prompt="Choose any amount of Energy to discard from your Pokémon.", + ) + if picks: + await ctx.discard_cards(picks) + await ctx.deal_damage(60 * len(picks)) + + card = PokemonCardDef( guid="0d0d5da2-bd80-5a43-a206-743b2d8c18f4", key="SWSH45", @@ -21,17 +60,17 @@ card = PokemonCardDef( abilities=[ Attack( title="Adornment", - game_text="For each of your Benched Pok\u00e9mon, search your deck for a Psychic Energy card and attach it to that Pok\u00e9mon. Then, shuffle your deck.", + game_text="For each of your Benched Pokémon, search your deck for a Psychic Energy card and attach it to that Pokémon. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=adornment, ), Attack( title="G-Max Whisk", - game_text="Discard any amount of Energy from your Pok\u00e9mon. This attack does 60 damage for each card you discarded in this way.", + game_text="Discard any amount of Energy from your Pokémon. This attack does 60 damage for each card you discarded in this way.", cost={PokemonTypes.PSYCHIC: 2}, damage=60, damage_operator="x", - effect=unimplemented, + effect=g_max_whisk, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH45/AlcremieV_64.py b/spirit/game/scripts/cards/SWSH45/AlcremieV_64.py index ececad3..c6b608e 100644 --- a/spirit/game/scripts/cards/SWSH45/AlcremieV_64.py +++ b/spirit/game/scripts/cards/SWSH45/AlcremieV_64.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_basic_pokemon +from spirit.game.card_effects.attacks_common import lock_defender_attacks + + +async def sugary_sprinkles(ctx): + """Heal 30 damage from each of your Benched Pokemon.""" + for pokemon in ctx.my_bench(): + await ctx.heal(30, pokemon) + + +async def sweet_splash(ctx): + """100 damage. If the Defending Pokemon is a Basic Pokemon, it can't attack next turn.""" + await ctx.deal_damage() + defender = ctx.defender + if defender is not None and is_basic_pokemon(defender): + lock_defender_attacks(ctx) + card = PokemonCardDef( guid="cf1a873b-b252-5cf9-a433-79c790b2fe1f", @@ -22,14 +39,14 @@ card = PokemonCardDef( title="Sugary Sprinkles", game_text="Heal 30 damage from each of your Benched Pok\u00e9mon.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=sugary_sprinkles, ), Attack( title="Sweet Splash", game_text="If the Defending Pok\u00e9mon is a Basic Pok\u00e9mon, it can't attack during your opponent's next turn.", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 2}, damage=100, - effect=unimplemented, + effect=sweet_splash, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH45/BallGuy_57.py b/spirit/game/scripts/cards/SWSH45/BallGuy_57.py index e8d0be7..ae34c75 100644 --- a/spirit/game/scripts/cards/SWSH45/BallGuy_57.py +++ b/spirit/game/scripts/cards/SWSH45/BallGuy_57.py @@ -1,5 +1,36 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef, def_for from spirit.game.attributes import Rarities +from spirit.game.session.effects import is_item_card + + +def _is_ball_item(card): + if not is_item_card(card): + return False + definition = def_for(getattr(card, "archetype_id", None) or "") + return bool(definition and "ball" in (definition.display_name or "").lower()) + + +async def ball_guy(ctx): + deck_cards = list(ctx.deck(ctx.player_id)) + reps = [] + seen_names = set() + for card_entity in deck_cards: + if not _is_ball_item(card_entity): + continue + definition = def_for(card_entity.archetype_id) + name = definition.display_name if definition else None + if not name or name in seen_names: + continue + seen_names.add(name) + reps.append(card_entity) + picks = await ctx.choose_cards( + reps, 3, minimum=0, + prompt="Choose up to 3 different Item cards with \"Ball\" in their name.", + display_cards=deck_cards, + ) + await ctx.put_in_hand(picks, reveal=True) + await ctx.shuffle_deck() + card = SupporterCardDef( guid="b0f5763d-6739-5653-9726-616e779924a8", @@ -11,5 +42,5 @@ card = SupporterCardDef( collector_number=57, set_code="SWSH45", rarity=Rarities.Uncommon, - effect=unimplemented + effect=ball_guy, ) diff --git a/spirit/game/scripts/cards/SWSH45/BallGuy_65.py b/spirit/game/scripts/cards/SWSH45/BallGuy_65.py index 93db413..c850b34 100644 --- a/spirit/game/scripts/cards/SWSH45/BallGuy_65.py +++ b/spirit/game/scripts/cards/SWSH45/BallGuy_65.py @@ -1,5 +1,36 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef, def_for from spirit.game.attributes import Rarities +from spirit.game.session.effects import is_item_card + + +def _is_ball_item(card): + if not is_item_card(card): + return False + definition = def_for(getattr(card, "archetype_id", None) or "") + return bool(definition and "ball" in (definition.display_name or "").lower()) + + +async def ball_guy(ctx): + deck_cards = list(ctx.deck(ctx.player_id)) + reps = [] + seen_names = set() + for card_entity in deck_cards: + if not _is_ball_item(card_entity): + continue + definition = def_for(card_entity.archetype_id) + name = definition.display_name if definition else None + if not name or name in seen_names: + continue + seen_names.add(name) + reps.append(card_entity) + picks = await ctx.choose_cards( + reps, 3, minimum=0, + prompt="Choose up to 3 different Item cards with \"Ball\" in their name.", + display_cards=deck_cards, + ) + await ctx.put_in_hand(picks, reveal=True) + await ctx.shuffle_deck() + card = SupporterCardDef( guid="781b22f5-be57-549e-a72e-3380392bf826", @@ -11,5 +42,5 @@ card = SupporterCardDef( collector_number=65, set_code="SWSH45", rarity=Rarities.RareUltra, - effect=unimplemented + effect=ball_guy, ) diff --git a/spirit/game/scripts/cards/SWSH45/BirdKeeper_66.py b/spirit/game/scripts/cards/SWSH45/BirdKeeper_66.py index 86088cb..f9d2c5d 100644 --- a/spirit/game/scripts/cards/SWSH45/BirdKeeper_66.py +++ b/spirit/game/scripts/cards/SWSH45/BirdKeeper_66.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import player_has_bench + + +async def bird_keeper(ctx): + """Switch your Active Pokemon with 1 of your Benched Pokemon. If you do, draw 3 cards.""" + target = await ctx.choose_pokemon(ctx.my_bench(), "Choose your new Active Pokémon") + if target is None: + return + await ctx.switch_active(ctx.player_id, target) + await ctx.draw_cards(3) + card = SupporterCardDef( guid="82d01bbf-c787-595d-8d14-6afce683a65d", @@ -11,5 +22,6 @@ card = SupporterCardDef( collector_number=66, set_code="SWSH45", rarity=Rarities.RareUltra, - effect=unimplemented + effect=bird_keeper, + condition=player_has_bench ) diff --git a/spirit/game/scripts/cards/SWSH45/CaraLiss_67.py b/spirit/game/scripts/cards/SWSH45/CaraLiss_67.py index ae3272d..8af2613 100644 --- a/spirit/game/scripts/cards/SWSH45/CaraLiss_67.py +++ b/spirit/game/scripts/cards/SWSH45/CaraLiss_67.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import bench_has_room, fossil_search, is_rare_fossil card = SupporterCardDef( guid="db664100-46c7-5af9-a0d9-300ab1688ee8", @@ -11,5 +12,6 @@ card = SupporterCardDef( collector_number=67, set_code="SWSH45", rarity=Rarities.RareUltra, - effect=unimplemented + effect=fossil_search(is_rare_fossil), + condition=bench_has_room, ) diff --git a/spirit/game/scripts/cards/SWSH45/Celebi_3.py b/spirit/game/scripts/cards/SWSH45/Celebi_3.py index f9f4aef..89bb970 100644 --- a/spirit/game/scripts/cards/SWSH45/Celebi_3.py +++ b/spirit/game/scripts/cards/SWSH45/Celebi_3.py @@ -1,5 +1,26 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import in_active_spot, is_energy_card + + +async def _woodland_stroll(ctx): + if not await ctx.ask_yes_no("Look at the top 6 cards of your deck?"): + return + top = ctx.deck_top(6) + if not top: + return + candidates = [c for c in top if is_energy_card(c)] + picks = [] + if candidates: + picks = await ctx.choose_cards( + candidates, 1, minimum=0, + prompt="Reveal an Energy card you find there and put it into your hand.", + display_cards=top, + ) + if picks: + await ctx.put_in_hand(picks, reveal=True) + await ctx.shuffle_deck() + card = PokemonCardDef( guid="8ec72ba1-63a1-553d-b33a-af5c8a261e6a", @@ -21,7 +42,9 @@ card = PokemonCardDef( Ability( title="Woodland Stroll", game_text="Once during your turn, if this Pok\u00e9mon is in the Active Spot, you may look at the top 6 cards of your deck, reveal an Energy card you find there, and put it into your hand. Shuffle the other cards back into your deck.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=in_active_spot, + effect=_woodland_stroll, ), Attack( title="Leaf Step", diff --git a/spirit/game/scripts/cards/SWSH45/CinderaceVMAX_19.py b/spirit/game/scripts/cards/SWSH45/CinderaceVMAX_19.py index b28cdb7..e48db9d 100644 --- a/spirit/game/scripts/cards/SWSH45/CinderaceVMAX_19.py +++ b/spirit/game/scripts/cards/SWSH45/CinderaceVMAX_19.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack + + +async def counter(ctx): + """30, plus that much more damage if this Pokémon was damaged by an + attack during the opponent's last turn.""" + bonus = ctx.damage_taken_last_turn(ctx.attacker) + await ctx.deal_damage(30 + bonus) + card = PokemonCardDef( guid="6d49edc8-3477-5ce3-b8b0-642684647172", @@ -25,14 +34,14 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 1}, damage=30, damage_operator="+", - effect=unimplemented, + effect=counter, ), Attack( title="Max Pyro Ball", game_text="Your opponent's Active Pok\u00e9mon is now Burned.", cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=170, - effect=unimplemented, + effect=condition_attack(SpecialConditions.BURNED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH45/CinderaceV_18.py b/spirit/game/scripts/cards/SWSH45/CinderaceV_18.py index 9659607..b96467f 100644 --- a/spirit/game/scripts/cards/SWSH45/CinderaceV_18.py +++ b/spirit/game/scripts/cards/SWSH45/CinderaceV_18.py @@ -1,5 +1,23 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.attributes import AttrID +from spirit.game.session.passives import Passive + + +class _FieldRunnerPassive(Passive): + """No Retreat Cost while a Stadium is in play.""" + + def modify_retreat_cost(self, cost, pokemon, carrier, board): + if pokemon is not carrier: + return cost + node = carrier + while node.parent is not None: + node = node.parent + for child in node.children: + if child.get_attribute(AttrID.NAME) == "activeStadium" and child.children: + return 0 + return cost + card = PokemonCardDef( guid="01521ed1-11a9-5201-bde6-342db3988f85", @@ -21,7 +39,7 @@ card = PokemonCardDef( Ability( title="Field Runner", game_text="If a Stadium is in play, this Pok\u00e9mon has no Retreat Cost.", - effect=unimplemented, + passive=_FieldRunnerPassive(), ), Attack( title="Crimson Legs", diff --git a/spirit/game/scripts/cards/SWSH45/CramorantVMAX_55.py b/spirit/game/scripts/cards/SWSH45/CramorantVMAX_55.py index 01c26e1..d8f3a94 100644 --- a/spirit/game/scripts/cards/SWSH45/CramorantVMAX_55.py +++ b/spirit/game/scripts/cards/SWSH45/CramorantVMAX_55.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import count_energy, flip_damage +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -26,7 +27,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=80, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins_from=count_energy("self"), per_heads=80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH45/CramorantV_54.py b/spirit/game/scripts/cards/SWSH45/CramorantV_54.py index a6f461c..c8eb888 100644 --- a/spirit/game/scripts/cards/SWSH45/CramorantV_54.py +++ b/spirit/game/scripts/cards/SWSH45/CramorantV_54.py @@ -1,6 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def spit_shot(ctx): + """Discard all Energy from this Pokémon. 160 damage to 1 of your + opponent's Pokémon (no W/R on a Benched target).""" + await ctx.discard_energy_from(ctx.source, 99) + candidates = ctx.opponent_pokemon_in_play() + if not candidates: + return + target = await ctx.choose_pokemon(candidates, "Choose 1 of your opponent's Pokémon") + if target is None: + return + await ctx.deal_damage(160, target=target) + + card = PokemonCardDef( guid="4f69b71b-1896-5148-b5db-20b85b562aca", key="SWSH45", @@ -23,13 +38,13 @@ card = PokemonCardDef( title="Beak Catch", game_text="Search your deck for up to 2 cards and put them into your hand. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_to_hand(count=2, reveal=False, prompt="Choose up to 2 cards."), ), Attack( title="Spit Shot", game_text="Discard all Energy from this Pok\u00e9mon. This attack does 160 damage to 1 of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.COLORLESS: 3}, - effect=unimplemented, + effect=spit_shot, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH45/Cramorant_28.py b/spirit/game/scripts/cards/SWSH45/Cramorant_28.py index fa0e4da..6e7c78a 100644 --- a/spirit/game/scripts/cards/SWSH45/Cramorant_28.py +++ b/spirit/game/scripts/cards/SWSH45/Cramorant_28.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import snipe_attack card = PokemonCardDef( guid="00748805-c540-5a3a-b701-0f06e4267459", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Spit-Out Shot", game_text="This attack does 50 damage to 1 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.WATER: 2}, - effect=unimplemented, + effect=snipe_attack(50, pool="bench", count=1), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH45/CrobatVMAX_45.py b/spirit/game/scripts/cards/SWSH45/CrobatVMAX_45.py index 764e483..780cd13 100644 --- a/spirit/game/scripts/cards/SWSH45/CrobatVMAX_45.py +++ b/spirit/game/scripts/cards/SWSH45/CrobatVMAX_45.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions + + +async def stealth_poison(ctx): + """70, opponent's Active becomes Poisoned, then switch this Pokémon with 1 of your Benched.""" + await ctx.deal_damage() + await ctx.apply_special_condition(ctx.defender, SpecialConditions.POISONED) + bench = ctx.my_bench() + if bench: + target = await ctx.choose_pokemon(bench, "Choose your new Active Pokémon") + if target is not None: + await ctx.switch_active(ctx.player_id, target) card = PokemonCardDef( guid="dc057a12-def2-5b04-b8d5-42a44e7da160", @@ -24,7 +35,7 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Poisoned. Switch this Pok\u00e9mon with 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 1}, damage=70, - effect=unimplemented, + effect=stealth_poison, ), Attack( title="Max Cutter", diff --git a/spirit/game/scripts/cards/SWSH45/Decidueye_8.py b/spirit/game/scripts/cards/SWSH45/Decidueye_8.py index 0cda924..b42a6e6 100644 --- a/spirit/game/scripts/cards/SWSH45/Decidueye_8.py +++ b/spirit/game/scripts/cards/SWSH45/Decidueye_8.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, is_pokemon_v from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import prevent_damage_when +from spirit.game.card_effects.pokemon import is_pokemon_gx +from spirit.game.card_effects.attacks_common import snipe_attack + + +def _deep_forest_camo_pred(calc, carrier): + if calc.target is not carrier or calc.attacker is None: + return False + atk_id = calc.attacker.archetype_id + return is_pokemon_v(atk_id) or is_pokemon_gx(atk_id) + card = PokemonCardDef( guid="ab90ab27-2f4a-59d9-929a-39c82fa29f62", @@ -22,14 +33,14 @@ card = PokemonCardDef( Ability( title="Deep Forest Camo", game_text="Prevent all damage done to this Pok\u00e9mon by attacks from your opponent's Pok\u00e9mon V and Pok\u00e9mon-GX.", - effect=unimplemented, + passive=prevent_damage_when(_deep_forest_camo_pred), ), Attack( title="Splitting Arrow", game_text="This attack also does 20 damage to 2 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=90, - effect=unimplemented, + effect=snipe_attack(20, pool="bench", count=2, also_base=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH45/DhelmiseVMAX_10.py b/spirit/game/scripts/cards/SWSH45/DhelmiseVMAX_10.py index c967bf4..97dc9ef 100644 --- a/spirit/game/scripts/cards/SWSH45/DhelmiseVMAX_10.py +++ b/spirit/game/scripts/cards/SWSH45/DhelmiseVMAX_10.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import count_energy + + +async def swinging_chain(ctx): + amount = 30 * count_energy("self", energy_type=PokemonTypes.GRASS)(ctx) + if amount <= 0: + return + candidates = ctx.opponent_pokemon_in_play() + if not candidates: + return + target = await ctx.choose_pokemon(candidates, "Choose 1 of your opponent's Pokémon") + if target is not None: + await ctx.deal_damage(amount, target=target) + card = PokemonCardDef( guid="64878312-a93f-5b53-ab2d-255fd5bf5529", @@ -23,14 +37,14 @@ card = PokemonCardDef( title="Swinging Chain", game_text="This attack does 30 damage to 1 of your opponent's Pok\u00e9mon for each Grass Energy attached to this Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.GRASS: 1}, - effect=unimplemented, + effect=swinging_chain, ), Attack( title="Max Anchor", game_text="During your next turn, this Pok\u00e9mon can't use Max Anchor.", cost={PokemonTypes.GRASS: 2, PokemonTypes.COLORLESS: 1}, damage=240, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH45/DhelmiseV_9.py b/spirit/game/scripts/cards/SWSH45/DhelmiseV_9.py index 6834ccd..1f76eff 100644 --- a/spirit/game/scripts/cards/SWSH45/DhelmiseV_9.py +++ b/spirit/game/scripts/cards/SWSH45/DhelmiseV_9.py @@ -1,5 +1,30 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +import json + +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, def_for +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID + + +def _is_grass_archetype(archetype_id): + definition = def_for(archetype_id) + attr = definition.extra_attributes.get(str(AttrID.POKEMON_TYPES.value)) if definition else None + if not attr: + return False + try: + types = json.loads(attr["value"]) + except (TypeError, ValueError): + return False + return PokemonTypes.GRASS.value in types + + +async def anchor_anger(ctx): + """30, +90 if one of your Grass Pokemon was KO'd by an opponent's attack last turn.""" + bonus = 0 + for ko in ctx.session.turn_state.kos_by_attack_last_turn.get(ctx.player_id, []): + if _is_grass_archetype(ko.get("archetype_id")): + bonus = 90 + break + await ctx.deal_damage(30 + bonus) + card = PokemonCardDef( guid="4f596aec-fed7-5177-a3af-ffe4858bad60", @@ -24,14 +49,14 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1}, damage=30, damage_operator="+", - effect=unimplemented, + effect=anchor_anger, ), Attack( title="Giga Hammer", game_text="During your next turn, this Pok\u00e9mon can't use Giga Hammer.", cost={PokemonTypes.GRASS: 2, PokemonTypes.COLORLESS: 1}, damage=200, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH45/DittoVMAX_51.py b/spirit/game/scripts/cards/SWSH45/DittoVMAX_51.py index 86b3a06..71c5ad8 100644 --- a/spirit/game/scripts/cards/SWSH45/DittoVMAX_51.py +++ b/spirit/game/scripts/cards/SWSH45/DittoVMAX_51.py @@ -1,5 +1,30 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, def_for +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities + + +async def max_transform(ctx): + """Choose 1 of your opponent's Active Pokemon's attacks and use it as this attack.""" + active = ctx.opponent_active() + if active is None: + return + definition = def_for(active.archetype_id) + candidates = [ + (active, ability) for ability in getattr(definition, "abilities", []) + if isinstance(ability, Attack) + ] + if not candidates: + return + picked = await ctx.choose_attack_to_copy(candidates, "Choose an attack to copy") + if picked is None: + return + _, chosen = picked + if not await ctx.use_attack(chosen): + return + if getattr(chosen, "locks_next_turn", False): + for entry in ctx.attacker.get_attribute(AttrID.PIE_ABILITIES) or []: + if isinstance(entry, dict) and entry.get("abilityType") == "Attack": + ctx.session.turn_state.lock_attack(ctx.attacker.entity_id, entry["abilityID"]) + card = PokemonCardDef( guid="d5487ebb-78fd-5d16-b182-706c8afd77da", @@ -23,7 +48,7 @@ card = PokemonCardDef( title="Max Transform", game_text="Choose 1 of your opponent's Active Pok\u00e9mon's attacks and use it as this attack.", cost={PokemonTypes.COLORLESS: 3}, - effect=unimplemented, + effect=max_transform, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH45/Drednaw_27.py b/spirit/game/scripts/cards/SWSH45/Drednaw_27.py index 430a120..162402e 100644 --- a/spirit/game/scripts/cards/SWSH45/Drednaw_27.py +++ b/spirit/game/scripts/cards/SWSH45/Drednaw_27.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import takes_less_passive card = PokemonCardDef( guid="83b2b76d-fdd4-5c18-84ad-d63ff6e988da", @@ -22,7 +23,7 @@ card = PokemonCardDef( Ability( title="Exoskeleton", game_text="This Pok\u00e9mon takes 30 less damage from attacks (after applying Weakness and Resistance).", - effect=unimplemented, + passive=takes_less_passive(30), ), Attack( title="Skull Bash", diff --git a/spirit/game/scripts/cards/SWSH45/Eevee_52.py b/spirit/game/scripts/cards/SWSH45/Eevee_52.py index 5a3ba8f..fdd7eae 100644 --- a/spirit/game/scripts/cards/SWSH45/Eevee_52.py +++ b/spirit/game/scripts/cards/SWSH45/Eevee_52.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, evolves_from from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_or_nothing +from spirit.game.card_effects.support_common import search_to_hand + +signs_of_evolution = search_to_hand( + predicate=lambda c: evolves_from(c.archetype_id, "Eevee"), + count=1, minimum=0, reveal=True, + prompt="Choose a card that evolves from Eevee.", +) card = PokemonCardDef( guid="510181d3-5299-5bcf-9c28-c7ea44f84e3e", @@ -22,14 +30,14 @@ card = PokemonCardDef( title="Signs of Evolution", game_text="Search your deck for a card that evolves from Eevee, reveal it, and put it into your hand. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=signs_of_evolution, ), Attack( title="Wild Kick", game_text="Flip a coin. If tails, this attack does nothing.", cost={PokemonTypes.COLORLESS: 2}, damage=30, - effect=unimplemented, + effect=flip_or_nothing(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH45/Eldegoss_15.py b/spirit/game/scripts/cards/SWSH45/Eldegoss_15.py index 7d547e7..6f5be1e 100644 --- a/spirit/game/scripts/cards/SWSH45/Eldegoss_15.py +++ b/spirit/game/scripts/cards/SWSH45/Eldegoss_15.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.attacks_common import flip_or_nothing + + +async def enriching_seeds(ctx): + """Heal all damage from 1 of your Benched Pokémon.""" + bench = [p for p in ctx.my_bench() + if p.get_attribute(AttrID.HP, 0) < ctx.max_hp(p)] + if not bench: + return + target = await ctx.choose_pokemon(bench, "Choose a Benched Pokémon to heal") + if target is not None: + await ctx.heal(ctx.max_hp(target) - target.get_attribute(AttrID.HP, 0), target) card = PokemonCardDef( guid="965085fc-ce66-5cce-a9a2-98cf514b0ea9", @@ -23,14 +35,14 @@ card = PokemonCardDef( title="Enriching Seeds", game_text="Heal all damage from 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=enriching_seeds, ), Attack( title="Surprise Attack", game_text="Flip a coin. If tails, this attack does nothing.", cost={PokemonTypes.GRASS: 1}, damage=50, - effect=unimplemented, + effect=flip_or_nothing(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH45/Frosmoth_30.py b/spirit/game/scripts/cards/SWSH45/Frosmoth_30.py index 66f2fc4..e83d0a8 100644 --- a/spirit/game/scripts/cards/SWSH45/Frosmoth_30.py +++ b/spirit/game/scripts/cards/SWSH45/Frosmoth_30.py @@ -1,5 +1,33 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.session.effects import is_special_energy, is_water_pokemon +from spirit.game.card_effects.pokemon import is_energy_card + + +def _is_water_energy_card(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_energy_card(card) and not is_special_energy(card) and PokemonTypes.WATER.value in types + + +def _ice_dance_condition(board, player_id, pokemon): + hand = board.find_player_area(player_id, "hand") + if not hand or not any(_is_water_energy_card(c) for c in hand.children): + return False + bench = board.find_player_area(player_id, "bench") + return bool(bench) and any(is_water_pokemon(c) for c in bench.children) + + +async def ice_dance(ctx): + """As often as you like: attach a Water Energy card from hand to a Benched Water Pokemon.""" + energies = [c for c in ctx.hand() if _is_water_energy_card(c)] + picked = await ctx.choose_cards(energies, 1, minimum=1, prompt="Choose a Water Energy card to attach") + if not picked: + return + targets = [p for p in ctx.my_bench() if is_water_pokemon(p)] + target = await ctx.choose_pokemon(targets, "Choose the Benched Water Pokémon to attach it to") + if target is not None: + await ctx.attach_energy(picked[0], target) + card = PokemonCardDef( guid="d1db5591-eb82-5402-9d57-8502335e1a69", @@ -22,7 +50,9 @@ card = PokemonCardDef( Ability( title="Ice Dance", game_text="As often as you like during your turn, you may attach a Water Energy card from your hand to 1 of your Benched Water Pok\u00e9mon.", - effect=unimplemented, + activation=Activations.UNLIMITED, + condition=_ice_dance_condition, + effect=ice_dance, ), Attack( title="Aurora Beam", diff --git a/spirit/game/scripts/cards/SWSH45/GalarianWeezing_42.py b/spirit/game/scripts/cards/SWSH45/GalarianWeezing_42.py index 93bfb8c..7ff8055 100644 --- a/spirit/game/scripts/cards/SWSH45/GalarianWeezing_42.py +++ b/spirit/game/scripts/cards/SWSH45/GalarianWeezing_42.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.passives_common import ( + ability_lock_passive, + is_in_active_spot, + opposing_pokemon, +) + + +def _neutralizing_gas_target(pokemon, carrier): + return opposing_pokemon(pokemon, carrier) and is_in_active_spot(carrier) + card = PokemonCardDef( guid="bceaefbe-6be9-54ab-87a0-675ff554bf50", @@ -21,14 +32,14 @@ card = PokemonCardDef( abilities=[ Ability( title="Neutralizing Gas", - game_text="As long as this Pok\u00e9mon is in the Active Spot, your opponent's Pok\u00e9mon in play have no Abilities, except for Neutralizing Gas.", - effect=unimplemented, + game_text="As long as this Pokémon is in the Active Spot, your opponent's Pokémon in play have no Abilities, except for Neutralizing Gas.", + passive=ability_lock_passive(_neutralizing_gas_target), ), Attack( title="Severe Poison", - game_text="Your opponent's Active Pok\u00e9mon is now Poisoned. Put 4 damage counters instead of 1 on that Pok\u00e9mon during Pok\u00e9mon Checkup.", + game_text="Your opponent's Active Pokémon is now Poisoned. Put 4 damage counters instead of 1 on that Pokémon during Pokémon Checkup.", cost={PokemonTypes.DARKNESS: 1}, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED, counters=4), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH45/GreedentV_53.py b/spirit/game/scripts/cards/SWSH45/GreedentV_53.py index bd820b9..67aada9 100644 --- a/spirit/game/scripts/cards/SWSH45/GreedentV_53.py +++ b/spirit/game/scripts/cards/SWSH45/GreedentV_53.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import discard_opponent_energy_attack, flip_or_nothing card = PokemonCardDef( guid="fce25894-c26a-5c8f-8937-1f63dcb7b102", @@ -23,14 +24,14 @@ card = PokemonCardDef( game_text="Discard an Energy from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 2}, damage=40, - effect=unimplemented, + effect=discard_opponent_energy_attack(), ), Attack( title="Stumbling Press", game_text="Flip a coin. If tails, this attack does nothing.", cost={PokemonTypes.COLORLESS: 3}, damage=180, - effect=unimplemented, + effect=flip_or_nothing(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH45/Grookey_11.py b/spirit/game/scripts/cards/SWSH45/Grookey_11.py index 09a7180..0802c40 100644 --- a/spirit/game/scripts/cards/SWSH45/Grookey_11.py +++ b/spirit/game/scripts/cards/SWSH45/Grookey_11.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="d9d1bc07-fc99-507a-8a73-9266442f143c", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 1}, damage=10, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=3, per_heads=10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH45/GymTrainer_59.py b/spirit/game/scripts/cards/SWSH45/GymTrainer_59.py index 12f546a..3fa016b 100644 --- a/spirit/game/scripts/cards/SWSH45/GymTrainer_59.py +++ b/spirit/game/scripts/cards/SWSH45/GymTrainer_59.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.support_common import conditional_draw + +gym_trainer = conditional_draw(2, 2, lambda ctx: bool(ctx.kos_suffered_last_turn())) card = SupporterCardDef( guid="ec1ef86f-165b-5355-b53f-fdb557866c26", @@ -11,5 +14,5 @@ card = SupporterCardDef( collector_number=59, set_code="SWSH45", rarity=Rarities.Uncommon, - effect=unimplemented + effect=gym_trainer ) diff --git a/spirit/game/scripts/cards/SWSH45/GymTrainer_68.py b/spirit/game/scripts/cards/SWSH45/GymTrainer_68.py index e650416..83c58f8 100644 --- a/spirit/game/scripts/cards/SWSH45/GymTrainer_68.py +++ b/spirit/game/scripts/cards/SWSH45/GymTrainer_68.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.support_common import conditional_draw + +gym_trainer = conditional_draw(2, 2, lambda ctx: bool(ctx.kos_suffered_last_turn())) card = SupporterCardDef( guid="99df5e28-f9fa-5326-a077-508539873816", @@ -11,5 +14,5 @@ card = SupporterCardDef( collector_number=68, set_code="SWSH45", rarity=Rarities.RareUltra, - effect=unimplemented + effect=gym_trainer ) diff --git a/spirit/game/scripts/cards/SWSH45/IndeedeeV_39.py b/spirit/game/scripts/cards/SWSH45/IndeedeeV_39.py index d980fd3..82bb1eb 100644 --- a/spirit/game/scripts/cards/SWSH45/IndeedeeV_39.py +++ b/spirit/game/scripts/cards/SWSH45/IndeedeeV_39.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy + + +async def watch_over(ctx): + """Once during your turn, you may heal 20 damage from your Active Pokémon.""" + if await ctx.ask_yes_no("Heal 20 damage from your Active Pokémon?"): + await ctx.heal(20) + card = PokemonCardDef( guid="37c796ee-50cb-53c9-9088-6214e2371282", @@ -22,7 +30,8 @@ card = PokemonCardDef( Ability( title="Watch Over", game_text="Once during your turn, you may heal 20 damage from your Active Pok\u00e9mon.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + effect=watch_over, ), Attack( title="Psychic", @@ -30,7 +39,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 2}, damage=10, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("defender"), 60, base=10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH45/Indeedee_56.py b/spirit/game/scripts/cards/SWSH45/Indeedee_56.py index 03674e0..d117d9c 100644 --- a/spirit/game/scripts/cards/SWSH45/Indeedee_56.py +++ b/spirit/game/scripts/cards/SWSH45/Indeedee_56.py @@ -1,5 +1,25 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy +from spirit.game.card_effects.trainers import is_basic_energy_card + + +async def helping_hand(ctx): + """Search deck for a basic Energy card and attach it to a Benched Pokemon.""" + picks = await ctx.search_deck( + is_basic_energy_card, count=1, minimum=0, + prompt="Choose a basic Energy card to attach.", + ) + await ctx.shuffle_deck() + if not picks: + return + bench = ctx.my_bench() + if not bench: + return + target = await ctx.choose_pokemon(bench, "Choose a Benched Pokémon to attach the Energy to") + if target is not None: + await ctx.attach_energy(picks[0], target) + card = PokemonCardDef( guid="db0d3387-b717-5dcf-8dd3-afede27df122", @@ -22,7 +42,7 @@ card = PokemonCardDef( title="Helping Hand", game_text="Search your deck for a basic Energy card and attach it to 1 of your Benched Pok\u00e9mon. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=helping_hand, ), Attack( title="Psychic", @@ -30,7 +50,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=20, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("defender"), 20, base=20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH45/Koffing_41.py b/spirit/game/scripts/cards/SWSH45/Koffing_41.py index ce98895..2664a4b 100644 --- a/spirit/game/scripts/cards/SWSH45/Koffing_41.py +++ b/spirit/game/scripts/cards/SWSH45/Koffing_41.py @@ -1,6 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, evolves_from from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def ascension(ctx): + """Search for a card that evolves from this Pokémon and evolve it onto this Pokémon; shuffle.""" + picks = await ctx.search_deck( + lambda c: evolves_from(c.archetype_id, "Koffing"), + count=1, minimum=0, + prompt="Choose a card that evolves from Koffing.", + ) + if picks: + await ctx.evolve_pokemon(ctx.source, picks[0]) + await ctx.shuffle_deck() + + card = PokemonCardDef( guid="9e32b598-14c6-5cde-aa4c-9ece39ca8fa7", key="SWSH45", @@ -22,7 +35,7 @@ card = PokemonCardDef( title="Ascension", game_text="Search your deck for a card that evolves from this Pok\u00e9mon and put it onto this Pok\u00e9mon to evolve it. Then, shuffle your deck.", cost={PokemonTypes.DARKNESS: 1}, - effect=unimplemented, + effect=ascension, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH45/Kyogre_21.py b/spirit/game/scripts/cards/SWSH45/Kyogre_21.py index 66692a9..6ea28f9 100644 --- a/spirit/game/scripts/cards/SWSH45/Kyogre_21.py +++ b/spirit/game/scripts/cards/SWSH45/Kyogre_21.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_all_opponents card = PokemonCardDef( guid="6ad55229-24e6-58b4-9a35-b0ebbf2ff216", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Amazing Surge", game_text="This attack does 80 damage to each of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.WATER: 1, PokemonTypes.LIGHTNING: 1, PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=damage_all_opponents(80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH45/Luxio_32.py b/spirit/game/scripts/cards/SWSH45/Luxio_32.py index 8786161..e340d25 100644 --- a/spirit/game/scripts/cards/SWSH45/Luxio_32.py +++ b/spirit/game/scripts/cards/SWSH45/Luxio_32.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented, Triggers +from spirit.game.card_effects.pokemon import top_entry from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -21,8 +22,9 @@ card = PokemonCardDef( abilities=[ Ability( title="Top Entry", + trigger=Triggers.ON_TURN_DRAWN, game_text="Once during your turn, if you drew this Pok\u00e9mon from your deck at the beginning of your turn and your Bench isn't full, before you put it into your hand, you may put it onto your Bench.", - effect=unimplemented, + effect=top_entry(), ), Attack( title="Zap Kick", diff --git a/spirit/game/scripts/cards/SWSH45/Luxray_33.py b/spirit/game/scripts/cards/SWSH45/Luxray_33.py index c86328d..7505d1c 100644 --- a/spirit/game/scripts/cards/SWSH45/Luxray_33.py +++ b/spirit/game/scripts/cards/SWSH45/Luxray_33.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if + + +def _evolved_this_turn(ctx) -> bool: + state = ctx.session.turn_state + return state.entered_play_turn.get(ctx.attacker.entity_id) == state.turn_number + card = PokemonCardDef( guid="42ecd609-ca4a-5573-8397-4edcac08e6a8", @@ -25,7 +32,7 @@ card = PokemonCardDef( cost={PokemonTypes.LIGHTNING: 1}, damage=60, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_evolved_this_turn, 100), ), Attack( title="Head Bolt", diff --git a/spirit/game/scripts/cards/SWSH45/Manaphy_24.py b/spirit/game/scripts/cards/SWSH45/Manaphy_24.py index 12b1bac..0ac373a 100644 --- a/spirit/game/scripts/cards/SWSH45/Manaphy_24.py +++ b/spirit/game/scripts/cards/SWSH45/Manaphy_24.py @@ -1,5 +1,26 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_pokemon_card +from spirit.game.card_effects.pokemon import in_active_spot + + +async def ocean_search(ctx): + """You may look at the top 6 cards of your deck, reveal a Pokemon you + find there, and put it into your hand. Shuffle the rest back.""" + if not await ctx.ask_yes_no("Look at the top 6 cards of your deck?"): + return + top = ctx.deck_top(6) + candidates = [c for c in top if is_pokemon_card(c)] + picks = [] + if candidates: + picks = await ctx.choose_cards( + candidates, 1, minimum=0, + prompt="Choose a Pokémon to put into your hand.", + display_cards=top, + ) + await ctx.put_in_hand(picks, reveal=True) + await ctx.shuffle_deck() + card = PokemonCardDef( guid="e03d7ce7-cff1-5074-b980-7b57d8884816", @@ -21,7 +42,9 @@ card = PokemonCardDef( Ability( title="Ocean Search", game_text="Once during your turn, if this Pok\u00e9mon is in the Active Spot, you may look at the top 6 cards of your deck, reveal a Pok\u00e9mon you find there, and put it into your hand. Shuffle the other cards back into your deck.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=in_active_spot, + effect=ocean_search, ), Attack( title="Wave Splash", diff --git a/spirit/game/scripts/cards/SWSH45/MorpekoVMAX_38.py b/spirit/game/scripts/cards/SWSH45/MorpekoVMAX_38.py index 944ecd3..2af67a3 100644 --- a/spirit/game/scripts/cards/SWSH45/MorpekoVMAX_38.py +++ b/spirit/game/scripts/cards/SWSH45/MorpekoVMAX_38.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import spread_damage card = PokemonCardDef( guid="1d3d9134-27dd-5e42-99da-bfe219639d0a", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="This attack also does 20 damage to each of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 1}, damage=180, - effect=unimplemented, + effect=spread_damage(20, side="opponent", also_base=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH45/MorpekoV_37.py b/spirit/game/scripts/cards/SWSH45/MorpekoV_37.py index 10eaf3b..f954075 100644 --- a/spirit/game/scripts/cards/SWSH45/MorpekoV_37.py +++ b/spirit/game/scripts/cards/SWSH45/MorpekoV_37.py @@ -1,6 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import snipe_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def electro_wheel(ctx): + await ctx.deal_damage() + picks = await ctx.discard_energy_from(ctx.attacker, 1, prompt="Discard an Energy from this Pokémon.") + if not picks: + return + bench = ctx.my_bench() + if not bench: + return + target = await ctx.choose_pokemon(bench, "Choose your new Active Pokémon") + if target is not None: + await ctx.switch_active(ctx.player_id, target) + card = PokemonCardDef( guid="2d49019c-2d9f-53b1-92d0-688c6361a3c1", key="SWSH45", @@ -23,14 +37,14 @@ card = PokemonCardDef( game_text="This attack also does 20 damage to 1 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.COLORLESS: 1}, damage=20, - effect=unimplemented, + effect=snipe_attack(20, pool="bench", count=1, also_base=True), ), Attack( title="Electro Wheel", game_text="Discard an Energy from this Pok\u00e9mon. If you do, switch it with 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 1}, damage=150, - effect=unimplemented, + effect=electro_wheel, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH45/Morpeko_35.py b/spirit/game/scripts/cards/SWSH45/Morpeko_35.py index a624d3b..9d83856 100644 --- a/spirit/game/scripts/cards/SWSH45/Morpeko_35.py +++ b/spirit/game/scripts/cards/SWSH45/Morpeko_35.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.support_common import draw_attack card = PokemonCardDef( guid="89bbb29f-2a92-5fd7-a258-288daa1e06a6", @@ -22,14 +24,14 @@ card = PokemonCardDef( title="Famished", game_text="Draw a card.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=draw_attack(1), ), Attack( title="Thunder Shock", game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 1}, damage=40, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH45/Morpeko_36.py b/spirit/game/scripts/cards/SWSH45/Morpeko_36.py index f3a8d86..372cdf4 100644 --- a/spirit/game/scripts/cards/SWSH45/Morpeko_36.py +++ b/spirit/game/scripts/cards/SWSH45/Morpeko_36.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.support_common import draw_attack card = PokemonCardDef( guid="3da6a4c4-aee9-5eb7-8f50-9c58a79a6ba9", @@ -22,14 +24,14 @@ card = PokemonCardDef( title="Famished", game_text="Draw a card.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=draw_attack(1), ), Attack( title="Thunder Shock", game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 1}, damage=40, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH45/Nickit_47.py b/spirit/game/scripts/cards/SWSH45/Nickit_47.py index d87f78b..f682e08 100644 --- a/spirit/game/scripts/cards/SWSH45/Nickit_47.py +++ b/spirit/game/scripts/cards/SWSH45/Nickit_47.py @@ -1,6 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def instigate(ctx): + moved = await ctx.hand_to_bottom_of_deck(ctx.opponent_id) + if moved > 0: + await ctx.draw_cards(3, player_id=ctx.opponent_id) + + card = PokemonCardDef( guid="3918cd9c-ba25-514c-b0ef-cd361f9ad76c", key="SWSH45", @@ -22,7 +29,7 @@ card = PokemonCardDef( title="Instigate", game_text="Your opponent shuffles their hand and puts it on the bottom of their deck. If they put any cards on the bottom of their deck in this way, they draw 3 cards.", cost={PokemonTypes.DARKNESS: 1}, - effect=unimplemented, + effect=instigate, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH45/PokKid_70.py b/spirit/game/scripts/cards/SWSH45/PokKid_70.py index 3af1d48..8ed2deb 100644 --- a/spirit/game/scripts/cards/SWSH45/PokKid_70.py +++ b/spirit/game/scripts/cards/SWSH45/PokKid_70.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.session.effects import is_pokemon_card card = SupporterCardDef( guid="2308f25f-baff-599f-8cec-169cf0244c7d", @@ -11,5 +13,8 @@ card = SupporterCardDef( collector_number=70, set_code="SWSH45", rarity=Rarities.RareUltra, - effect=unimplemented + effect=search_to_hand( + is_pokemon_card, count=1, minimum=0, reveal=True, + prompt="Choose a Pokémon to put into your hand.", + ) ) diff --git a/spirit/game/scripts/cards/SWSH45/Reshiram_17.py b/spirit/game/scripts/cards/SWSH45/Reshiram_17.py index 5907292..c214c23 100644 --- a/spirit/game/scripts/cards/SWSH45/Reshiram_17.py +++ b/spirit/game/scripts/cards/SWSH45/Reshiram_17.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack card = PokemonCardDef( guid="7f078ac8-d988-5e3a-a311-5a2de6b19880", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="This Pok\u00e9mon also does 60 damage to itself.", cost={PokemonTypes.FIRE: 1, PokemonTypes.LIGHTNING: 1, PokemonTypes.DARKNESS: 1}, damage=270, - effect=unimplemented, + effect=recoil_attack(60), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH45/Rillaboom_13.py b/spirit/game/scripts/cards/SWSH45/Rillaboom_13.py index 4a1def4..0d12548 100644 --- a/spirit/game/scripts/cards/SWSH45/Rillaboom_13.py +++ b/spirit/game/scripts/cards/SWSH45/Rillaboom_13.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.trainers import is_grass_energy_card +from spirit.game.card_effects.support_common import search_attach_energy card = PokemonCardDef( guid="3763b712-bdeb-5e81-ac86-9a848102c00d", @@ -22,7 +24,8 @@ card = PokemonCardDef( Ability( title="Voltage Beat", game_text="Once during your turn, you may search your deck for up to 2 Grass Energy cards and attach them to 1 of your Pok\u00e9mon. Then, shuffle your deck.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + effect=search_attach_energy(predicate=is_grass_energy_card, count=2, distribute=False), ), Attack( title="Hammer In", diff --git a/spirit/game/scripts/cards/SWSH45/Rotom_34.py b/spirit/game/scripts/cards/SWSH45/Rotom_34.py index faf19ea..7d9df01 100644 --- a/spirit/game/scripts/cards/SWSH45/Rotom_34.py +++ b/spirit/game/scripts/cards/SWSH45/Rotom_34.py @@ -1,5 +1,28 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers, def_for +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.session.effects import is_item_card + + +def _is_rotom_item(card): + if not is_item_card(card): + return False + definition = def_for(card.archetype_id) + name = getattr(definition, "display_name", None) or "" + return "rotom" in name.lower() + + +async def roto_choice(ctx): + if not await ctx.ask_yes_no( + "Search your deck for up to 2 Item cards that have the word " + "\"Rotom\" in their name?"): + return + picks = await ctx.search_deck( + _is_rotom_item, count=2, minimum=0, + prompt="Choose up to 2 Item cards with \"Rotom\" in their name.", + ) + await ctx.put_in_hand(picks, reveal=True) + await ctx.shuffle_deck() card = PokemonCardDef( guid="c3ce1412-65f6-556c-a148-09658e79cc26", @@ -21,14 +44,15 @@ card = PokemonCardDef( Ability( title="Roto Choice", game_text="When you play this Pok\u00e9mon from your hand onto your Bench during your turn, you may search your deck for up to 2 Item cards that have the word \"Rotom\" in their name, reveal them, and put them into your hand. Then, shuffle your deck.", - effect=unimplemented, + trigger=Triggers.ON_PLAY, + effect=roto_choice, ), Attack( title="Thunder Shock", game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH45/Rowlet_6.py b/spirit/game/scripts/cards/SWSH45/Rowlet_6.py index 824105e..70e82a8 100644 --- a/spirit/game/scripts/cards/SWSH45/Rowlet_6.py +++ b/spirit/game/scripts/cards/SWSH45/Rowlet_6.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.passives import Passive +from spirit.game.card_effects.attacks_common import snipe_attack + + +class _SkyCircusPassive(Passive): + def modify_attack_cost(self, cost, pokemon, carrier, board): + if pokemon is not carrier: + return cost + state = getattr(board, "turn_state", None) + if state and any(name == "Bird Keeper" for _, name, _ in state.trainers_played): + return {} + return cost + card = PokemonCardDef( guid="e3f03052-f33f-5e1a-a3b6-599fbbe145c5", @@ -21,13 +34,13 @@ card = PokemonCardDef( Ability( title="Sky Circus", game_text="If you played Bird Keeper from your hand during this turn, ignore all Energy in this Pok\u00e9mon's attack costs.", - effect=unimplemented, + passive=_SkyCircusPassive(), ), Attack( title="Wind Shard", game_text="This attack does 60 damage to 1 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.COLORLESS: 3}, - effect=unimplemented, + effect=snipe_attack(60, pool="bench"), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH45/RustedShield_61.py b/spirit/game/scripts/cards/SWSH45/RustedShield_61.py index 5666d1b..66ed87e 100644 --- a/spirit/game/scripts/cards/SWSH45/RustedShield_61.py +++ b/spirit/game/scripts/cards/SWSH45/RustedShield_61.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import PokemonToolCardDef +from spirit.game.attributes import Rarities, AttrID +from spirit.game.card_effects.passives_common import hp_bonus_tool + +_is_zamazenta_v = lambda p: p.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) == "ZamazentaV" card = PokemonToolCardDef( guid="4a70d861-2878-5513-ba41-867d9cd5e4ea", @@ -11,5 +14,5 @@ card = PokemonToolCardDef( collector_number=61, set_code="SWSH45", rarity=Rarities.Uncommon, - effect=unimplemented + passive=hp_bonus_tool(70, holder_pred=_is_zamazenta_v), ) diff --git a/spirit/game/scripts/cards/SWSH45/RustedSword_62.py b/spirit/game/scripts/cards/SWSH45/RustedSword_62.py index f67525e..58d4116 100644 --- a/spirit/game/scripts/cards/SWSH45/RustedSword_62.py +++ b/spirit/game/scripts/cards/SWSH45/RustedSword_62.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import PokemonToolCardDef +from spirit.game.attributes import Rarities, AttrID +from spirit.game.session.passives import Passive, carrier_pokemon + + +class _RustedSwordPassive(Passive): + """+30 damage to the opponent's Active from the attached Zacian V.""" + + def modify_damage_dealt(self, calc, carrier): + holder = carrier_pokemon(carrier) + if ( + calc.is_attack + and calc.is_opposing + and calc.to_active + and holder is calc.attacker + and holder.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) == "ZacianV" + ): + calc.amount += 30 + card = PokemonToolCardDef( guid="4a0e1002-2542-58e5-83e6-9281a0ccf4e8", @@ -11,5 +28,5 @@ card = PokemonToolCardDef( collector_number=62, set_code="SWSH45", rarity=Rarities.Uncommon, - effect=unimplemented + passive=_RustedSwordPassive(), ) diff --git a/spirit/game/scripts/cards/SWSH45/Shinx_31.py b/spirit/game/scripts/cards/SWSH45/Shinx_31.py index 6e03e9d..c405d59 100644 --- a/spirit/game/scripts/cards/SWSH45/Shinx_31.py +++ b/spirit/game/scripts/cards/SWSH45/Shinx_31.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, opponent_prizes_taken_at_least card = PokemonCardDef( guid="06cc923f-51c5-577e-b3bc-993874f77189", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.LIGHTNING: 1}, damage=10, damage_operator="+", - effect=unimplemented, + effect=bonus_if(opponent_prizes_taken_at_least(3), 50), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH45/Skyla_72.py b/spirit/game/scripts/cards/SWSH45/Skyla_72.py index d3afb59..e5c9fff 100644 --- a/spirit/game/scripts/cards/SWSH45/Skyla_72.py +++ b/spirit/game/scripts/cards/SWSH45/Skyla_72.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.session.effects import is_trainer_card +from spirit.game.card_effects.support_common import search_to_hand card = SupporterCardDef( guid="41e1b8be-d7c3-5909-837b-321423cc335c", @@ -11,5 +13,5 @@ card = SupporterCardDef( collector_number=72, set_code="SWSH45", rarity=Rarities.RareUltra, - effect=unimplemented + effect=search_to_hand(is_trainer_card, count=1, reveal=True, prompt="Choose a Trainer card."), ) diff --git a/spirit/game/scripts/cards/SWSH45/Snom_29.py b/spirit/game/scripts/cards/SWSH45/Snom_29.py index 05270f9..aa1d5a5 100644 --- a/spirit/game/scripts/cards/SWSH45/Snom_29.py +++ b/spirit/game/scripts/cards/SWSH45/Snom_29.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_bench + +call_for_family = search_to_bench() card = PokemonCardDef( guid="a2028a69-bb2c-547e-90e4-9e7b2a12deff", @@ -22,7 +25,7 @@ card = PokemonCardDef( title="Call for Family", game_text="Search your deck for a Basic Pok\u00e9mon and put it onto your Bench. Then, shuffle your deck.", cost={PokemonTypes.WATER: 1}, - effect=unimplemented, + effect=call_for_family, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH45/TeamYellTowel_63.py b/spirit/game/scripts/cards/SWSH45/TeamYellTowel_63.py index f7bbf6b..fac5728 100644 --- a/spirit/game/scripts/cards/SWSH45/TeamYellTowel_63.py +++ b/spirit/game/scripts/cards/SWSH45/TeamYellTowel_63.py @@ -1,5 +1,29 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import ItemCardDef +from spirit.game.attributes import AttrID, Rarities +from spirit.game.session.passives import effective_max_hp + + +def _is_damaged(board, pokemon): + return pokemon is not None and \ + pokemon.get_attribute(AttrID.HP, 0) < effective_max_hp(board, pokemon) + + +def _team_yell_towel_condition(board, player_id): + opponent = next((p for p in board.player_ids if p != player_id), None) + mine = board.active_pokemon(player_id) + theirs = board.active_pokemon(opponent) if opponent else None + return _is_damaged(board, mine) or _is_damaged(board, theirs) + + +async def team_yell_towel(ctx): + """Heal 50 damage from both Active Pokemon.""" + my_active = ctx.my_active() + if my_active is not None: + await ctx.heal(50, target=my_active) + opponent_active = ctx.opponent_active() + if opponent_active is not None: + await ctx.heal(50, target=opponent_active) + card = ItemCardDef( guid="979176fc-395b-5e8e-8439-68786787763b", @@ -11,5 +35,6 @@ card = ItemCardDef( collector_number=63, set_code="SWSH45", rarity=Rarities.Uncommon, - effect=unimplemented + effect=team_yell_towel, + condition=_team_yell_towel_condition, ) diff --git a/spirit/game/scripts/cards/SWSH45/Thievul_48.py b/spirit/game/scripts/cards/SWSH45/Thievul_48.py index 86e374c..53eb512 100644 --- a/spirit/game/scripts/cards/SWSH45/Thievul_48.py +++ b/spirit/game/scripts/cards/SWSH45/Thievul_48.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_bench +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="36e39408-5f2e-57b9-9253-3025b2a50009", @@ -23,14 +25,14 @@ card = PokemonCardDef( title="Call for Goons", game_text="Search your deck for up to 3 Basic Pok\u00e9mon and put them onto your Bench. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_to_bench(count=3), ), Attack( title="Corner", game_text="During your opponent's next turn, the Defending Pok\u00e9mon can't retreat.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 2}, damage=80, - effect=unimplemented, + effect=condition_attack(no_retreat=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH45/Thwackey_12.py b/spirit/game/scripts/cards/SWSH45/Thwackey_12.py index 673e1c3..29e575e 100644 --- a/spirit/game/scripts/cards/SWSH45/Thwackey_12.py +++ b/spirit/game/scripts/cards/SWSH45/Thwackey_12.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.passives import Passive + + +class LayOfTheLandPassive(Passive): + def modify_retreat_cost(self, cost, pokemon, carrier, board): + if pokemon is not carrier: + return cost + area = board.find_global_area("activeStadium") + if not (area and area.children): + return cost + return 0 + card = PokemonCardDef( guid="77fb9e8a-b528-5742-82c1-995e38fcb067", @@ -22,7 +34,7 @@ card = PokemonCardDef( Ability( title="Lay of the Land", game_text="If you have a Stadium in play, this Pok\u00e9mon has no Retreat Cost.", - effect=unimplemented, + passive=LayOfTheLandPassive(), ), Attack( title="Branch Poke", diff --git a/spirit/game/scripts/cards/SWSH45/Trapinch_40.py b/spirit/game/scripts/cards/SWSH45/Trapinch_40.py index f90a74b..6d201cd 100644 --- a/spirit/game/scripts/cards/SWSH45/Trapinch_40.py +++ b/spirit/game/scripts/cards/SWSH45/Trapinch_40.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if card = PokemonCardDef( guid="10ffdf6a-6dd7-5190-9742-e9e56b0ef40c", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1}, damage=10, damage_operator="+", - effect=unimplemented, + effect=bonus_if(lambda ctx: ctx.stadium_in_play() is not None, 10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH45/Tropius_5.py b/spirit/game/scripts/cards/SWSH45/Tropius_5.py index 601b5ca..fbe4ca7 100644 --- a/spirit/game/scripts/cards/SWSH45/Tropius_5.py +++ b/spirit/game/scripts/cards/SWSH45/Tropius_5.py @@ -1,5 +1,24 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.trainers import is_grass_energy_card +from spirit.game.card_effects.support_common import distribute_energy + + +async def attach_leaves(ctx): + bench = ctx.my_bench() + if not bench: + return + cards = [c for c in ctx.discard_pile() if is_grass_energy_card(c)] + if not cards: + return + picks = await ctx.choose_cards( + cards, 2, minimum=0, + prompt="Choose up to 2 Grass Energy cards to attach to your Benched Pokémon", + ) + if not picks: + return + await distribute_energy(ctx, picks, bench) + card = PokemonCardDef( guid="93ae08e5-6541-5b28-9c6b-99a77d28f1d7", @@ -22,7 +41,7 @@ card = PokemonCardDef( title="Attach Leaves", game_text="Attach up to 2 Grass Energy cards from your discard pile to your Benched Pok\u00e9mon in any way you like.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=attach_leaves, ), Attack( title="Gust", diff --git a/spirit/game/scripts/cards/SWSH45/Volcanion_25.py b/spirit/game/scripts/cards/SWSH45/Volcanion_25.py index bb9d646..09a786f 100644 --- a/spirit/game/scripts/cards/SWSH45/Volcanion_25.py +++ b/spirit/game/scripts/cards/SWSH45/Volcanion_25.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="780674b4-372b-5241-b480-f9f01276b8ef", @@ -29,7 +30,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 3, PokemonTypes.COLORLESS: 1}, damage=120, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=2, per_heads=120), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH45/Yanmega_2.py b/spirit/game/scripts/cards/SWSH45/Yanmega_2.py index 5e3b53a..84ba668 100644 --- a/spirit/game/scripts/cards/SWSH45/Yanmega_2.py +++ b/spirit/game/scripts/cards/SWSH45/Yanmega_2.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack card = PokemonCardDef( guid="ddc11dde-ae01-51b7-b894-8ae3e9b8d54b", @@ -26,10 +27,10 @@ card = PokemonCardDef( ), Attack( title="Nosedive", - game_text="This Pok\u00e9mon also does 30 damage to itself.", + game_text="This Pokémon also does 30 damage to itself.", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + effect=recoil_attack(30), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH45/Zarude_16.py b/spirit/game/scripts/cards/SWSH45/Zarude_16.py index 67017a0..322a116 100644 --- a/spirit/game/scripts/cards/SWSH45/Zarude_16.py +++ b/spirit/game/scripts/cards/SWSH45/Zarude_16.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import heal_attack card = PokemonCardDef( guid="442b61c8-b151-5eb2-ad59-8f79ebd6d951", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Heal 30 damage from this Pok\u00e9mon.", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=heal_attack(30, target="self"), ), Attack( title="Jungle Blast", diff --git a/spirit/game/scripts/cards/SWSH5/Aegislash_107.py b/spirit/game/scripts/cards/SWSH5/Aegislash_107.py index 1fc08e3..8372033 100644 --- a/spirit/game/scripts/cards/SWSH5/Aegislash_107.py +++ b/spirit/game/scripts/cards/SWSH5/Aegislash_107.py @@ -1,4 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import self_energy_discard_attack +from spirit.game.card_effects.pokemon import stance_change, stance_change_condition +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -23,14 +25,16 @@ card = PokemonCardDef( Ability( title="Stance Change", game_text="Once during your turn, you may switch this Pok\u00e9mon with an Aegislash in your hand. Any attached cards, damage counters, Special Conditions, turns in play, and any other effects remain on the new Pok\u00e9mon.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=stance_change_condition, + effect=stance_change, ), Attack( title="Full Metal Blade", game_text="Discard 2 Metal Energy from this Pok\u00e9mon.", cost={PokemonTypes.METAL: 2, PokemonTypes.COLORLESS: 1}, damage=210, - effect=unimplemented, + effect=self_energy_discard_attack(count=2, energy_type=PokemonTypes.METAL), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/Aegislash_108.py b/spirit/game/scripts/cards/SWSH5/Aegislash_108.py index 7abeee7..db8895b 100644 --- a/spirit/game/scripts/cards/SWSH5/Aegislash_108.py +++ b/spirit/game/scripts/cards/SWSH5/Aegislash_108.py @@ -1,6 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.passives_common import prevent_damage_when +from spirit.game.card_effects.pokemon import ( + is_pokemon_vmax, stance_change, stance_change_condition, +) +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def gigaton_bash(ctx): + """70; next turn, prevent all damage done to this Pokemon by VMAX attacks.""" + await ctx.deal_damage() + shield = prevent_damage_when( + lambda calc, c: calc.target is c and calc.attacker is not None + and is_pokemon_vmax(calc.attacker.archetype_id) + ) + ctx.add_passive_through_opponents_turn(ctx.attacker, shield) + card = PokemonCardDef( guid="e280a8c7-5005-5e52-ac53-e88293377075", key="SWSH5", @@ -23,14 +37,16 @@ card = PokemonCardDef( Ability( title="Stance Change", game_text="Once during your turn, you may switch this Pok\u00e9mon with an Aegislash in your hand. Any attached cards, damage counters, Special Conditions, turns in play, and any other effects remain on the new Pok\u00e9mon.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=stance_change_condition, + effect=stance_change, ), Attack( title="Gigaton Bash", game_text="During your opponent's next turn, prevent all damage done to this Pok\u00e9mon by attacks from Pok\u00e9mon VMAX.", cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 1}, damage=70, - effect=unimplemented, + effect=gigaton_bash, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/Baltoy_57.py b/spirit/game/scripts/cards/SWSH5/Baltoy_57.py index f6f435a..0c9f3bf 100644 --- a/spirit/game/scripts/cards/SWSH5/Baltoy_57.py +++ b/spirit/game/scripts/cards/SWSH5/Baltoy_57.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack card = PokemonCardDef( guid="512ff60a-fcbe-5777-b1af-daddce05888f", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="This Pok\u00e9mon also does 60 damage to itself.", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=60, - effect=unimplemented, + effect=recoil_attack(60), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/Bellsprout_1.py b/spirit/game/scripts/cards/SWSH5/Bellsprout_1.py index 883b39e..2dfb0ed 100644 --- a/spirit/game/scripts/cards/SWSH5/Bellsprout_1.py +++ b/spirit/game/scripts/cards/SWSH5/Bellsprout_1.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, defender_has_condition +from spirit.game.data_utils import PokemonCardDef, Attack +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions card = PokemonCardDef( guid="acb0ca84-8b3e-5ce1-8c0e-95384b203a08", @@ -20,11 +21,11 @@ card = PokemonCardDef( abilities=[ Attack( title="Venoshock", - game_text="If your opponent's Active Pok\u00e9mon is Poisoned, this attack does 40 more damage.", + game_text="If your opponent's Active Pokémon is Poisoned, this attack does 40 more damage.", cost={PokemonTypes.COLORLESS: 1}, damage=10, damage_operator="+", - effect=unimplemented, + effect=bonus_if(defender_has_condition(SpecialConditions.POISONED), 40), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH5/Bisharp_104.py b/spirit/game/scripts/cards/SWSH5/Bisharp_104.py index c2a70a7..3675280 100644 --- a/spirit/game/scripts/cards/SWSH5/Bisharp_104.py +++ b/spirit/game/scripts/cards/SWSH5/Bisharp_104.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.attacks_common import bonus_if, active_is + +_is_metal = lambda p: PokemonTypes.METAL.value in (p.get_attribute(AttrID.POKEMON_TYPES) or []) card = PokemonCardDef( guid="d0b9de5e-00ae-5081-bf9c-9ce431db2ed4", @@ -31,7 +34,7 @@ card = PokemonCardDef( cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 2}, damage=90, damage_operator="+", - effect=unimplemented, + effect=bonus_if(active_is(_is_metal), 90), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/Boltund_53.py b/spirit/game/scripts/cards/SWSH5/Boltund_53.py index ece08e6..fe7fa6a 100644 --- a/spirit/game/scripts/cards/SWSH5/Boltund_53.py +++ b/spirit/game/scripts/cards/SWSH5/Boltund_53.py @@ -1,5 +1,30 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.session.passives import Passive, effective_max_hp + + +class DefiantSparkAltCostPassive(Passive): + """Defiant Spark can be paid with a single [L] once this Pokémon has + any damage counters on it.""" + + def modify_attack_cost(self, cost, pokemon, carrier, board): + if pokemon is not carrier: + return cost + if cost.get("Lightning") == 2 and cost.get("Colorless") == 1 and len(cost) == 2: + current = pokemon.get_attribute(AttrID.HP, 0) + if current < effective_max_hp(board, pokemon): + return {"Lightning": 1} + return cost + + +async def corner(ctx): + """30 damage. During your opponent's next turn, the Defending Pokémon + can't retreat.""" + await ctx.deal_damage() + defender = ctx.defender + if defender is not None and not ctx.effects_blocked(defender): + ctx.lock_retreat(defender) + card = PokemonCardDef( guid="519af55e-cc74-52eb-9acc-6ee02ce55067", @@ -18,20 +43,20 @@ card = PokemonCardDef( weakness_type=PokemonTypes.FIGHTING, evolves_from="com.direwolfdigital.cake.data.archetypes.pokemon.Yamper.Name", family_id=835, + passive=DefiantSparkAltCostPassive(), abilities=[ Attack( title="Corner", game_text="During your opponent's next turn, the Defending Pok\u00e9mon can't retreat.", cost={PokemonTypes.LIGHTNING: 1}, damage=30, - effect=unimplemented, + effect=corner, ), Attack( title="Defiant Spark", game_text="If this Pok\u00e9mon has any damage counters on it, this attack can be used for Lightning.", cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 1}, damage=130, - effect=unimplemented, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/Bouffalant_118.py b/spirit/game/scripts/cards/SWSH5/Bouffalant_118.py index 0cb3f10..8779d40 100644 --- a/spirit/game/scripts/cards/SWSH5/Bouffalant_118.py +++ b/spirit/game/scripts/cards/SWSH5/Bouffalant_118.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack +from spirit.game.card_effects.passives_common import typed_damage_boost_tool card = PokemonCardDef( guid="5d16a1d8-6f74-55d8-af7f-9e99aa4f9ae5", @@ -21,14 +23,14 @@ card = PokemonCardDef( Ability( title="Sap Sipper", game_text="This Pok\u00e9mon's attacks do 60 more damage to your opponent's Grass Pok\u00e9mon (before applying Weakness and Resistance).", - effect=unimplemented, + passive=typed_damage_boost_tool(PokemonTypes.GRASS, 60, to_active_only=False), ), Attack( title="Head Charge", game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.COLORLESS: 3}, damage=120, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/Bronzong_102.py b/spirit/game/scripts/cards/SWSH5/Bronzong_102.py index 3eb3dfe..d6660cd 100644 --- a/spirit/game/scripts/cards/SWSH5/Bronzong_102.py +++ b/spirit/game/scripts/cards/SWSH5/Bronzong_102.py @@ -1,5 +1,40 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import energy_provides_type + + +def _metal_transfer_condition(board, player_id, pokemon): + pkmn = board.pokemon_in_play(player_id) + if len(pkmn) < 2: + return False + return any( + energy_provides_type(e, PokemonTypes.METAL) + for p in pkmn for e in board.attached_energies(p) + ) + + +async def metal_transfer(ctx): + pool = [ + (e, p) for p in ctx.my_pokemon_in_play() + for e in ctx.attached_energies(p) + if energy_provides_type(e, PokemonTypes.METAL) + ] + if not pool: + return + picked = await ctx.choose_cards( + [e for e, _ in pool], 1, prompt="Choose a Metal Energy to move" + ) + if not picked: + return + energy = picked[0] + holder = next(p for e, p in pool if e is energy) + targets = [p for p in ctx.my_pokemon_in_play() if p is not holder] + if not targets: + return + target = await ctx.choose_pokemon(targets, "Choose a Pokémon to move the Energy to") + if target is not None: + await ctx.move_energy(energy, target) + card = PokemonCardDef( guid="7df7a5df-48ff-5e06-a961-f5e068a186df", @@ -23,7 +58,9 @@ card = PokemonCardDef( Ability( title="Metal Transfer", game_text="As often as you like during your turn, you may move a Metal Energy from 1 of your Pok\u00e9mon to another of your Pok\u00e9mon.", - effect=unimplemented, + activation=Activations.UNLIMITED, + condition=_metal_transfer_condition, + effect=metal_transfer, ), Attack( title="Zen Headbutt", diff --git a/spirit/game/scripts/cards/SWSH5/Bruno_121.py b/spirit/game/scripts/cards/SWSH5/Bruno_121.py index 40936e3..b00767a 100644 --- a/spirit/game/scripts/cards/SWSH5/Bruno_121.py +++ b/spirit/game/scripts/cards/SWSH5/Bruno_121.py @@ -1,6 +1,14 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities + +async def bruno(ctx): + """Shuffle hand into deck; draw 4, or 7 if a Pokémon of yours was Knocked Out during the opponent's last turn.""" + await ctx.shuffle_into_deck(ctx.hand(), ctx.player_id) + count = 7 if ctx.kos_suffered_last_turn() else 4 + await ctx.draw_cards(count) + + card = SupporterCardDef( guid="c6132912-8c6c-5334-b685-9bd05a5f1e41", key="SWSH5", @@ -11,5 +19,5 @@ card = SupporterCardDef( collector_number=121, set_code="SWSH5", rarity=Rarities.Uncommon, - effect=unimplemented + effect=bruno, ) diff --git a/spirit/game/scripts/cards/SWSH5/Bruno_158.py b/spirit/game/scripts/cards/SWSH5/Bruno_158.py index 2985c67..6848ea5 100644 --- a/spirit/game/scripts/cards/SWSH5/Bruno_158.py +++ b/spirit/game/scripts/cards/SWSH5/Bruno_158.py @@ -1,6 +1,14 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities + +async def bruno(ctx): + """Shuffle hand into deck; draw 4, or 7 if a Pokémon of yours was Knocked Out during the opponent's last turn.""" + await ctx.shuffle_into_deck(ctx.hand(), ctx.player_id) + count = 7 if ctx.kos_suffered_last_turn() else 4 + await ctx.draw_cards(count) + + card = SupporterCardDef( guid="64ffaa7b-3b51-5255-9d54-fca0f339e064", key="SWSH5", @@ -11,5 +19,5 @@ card = SupporterCardDef( collector_number=158, set_code="SWSH5", rarity=Rarities.RareUltra, - effect=unimplemented + effect=bruno, ) diff --git a/spirit/game/scripts/cards/SWSH5/Bruno_172.py b/spirit/game/scripts/cards/SWSH5/Bruno_172.py index f1f296a..6cf9fa3 100644 --- a/spirit/game/scripts/cards/SWSH5/Bruno_172.py +++ b/spirit/game/scripts/cards/SWSH5/Bruno_172.py @@ -1,6 +1,14 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities + +async def bruno(ctx): + """Shuffle hand into deck; draw 4, or 7 if a Pokémon of yours was Knocked Out during the opponent's last turn.""" + await ctx.shuffle_into_deck(ctx.hand(), ctx.player_id) + count = 7 if ctx.kos_suffered_last_turn() else 4 + await ctx.draw_cards(count) + + card = SupporterCardDef( guid="8f51a54b-5f6e-57e1-9685-3e491a00c693", key="SWSH5", @@ -11,5 +19,5 @@ card = SupporterCardDef( collector_number=172, set_code="SWSH5", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=bruno, ) diff --git a/spirit/game/scripts/cards/SWSH5/Cacturne_5.py b/spirit/game/scripts/cards/SWSH5/Cacturne_5.py index b5782bc..0a9b647 100644 --- a/spirit/game/scripts/cards/SWSH5/Cacturne_5.py +++ b/spirit/game/scripts/cards/SWSH5/Cacturne_5.py @@ -1,6 +1,10 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import bonus_if, count_energy +from spirit.game.card_effects.support_common import gust_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +_darkness_attached = count_energy("self", energy_type=PokemonTypes.DARKNESS) + card = PokemonCardDef( guid="ce08952d-0cbc-5b90-ab30-e695c040907a", key="SWSH5", @@ -23,7 +27,7 @@ card = PokemonCardDef( title="Pull", game_text="Switch 1 of your opponent's Benched Pok\u00e9mon with their Active Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=gust_attack(), ), Attack( title="Spiny Punch", @@ -31,7 +35,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=60, damage_operator="+", - effect=unimplemented, + effect=bonus_if(lambda ctx: _darkness_attached(ctx) > 0, 70), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/CampingGear_122.py b/spirit/game/scripts/cards/SWSH5/CampingGear_122.py index cda6c70..40b740b 100644 --- a/spirit/game/scripts/cards/SWSH5/CampingGear_122.py +++ b/spirit/game/scripts/cards/SWSH5/CampingGear_122.py @@ -1,6 +1,18 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef from spirit.game.attributes import Rarities + +async def camping_gear(ctx): + """Search your deck for a card and put it into your hand, then shuffle. + End the turn.""" + picks = await ctx.search_deck( + count=1, minimum=0, prompt="Choose a card to put into your hand.", + ) + await ctx.put_in_hand(picks, reveal=False) + await ctx.shuffle_deck() + ctx.ends_turn = True + + card = ItemCardDef( guid="d4ec25d9-0922-5db8-87f1-0fe99c66e430", key="SWSH5", @@ -11,5 +23,5 @@ card = ItemCardDef( collector_number=122, set_code="SWSH5", rarity=Rarities.Uncommon, - effect=unimplemented + effect=camping_gear ) diff --git a/spirit/game/scripts/cards/SWSH5/Carkol_79.py b/spirit/game/scripts/cards/SWSH5/Carkol_79.py index 5136b3e..eb59068 100644 --- a/spirit/game/scripts/cards/SWSH5/Carkol_79.py +++ b/spirit/game/scripts/cards/SWSH5/Carkol_79.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack card = PokemonCardDef( guid="895c1576-4cee-5c73-a9a3-05d9c7fae233", @@ -29,7 +30,7 @@ card = PokemonCardDef( game_text="This Pok\u00e9mon also does 10 damage to itself.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 3}, damage=100, - effect=unimplemented, + effect=recoil_attack(10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/Carnivine_9.py b/spirit/game/scripts/cards/SWSH5/Carnivine_9.py index 8bf0e87..2e561a5 100644 --- a/spirit/game/scripts/cards/SWSH5/Carnivine_9.py +++ b/spirit/game/scripts/cards/SWSH5/Carnivine_9.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import condition_attack, flip_damage card = PokemonCardDef( guid="80f18789-1d99-5768-9806-a6684539280e", @@ -20,10 +21,10 @@ card = PokemonCardDef( abilities=[ Attack( title="Big Bite", - game_text="During your opponent's next turn, the Defending Pok\u00e9mon can't retreat.", + game_text="During your opponent's next turn, the Defending Pokémon can't retreat.", cost={PokemonTypes.COLORLESS: 2}, damage=30, - effect=unimplemented, + effect=condition_attack(no_retreat=True), ), Attack( title="Triple Whip", @@ -31,7 +32,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 2}, damage=60, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=3, per_heads=60), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH5/Centiskorch_30.py b/spirit/game/scripts/cards/SWSH5/Centiskorch_30.py index 3246b5a..13b5a10 100644 --- a/spirit/game/scripts/cards/SWSH5/Centiskorch_30.py +++ b/spirit/game/scripts/cards/SWSH5/Centiskorch_30.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.session.passives import Passive + + +class OverheaterPassive(Passive): + def blocks_burn_recovery(self, pokemon, carrier): + return pokemon.owning_player_id != carrier.owning_player_id + card = PokemonCardDef( guid="f61cb59b-d8ab-5de7-924e-ea9da902a205", @@ -21,15 +29,15 @@ card = PokemonCardDef( abilities=[ Ability( title="Overheater", - game_text="Whenever your opponent flips a coin for their Burned Pok\u00e9mon during Pok\u00e9mon Checkup, it doesn't recover from that Special Condition even if the result is heads.", - effect=unimplemented, + game_text="Whenever your opponent flips a coin for their Burned Pokémon during Pokémon Checkup, it doesn't recover from that Special Condition even if the result is heads.", + passive=OverheaterPassive(), ), Attack( title="Bursting Inferno", - game_text="Your opponent's Active Pok\u00e9mon is now Burned.", + game_text="Your opponent's Active Pokémon is now Burned.", cost={PokemonTypes.FIRE: 3, PokemonTypes.COLORLESS: 1}, damage=130, - effect=unimplemented, + effect=condition_attack(SpecialConditions.BURNED), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH5/Cherrim_8.py b/spirit/game/scripts/cards/SWSH5/Cherrim_8.py index 21dff8c..967a931 100644 --- a/spirit/game/scripts/cards/SWSH5/Cherrim_8.py +++ b/spirit/game/scripts/cards/SWSH5/Cherrim_8.py @@ -1,5 +1,33 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations, has_rule_box from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.trainers import is_grass_energy_card + + +def _spring_bloom_condition(board, player_id, pokemon): + hand = board.find_player_area(player_id, "hand") + if not hand or not any(is_grass_energy_card(c) for c in hand.children): + return False + return any(not has_rule_box(p.archetype_id) for p in board.pokemon_in_play(player_id)) + + +async def spring_bloom(ctx): + hand_energies = [c for c in ctx.hand() if is_grass_energy_card(c)] + if not hand_energies: + return + targets = [p for p in ctx.my_pokemon_in_play() if not has_rule_box(p.archetype_id)] + if not targets: + return + picked = await ctx.choose_cards( + hand_energies, 1, prompt="Choose a Grass Energy card to attach" + ) + if not picked: + return + target = await ctx.choose_pokemon( + targets, "Choose a Pokémon without a Rule Box to attach the Energy to" + ) + if target is not None: + await ctx.attach_energy(picked[0], target) + card = PokemonCardDef( guid="48e717c5-8281-53aa-831e-ce4d059a83fd", @@ -22,7 +50,9 @@ card = PokemonCardDef( Ability( title="Spring Bloom", game_text="As often as you like during your turn, you may attach a Grass Energy card from your hand to 1 of your Pok\u00e9mon that doesn't have a Rule Box (Pok\u00e9mon V, Pok\u00e9mon-GX, etc. have Rule Boxes).", - effect=unimplemented, + activation=Activations.UNLIMITED, + condition=_spring_bloom_condition, + effect=spring_bloom, ), Attack( title="Seed Bomb", diff --git a/spirit/game/scripts/cards/SWSH5/Cheryl_123.py b/spirit/game/scripts/cards/SWSH5/Cheryl_123.py index da27918..1e902b1 100644 --- a/spirit/game/scripts/cards/SWSH5/Cheryl_123.py +++ b/spirit/game/scripts/cards/SWSH5/Cheryl_123.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import SupporterCardDef +from spirit.game.attributes import AttrID, Rarities +from spirit.game.session.effects import is_evolution_pokemon + + +async def cheryl(ctx): + """Heal all damage from each Evolution Pokemon; discard all Energy from + the ones healed this way.""" + for pokemon in ctx.my_pokemon_in_play(): + if not is_evolution_pokemon(pokemon): + continue + if pokemon.get_attribute(AttrID.HP, 0) >= ctx.max_hp(pokemon): + continue + healed = await ctx.heal(ctx.max_hp(pokemon), target=pokemon) + if healed: + energies = ctx.attached_energies(pokemon) + if energies: + await ctx.discard_cards(energies) + card = SupporterCardDef( guid="65199852-a0b7-5eef-8f0e-116d052100f7", @@ -11,5 +28,5 @@ card = SupporterCardDef( collector_number=123, set_code="SWSH5", rarity=Rarities.Uncommon, - effect=unimplemented + effect=cheryl, ) diff --git a/spirit/game/scripts/cards/SWSH5/Cheryl_159.py b/spirit/game/scripts/cards/SWSH5/Cheryl_159.py index 418c8f0..1e90ddd 100644 --- a/spirit/game/scripts/cards/SWSH5/Cheryl_159.py +++ b/spirit/game/scripts/cards/SWSH5/Cheryl_159.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import SupporterCardDef +from spirit.game.attributes import AttrID, Rarities +from spirit.game.session.effects import is_evolution_pokemon + + +async def cheryl(ctx): + """Heal all damage from each Evolution Pokemon; discard all Energy from + the ones healed this way.""" + for pokemon in ctx.my_pokemon_in_play(): + if not is_evolution_pokemon(pokemon): + continue + if pokemon.get_attribute(AttrID.HP, 0) >= ctx.max_hp(pokemon): + continue + healed = await ctx.heal(ctx.max_hp(pokemon), target=pokemon) + if healed: + energies = ctx.attached_energies(pokemon) + if energies: + await ctx.discard_cards(energies) + card = SupporterCardDef( guid="5b62307a-a7da-5e4b-8bcf-6f4082d95e29", @@ -11,5 +28,5 @@ card = SupporterCardDef( collector_number=159, set_code="SWSH5", rarity=Rarities.RareUltra, - effect=unimplemented + effect=cheryl, ) diff --git a/spirit/game/scripts/cards/SWSH5/Cheryl_173.py b/spirit/game/scripts/cards/SWSH5/Cheryl_173.py index 12b1bad..342b2fe 100644 --- a/spirit/game/scripts/cards/SWSH5/Cheryl_173.py +++ b/spirit/game/scripts/cards/SWSH5/Cheryl_173.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import SupporterCardDef +from spirit.game.attributes import AttrID, Rarities +from spirit.game.session.effects import is_evolution_pokemon + + +async def cheryl(ctx): + """Heal all damage from each Evolution Pokemon; discard all Energy from + the ones healed this way.""" + for pokemon in ctx.my_pokemon_in_play(): + if not is_evolution_pokemon(pokemon): + continue + if pokemon.get_attribute(AttrID.HP, 0) >= ctx.max_hp(pokemon): + continue + healed = await ctx.heal(ctx.max_hp(pokemon), target=pokemon) + if healed: + energies = ctx.attached_energies(pokemon) + if energies: + await ctx.discard_cards(energies) + card = SupporterCardDef( guid="81e68f86-b8e4-58da-8fbb-49b009cb72fe", @@ -11,5 +28,5 @@ card = SupporterCardDef( collector_number=173, set_code="SWSH5", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=cheryl, ) diff --git a/spirit/game/scripts/cards/SWSH5/Chimecho_59.py b/spirit/game/scripts/cards/SWSH5/Chimecho_59.py index 009f822..b9d8eb4 100644 --- a/spirit/game/scripts/cards/SWSH5/Chimecho_59.py +++ b/spirit/game/scripts/cards/SWSH5/Chimecho_59.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.card_effects.trainers import is_basic_energy_card + +minor_errand_running = search_to_hand( + is_basic_energy_card, count=2, + prompt="Choose up to 2 basic Energy cards.", +) card = PokemonCardDef( guid="eb2bea59-78b8-5ed7-a78a-a65854b3bfd8", @@ -23,14 +31,14 @@ card = PokemonCardDef( title="Minor Errand-Running", game_text="Search your deck for up to 2 basic Energy cards, reveal them, and put them into your hand. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=minor_errand_running, ), Attack( title="Pleasant Tone", game_text="Your opponent's Active Pok\u00e9mon is now Asleep.", cost={PokemonTypes.COLORLESS: 1}, damage=20, - effect=unimplemented, + effect=condition_attack(SpecialConditions.ASLEEP), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/Claydol_58.py b/spirit/game/scripts/cards/SWSH5/Claydol_58.py index 05e732c..c9d45bf 100644 --- a/spirit/game/scripts/cards/SWSH5/Claydol_58.py +++ b/spirit/game/scripts/cards/SWSH5/Claydol_58.py @@ -1,5 +1,33 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import count_energy +from spirit.game.card_effects.pokemon import energy_provides_type +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.legal_actions import energy_provided_count + + +async def power_split(ctx): + """Attach Psychic Energy from discard, any way you like, until totals match.""" + needed = count_energy("opponent")(ctx) - count_energy("mine")(ctx) + while needed > 0: + pool = [c for c in ctx.discard_pile() + if energy_provides_type(c, PokemonTypes.PSYCHIC.value)] + if not pool: + break + picks = await ctx.choose_cards( + pool, 1, minimum=1, + prompt="Choose a Psychic Energy card to attach.", + ) + if not picks: + break + card = picks[0] + target = await ctx.choose_pokemon( + ctx.my_pokemon_in_play(), "Choose a Pokémon to attach it to.", + ) + if target is None: + break + await ctx.attach_energy(card, target) + needed -= energy_provided_count(card, ctx.board) + card = PokemonCardDef( guid="ecfc0951-af29-5dfa-8fc3-990d2169617e", @@ -24,7 +52,7 @@ card = PokemonCardDef( title="Power Split", game_text="Attach Psychic Energy cards from your discard pile to your Pok\u00e9mon in any way you like until your Pok\u00e9mon and your opponent's Pok\u00e9mon have the same total amount of Energy attached.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=power_split, ), Attack( title="Psyshot", diff --git a/spirit/game/scripts/cards/SWSH5/Coalossal_80.py b/spirit/game/scripts/cards/SWSH5/Coalossal_80.py index 0ccbf23..8751534 100644 --- a/spirit/game/scripts/cards/SWSH5/Coalossal_80.py +++ b/spirit/game/scripts/cards/SWSH5/Coalossal_80.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import count_energy, flip_damage, recoil_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -25,14 +26,14 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 1}, damage=90, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins_from=count_energy("self"), per_heads=90), ), Attack( title="Wild Tackle", game_text="This Pok\u00e9mon also does 50 damage to itself.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 3}, damage=200, - effect=unimplemented, + effect=recoil_attack(50), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/Conkeldurr_75.py b/spirit/game/scripts/cards/SWSH5/Conkeldurr_75.py index 28477c8..f0eb29b 100644 --- a/spirit/game/scripts/cards/SWSH5/Conkeldurr_75.py +++ b/spirit/game/scripts/cards/SWSH5/Conkeldurr_75.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import lock_defender_attacks +from spirit.game.session.effects import is_evolution_pokemon + + +async def _hammer_pressure(ctx): + await ctx.deal_damage() + defender = ctx.defender + if defender is not None and is_evolution_pokemon(defender): + lock_defender_attacks(ctx, defender) + card = PokemonCardDef( guid="4d0c6820-74f0-5ec8-9b81-d91096674b63", @@ -24,7 +34,7 @@ card = PokemonCardDef( game_text="If the Defending Pok\u00e9mon is an Evolution Pok\u00e9mon, it can't attack during your opponent's next turn.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 2}, damage=90, - effect=unimplemented, + effect=_hammer_pressure, ), Attack( title="Mega Punch", diff --git a/spirit/game/scripts/cards/SWSH5/Corphish_38.py b/spirit/game/scripts/cards/SWSH5/Corphish_38.py index bee5af2..cf13a31 100644 --- a/spirit/game/scripts/cards/SWSH5/Corphish_38.py +++ b/spirit/game/scripts/cards/SWSH5/Corphish_38.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions card = PokemonCardDef( guid="518c89d3-0987-5629-8315-79e7e3d913c0", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=30, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/CorviknightVMAX_110.py b/spirit/game/scripts/cards/SWSH5/CorviknightVMAX_110.py index 05df6b6..15c2b15 100644 --- a/spirit/game/scripts/cards/SWSH5/CorviknightVMAX_110.py +++ b/spirit/game/scripts/cards/SWSH5/CorviknightVMAX_110.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.passives import Passive, carrier_pokemon + + +class LustrousBodyPassive(Passive): + """Prevent all effects of your opponent's Pokémon's Abilities done to this Pokémon.""" + + def blocks_ability_effects(self, target, carrier): + return target is carrier_pokemon(carrier) + card = PokemonCardDef( guid="5f9dcb26-d56a-5603-8cdb-0c3a6902eece", @@ -23,14 +32,14 @@ card = PokemonCardDef( Ability( title="Lustrous Body", game_text="Prevent all effects of your opponent's Pok\u00e9mon's Abilities done to this Pok\u00e9mon.", - effect=unimplemented, + passive=LustrousBodyPassive(), ), Attack( title="G-Max Hurricane", game_text="During your next turn, this Pok\u00e9mon can't use G-Max Hurricane.", cost={PokemonTypes.METAL: 2, PokemonTypes.COLORLESS: 1}, damage=240, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/CorviknightVMAX_171.py b/spirit/game/scripts/cards/SWSH5/CorviknightVMAX_171.py index 11c88d7..9a5514e 100644 --- a/spirit/game/scripts/cards/SWSH5/CorviknightVMAX_171.py +++ b/spirit/game/scripts/cards/SWSH5/CorviknightVMAX_171.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.passives import Passive, carrier_pokemon + + +class LustrousBodyPassive(Passive): + """Prevent all effects of your opponent's Pokémon's Abilities done to this Pokémon.""" + + def blocks_ability_effects(self, target, carrier): + return target is carrier_pokemon(carrier) + card = PokemonCardDef( guid="a7156471-d7a8-5cb0-bf14-a7bd6dba7067", @@ -23,14 +32,14 @@ card = PokemonCardDef( Ability( title="Lustrous Body", game_text="Prevent all effects of your opponent's Pok\u00e9mon's Abilities done to this Pok\u00e9mon.", - effect=unimplemented, + passive=LustrousBodyPassive(), ), Attack( title="G-Max Hurricane", game_text="During your next turn, this Pok\u00e9mon can't use G-Max Hurricane.", cost={PokemonTypes.METAL: 2, PokemonTypes.COLORLESS: 1}, damage=240, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/CorviknightV_109.py b/spirit/game/scripts/cards/SWSH5/CorviknightV_109.py index f909e6b..51acfaf 100644 --- a/spirit/game/scripts/cards/SWSH5/CorviknightV_109.py +++ b/spirit/game/scripts/cards/SWSH5/CorviknightV_109.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="79be664c-11b9-58fc-9e8c-ae6f7710ed90", @@ -24,14 +25,14 @@ card = PokemonCardDef( game_text="During your opponent's next turn, the Defending Pok\u00e9mon can't retreat.", cost={PokemonTypes.METAL: 1}, damage=30, - effect=unimplemented, + effect=condition_attack(no_retreat=True), ), Attack( title="Sky Hurricane", game_text="During your next turn, this Pok\u00e9mon can't use Sky Hurricane.", cost={PokemonTypes.METAL: 2, PokemonTypes.COLORLESS: 1}, damage=190, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/CorviknightV_156.py b/spirit/game/scripts/cards/SWSH5/CorviknightV_156.py index 8cbf6ad..3dee42d 100644 --- a/spirit/game/scripts/cards/SWSH5/CorviknightV_156.py +++ b/spirit/game/scripts/cards/SWSH5/CorviknightV_156.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="a5d16d2f-5590-5396-8988-6c5e12f2ac6a", @@ -24,14 +25,14 @@ card = PokemonCardDef( game_text="During your opponent's next turn, the Defending Pok\u00e9mon can't retreat.", cost={PokemonTypes.METAL: 1}, damage=30, - effect=unimplemented, + effect=condition_attack(no_retreat=True), ), Attack( title="Sky Hurricane", game_text="During your next turn, this Pok\u00e9mon can't use Sky Hurricane.", cost={PokemonTypes.METAL: 2, PokemonTypes.COLORLESS: 1}, damage=190, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/Crawdaunt_39.py b/spirit/game/scripts/cards/SWSH5/Crawdaunt_39.py index c12686b..6a3164b 100644 --- a/spirit/game/scripts/cards/SWSH5/Crawdaunt_39.py +++ b/spirit/game/scripts/cards/SWSH5/Crawdaunt_39.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import discard_random_from_hand + + +async def knock_off(ctx): + """60 damage, then discard a random card from the opponent's hand.""" + await ctx.deal_damage() + await discard_random_from_hand(ctx, ctx.opponent_id, count=1) + card = PokemonCardDef( guid="33a38792-be6e-54b0-81aa-2b2d237cdb4b", @@ -24,7 +32,7 @@ card = PokemonCardDef( game_text="Discard a random card from your opponent's hand.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=60, - effect=unimplemented, + effect=knock_off, ), Attack( title="Crabhammer", @@ -32,4 +40,4 @@ card = PokemonCardDef( damage=140, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH5/Crobat_91.py b/spirit/game/scripts/cards/SWSH5/Crobat_91.py index 6c5765f..eb16bc6 100644 --- a/spirit/game/scripts/cards/SWSH5/Crobat_91.py +++ b/spirit/game/scripts/cards/SWSH5/Crobat_91.py @@ -1,6 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def drastic_draw(ctx): + """On evolve: you may draw 3 cards.""" + if await ctx.ask_yes_no("Draw 3 cards?"): + await ctx.draw_cards(3) + + card = PokemonCardDef( guid="6665bbd3-e931-5a6f-b151-61b8fe08a828", key="SWSH5", @@ -22,7 +29,8 @@ card = PokemonCardDef( Ability( title="Drastic Draw", game_text="When you play this Pok\u00e9mon from your hand to evolve 1 of your Pok\u00e9mon during your turn, you may draw 3 cards.", - effect=unimplemented, + trigger=Triggers.ON_EVOLVE, + effect=drastic_draw, ), Attack( title="Wing Attack", diff --git a/spirit/game/scripts/cards/SWSH5/Dottler_64.py b/spirit/game/scripts/cards/SWSH5/Dottler_64.py index 60f5bc7..8fe2305 100644 --- a/spirit/game/scripts/cards/SWSH5/Dottler_64.py +++ b/spirit/game/scripts/cards/SWSH5/Dottler_64.py @@ -1,6 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def radar(ctx): + """Look at the top 4 cards of your deck and put them back in any order.""" + await ctx.reorder_deck_top(4) + + card = PokemonCardDef( guid="811a4a95-aed5-51ab-8852-a49f7fe19727", key="SWSH5", @@ -24,7 +30,7 @@ card = PokemonCardDef( title="Radar", game_text="Look at the top 4 cards of your deck and put them back in any order.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=radar, ), Attack( title="Ram", @@ -32,4 +38,4 @@ card = PokemonCardDef( damage=20, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH5/Doublade_106.py b/spirit/game/scripts/cards/SWSH5/Doublade_106.py index 164e320..55b1337 100644 --- a/spirit/game/scripts/cards/SWSH5/Doublade_106.py +++ b/spirit/game/scripts/cards/SWSH5/Doublade_106.py @@ -1,6 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def cut_down(ctx): + """Flip a coin. If heads, discard an Energy from the opponent's Active Pokemon.""" + await ctx.deal_damage() + heads = await ctx.flip_coins(1, "Cut Down") + if heads[0] and not ctx.effects_blocked(ctx.defender): + await ctx.discard_energy_from( + ctx.defender, 1, + prompt="Choose an Energy to discard from the opponent's Active Pokémon", + ) + + card = PokemonCardDef( guid="9d2ad624-8d85-5bc5-a125-72be7a1494e4", key="SWSH5", @@ -25,7 +37,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, discard an Energy from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 1}, damage=40, - effect=unimplemented, + effect=cut_down, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/Drampa_119.py b/spirit/game/scripts/cards/SWSH5/Drampa_119.py index ec6fa39..3881e42 100644 --- a/spirit/game/scripts/cards/SWSH5/Drampa_119.py +++ b/spirit/game/scripts/cards/SWSH5/Drampa_119.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage +from spirit.game.card_effects.support_common import search_to_bench card = PokemonCardDef( guid="9e8cf1bc-6c20-56d7-a34b-17b7c60ee645", @@ -22,7 +24,10 @@ card = PokemonCardDef( title="Call for Family", game_text="Search your deck for up to 2 Basic Pok\u00e9mon and put them onto your Bench. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_to_bench( + count=2, + prompt="Choose up to 2 Basic Pokémon to put onto your Bench.", + ), ), Attack( title="Spiral Rush", @@ -30,7 +35,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=30, damage_operator="+", - effect=unimplemented, + effect=flip_damage(until_tails=True, bonus_per_heads=30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/Durant_10.py b/spirit/game/scripts/cards/SWSH5/Durant_10.py index 3f85ccf..cd4e8d7 100644 --- a/spirit/game/scripts/cards/SWSH5/Durant_10.py +++ b/spirit/game/scripts/cards/SWSH5/Durant_10.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID + + +async def devour(ctx): + """For each of your Durant in play, discard the top card of your + opponent's deck.""" + count = sum( + 1 for p in ctx.my_pokemon_in_play() + if p.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) == "Durant" + ) + if count: + await ctx.discard_cards(ctx.deck_top(count, player_id=ctx.opponent_id)) card = PokemonCardDef( guid="330eda35-2701-51b9-ab21-d990bb0f6db6", @@ -27,7 +38,7 @@ card = PokemonCardDef( title="Devour", game_text="For each of your Durant in play, discard the top card of your opponent's deck.", cost={PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + effect=devour, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/Electabuzz_44.py b/spirit/game/scripts/cards/SWSH5/Electabuzz_44.py index 0185d20..d2721df 100644 --- a/spirit/game/scripts/cards/SWSH5/Electabuzz_44.py +++ b/spirit/game/scripts/cards/SWSH5/Electabuzz_44.py @@ -1,4 +1,4 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -23,7 +23,7 @@ card = PokemonCardDef( game_text="During your next turn, this Pok\u00e9mon can't use Haymaker.", cost={PokemonTypes.LIGHTNING: 1}, damage=30, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/Electivire_45.py b/spirit/game/scripts/cards/SWSH5/Electivire_45.py index 3123dc8..29c7563 100644 --- a/spirit/game/scripts/cards/SWSH5/Electivire_45.py +++ b/spirit/game/scripts/cards/SWSH5/Electivire_45.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import flip_bonus +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -25,14 +26,14 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=60, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(30), ), Attack( title="Lightning Slam", game_text="During your next turn, this Pok\u00e9mon can't use Lightning Slam.", cost={PokemonTypes.LIGHTNING: 3, PokemonTypes.COLORLESS: 1}, damage=180, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/Emboar_25.py b/spirit/game/scripts/cards/SWSH5/Emboar_25.py index e4b84cc..3d17cba 100644 --- a/spirit/game/scripts/cards/SWSH5/Emboar_25.py +++ b/spirit/game/scripts/cards/SWSH5/Emboar_25.py @@ -1,5 +1,10 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, subtypes_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import team_damage_boost_passive + + +def _is_single_strike(pokemon) -> bool: + return "Single Strike" in subtypes_for(pokemon.archetype_id) card = PokemonCardDef( guid="19ccdd9a-83b9-50d9-9ef5-e3ca14d6bc9b", @@ -22,7 +27,7 @@ card = PokemonCardDef( Ability( title="Fighting Fury Stance", game_text="Your Single Strike Pok\u00e9mon's attacks do 30 more damage to your opponent's Active Pok\u00e9mon (before applying Weakness and Resistance).", - effect=unimplemented, + passive=team_damage_boost_passive(30, attacker_pred=_is_single_strike), ), Attack( title="Heat Crash", diff --git a/spirit/game/scripts/cards/SWSH5/EmpoleonV_145.py b/spirit/game/scripts/cards/SWSH5/EmpoleonV_145.py index 2c3b643..802d7b5 100644 --- a/spirit/game/scripts/cards/SWSH5/EmpoleonV_145.py +++ b/spirit/game/scripts/cards/SWSH5/EmpoleonV_145.py @@ -1,5 +1,37 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, has_rule_box from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import ( + ability_lock_passive, is_in_active_spot, opposing_pokemon, +) +from spirit.game.session.effects import is_basic_pokemon + + +def _emperors_eyes_target(pokemon, carrier): + return ( + is_in_active_spot(carrier) + and opposing_pokemon(pokemon, carrier) + and is_basic_pokemon(pokemon) + and not has_rule_box(pokemon.archetype_id) + ) + + +async def swirling_slice(ctx): + await ctx.deal_damage() + bench = ctx.my_bench() + energies = ctx.attached_energies(ctx.attacker) + if not bench or not energies: + return + picks = await ctx.choose_cards( + energies, 1, prompt="Choose an Energy to move to 1 of your Benched Pokémon" + ) + if not picks: + return + target = await ctx.choose_pokemon( + bench, "Choose a Benched Pokémon to move the Energy to" + ) + if target is not None: + await ctx.move_energy(picks[0], target) + card = PokemonCardDef( guid="7bbe899b-209a-59e8-a2f0-394ced52fe93", @@ -21,14 +53,14 @@ card = PokemonCardDef( Ability( title="Emperor's Eyes", game_text="As long as this Pok\u00e9mon is in the Active Spot, your opponent's Basic Pok\u00e9mon in play have no Abilities, except for Pok\u00e9mon with a Rule Box (Pok\u00e9mon V, Pok\u00e9mon-GX, etc. have Rule Boxes).", - effect=unimplemented, + passive=ability_lock_passive(_emperors_eyes_target), ), Attack( title="Swirling Slice", game_text="Move an Energy from this Pok\u00e9mon to 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=130, - effect=unimplemented, + effect=swirling_slice, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/EmpoleonV_146.py b/spirit/game/scripts/cards/SWSH5/EmpoleonV_146.py index 97be1a0..14cbf22 100644 --- a/spirit/game/scripts/cards/SWSH5/EmpoleonV_146.py +++ b/spirit/game/scripts/cards/SWSH5/EmpoleonV_146.py @@ -1,5 +1,37 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, has_rule_box from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import ( + ability_lock_passive, is_in_active_spot, opposing_pokemon, +) +from spirit.game.session.effects import is_basic_pokemon + + +def _emperors_eyes_target(pokemon, carrier): + return ( + is_in_active_spot(carrier) + and opposing_pokemon(pokemon, carrier) + and is_basic_pokemon(pokemon) + and not has_rule_box(pokemon.archetype_id) + ) + + +async def swirling_slice(ctx): + await ctx.deal_damage() + bench = ctx.my_bench() + energies = ctx.attached_energies(ctx.attacker) + if not bench or not energies: + return + picks = await ctx.choose_cards( + energies, 1, prompt="Choose an Energy to move to 1 of your Benched Pokémon" + ) + if not picks: + return + target = await ctx.choose_pokemon( + bench, "Choose a Benched Pokémon to move the Energy to" + ) + if target is not None: + await ctx.move_energy(picks[0], target) + card = PokemonCardDef( guid="88ac3dc5-9284-5562-84a7-3d908baa2fc5", @@ -21,14 +53,14 @@ card = PokemonCardDef( Ability( title="Emperor's Eyes", game_text="As long as this Pok\u00e9mon is in the Active Spot, your opponent's Basic Pok\u00e9mon in play have no Abilities, except for Pok\u00e9mon with a Rule Box (Pok\u00e9mon V, Pok\u00e9mon-GX, etc. have Rule Boxes).", - effect=unimplemented, + passive=ability_lock_passive(_emperors_eyes_target), ), Attack( title="Swirling Slice", game_text="Move an Energy from this Pok\u00e9mon to 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=130, - effect=unimplemented, + effect=swirling_slice, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/EmpoleonV_40.py b/spirit/game/scripts/cards/SWSH5/EmpoleonV_40.py index 9e05c28..7dbf3ca 100644 --- a/spirit/game/scripts/cards/SWSH5/EmpoleonV_40.py +++ b/spirit/game/scripts/cards/SWSH5/EmpoleonV_40.py @@ -1,5 +1,37 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, has_rule_box from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import ( + ability_lock_passive, is_in_active_spot, opposing_pokemon, +) +from spirit.game.session.effects import is_basic_pokemon + + +def _emperors_eyes_target(pokemon, carrier): + return ( + is_in_active_spot(carrier) + and opposing_pokemon(pokemon, carrier) + and is_basic_pokemon(pokemon) + and not has_rule_box(pokemon.archetype_id) + ) + + +async def swirling_slice(ctx): + await ctx.deal_damage() + bench = ctx.my_bench() + energies = ctx.attached_energies(ctx.attacker) + if not bench or not energies: + return + picks = await ctx.choose_cards( + energies, 1, prompt="Choose an Energy to move to 1 of your Benched Pokémon" + ) + if not picks: + return + target = await ctx.choose_pokemon( + bench, "Choose a Benched Pokémon to move the Energy to" + ) + if target is not None: + await ctx.move_energy(picks[0], target) + card = PokemonCardDef( guid="9c5935c1-0362-50df-aabc-4370364a48b3", @@ -21,14 +53,14 @@ card = PokemonCardDef( Ability( title="Emperor's Eyes", game_text="As long as this Pok\u00e9mon is in the Active Spot, your opponent's Basic Pok\u00e9mon in play have no Abilities, except for Pok\u00e9mon with a Rule Box (Pok\u00e9mon V, Pok\u00e9mon-GX, etc. have Rule Boxes).", - effect=unimplemented, + passive=ability_lock_passive(_emperors_eyes_target), ), Attack( title="Swirling Slice", game_text="Move an Energy from this Pok\u00e9mon to 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=130, - effect=unimplemented, + effect=swirling_slice, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/Entei_20.py b/spirit/game/scripts/cards/SWSH5/Entei_20.py index a3000d4..c1f8d07 100644 --- a/spirit/game/scripts/cards/SWSH5/Entei_20.py +++ b/spirit/game/scripts/cards/SWSH5/Entei_20.py @@ -1,5 +1,23 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.pokemon import energy_provides_type + + +async def heat_dash(ctx): + await ctx.deal_damage() + fire_energy = [c for c in ctx.hand() if energy_provides_type(c, PokemonTypes.FIRE.value)] + if not fire_energy: + return + if await ctx.ask_yes_no( + "Attach a Fire Energy card from your hand to this Pokémon?" + ): + picks = await ctx.choose_cards( + fire_energy, 1, prompt="Choose a Fire Energy card to attach" + ) + if picks: + await ctx.attach_energy(picks[0], ctx.attacker) + card = PokemonCardDef( guid="fcee5e8d-5d17-5574-826e-db745ca23697", @@ -23,14 +41,14 @@ card = PokemonCardDef( game_text="You may attach a Fire Energy card from your hand to this Pok\u00e9mon.", cost={PokemonTypes.FIRE: 1}, damage=30, - effect=unimplemented, + effect=heat_dash, ), Attack( title="Fire Fang", game_text="Your opponent's Active Pok\u00e9mon is now Burned.", cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=90, - effect=unimplemented, + effect=condition_attack(SpecialConditions.BURNED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/Espurr_60.py b/spirit/game/scripts/cards/SWSH5/Espurr_60.py index 2cc4c99..a36d68d 100644 --- a/spirit/game/scripts/cards/SWSH5/Espurr_60.py +++ b/spirit/game/scripts/cards/SWSH5/Espurr_60.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import flip_bonus +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -25,7 +26,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/ExpShare_126.py b/spirit/game/scripts/cards/SWSH5/ExpShare_126.py index 8e24c2d..24560c9 100644 --- a/spirit/game/scripts/cards/SWSH5/ExpShare_126.py +++ b/spirit/game/scripts/cards/SWSH5/ExpShare_126.py @@ -1,5 +1,25 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented +from spirit.game.data_utils import PokemonToolCardDef, Ability, Triggers from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import is_basic_energy_card + + +async def exp_share(ctx): + """Ally Active KO'd by an opponent's attack: you may move a Basic Energy + from that Pokemon to the Pokemon this card is attached to.""" + if not ctx.ko_from_attack or ctx.ko_pokemon is None: + return + if ctx.board.active_pokemon(ctx.player_id) is not ctx.ko_pokemon: + return + pool = [e for e in ctx.attached_energies(ctx.ko_pokemon) if is_basic_energy_card(e)] + if not pool: + return + if not await ctx.ask_yes_no( + "Move a Basic Energy from your Knocked Out Active to the Pok\u00e9mon Exp. Share is attached to?"): + return + picks = await ctx.choose_cards(pool, 1, prompt="Choose a Basic Energy to move") + if picks: + await ctx.move_energy(picks[0], ctx.source) + card = PokemonToolCardDef( guid="252d608c-3b8b-55ee-b4e3-8c10c77366e9", @@ -11,5 +31,12 @@ card = PokemonToolCardDef( collector_number=126, set_code="SWSH5", rarity=Rarities.Uncommon, - effect=unimplemented + granted_abilities=[ + Ability( + title="Exp. Share", + game_text="When your Active Pok\u00e9mon is Knocked Out by damage from an attack from your opponent's Pok\u00e9mon, you may move a Basic Energy from that Pok\u00e9mon to the Pok\u00e9mon this card is attached to.", + trigger=Triggers.ON_ALLY_KNOCKED_OUT, + effect=exp_share, + ), + ], ) diff --git a/spirit/game/scripts/cards/SWSH5/ExpShare_180.py b/spirit/game/scripts/cards/SWSH5/ExpShare_180.py index 73b92c6..790f196 100644 --- a/spirit/game/scripts/cards/SWSH5/ExpShare_180.py +++ b/spirit/game/scripts/cards/SWSH5/ExpShare_180.py @@ -1,5 +1,25 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented +from spirit.game.data_utils import PokemonToolCardDef, Ability, Triggers from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import is_basic_energy_card + + +async def exp_share(ctx): + """Ally Active KO'd by an opponent's attack: you may move a Basic Energy + from that Pokemon to the Pokemon this card is attached to.""" + if not ctx.ko_from_attack or ctx.ko_pokemon is None: + return + if ctx.board.active_pokemon(ctx.player_id) is not ctx.ko_pokemon: + return + pool = [e for e in ctx.attached_energies(ctx.ko_pokemon) if is_basic_energy_card(e)] + if not pool: + return + if not await ctx.ask_yes_no( + "Move a Basic Energy from your Knocked Out Active to the Pok\u00e9mon Exp. Share is attached to?"): + return + picks = await ctx.choose_cards(pool, 1, prompt="Choose a Basic Energy to move") + if picks: + await ctx.move_energy(picks[0], ctx.source) + card = PokemonToolCardDef( guid="aacfa754-656d-52df-b01e-7ee45ca8d10b", @@ -11,5 +31,12 @@ card = PokemonToolCardDef( collector_number=180, set_code="SWSH5", rarity=Rarities.RareSecret, - effect=unimplemented + granted_abilities=[ + Ability( + title="Exp. Share", + game_text="When your Active Pok\u00e9mon is Knocked Out by damage from an attack from your opponent's Pok\u00e9mon, you may move a Basic Energy from that Pok\u00e9mon to the Pok\u00e9mon this card is attached to.", + trigger=Triggers.ON_ALLY_KNOCKED_OUT, + effect=exp_share, + ), + ], ) diff --git a/spirit/game/scripts/cards/SWSH5/Falinks_83.py b/spirit/game/scripts/cards/SWSH5/Falinks_83.py index 0d27533..83147bc 100644 --- a/spirit/game/scripts/cards/SWSH5/Falinks_83.py +++ b/spirit/game/scripts/cards/SWSH5/Falinks_83.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import damage_per, count_in_play +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, subtypes_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -24,7 +25,10 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 1}, damage=20, damage_operator="x", - effect=unimplemented, + effect=damage_per( + count_in_play("mine", lambda p: "Rapid Strike" in subtypes_for(p.archetype_id)), + 20, + ), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/FlappleVMAX_164.py b/spirit/game/scripts/cards/SWSH5/FlappleVMAX_164.py index f1a9047..10cd1c3 100644 --- a/spirit/game/scripts/cards/SWSH5/FlappleVMAX_164.py +++ b/spirit/game/scripts/cards/SWSH5/FlappleVMAX_164.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, damage_counters_on card = PokemonCardDef( guid="afd7e0dc-0016-5628-b109-95f4566ce780", @@ -25,7 +26,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 2}, damage=250, damage_operator="-", - effect=unimplemented, + effect=damage_per(damage_counters_on("self"), -10, base=250), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/FlappleVMAX_19.py b/spirit/game/scripts/cards/SWSH5/FlappleVMAX_19.py index d8b90a8..7a247aa 100644 --- a/spirit/game/scripts/cards/SWSH5/FlappleVMAX_19.py +++ b/spirit/game/scripts/cards/SWSH5/FlappleVMAX_19.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, damage_counters_on card = PokemonCardDef( guid="1ff05ee0-c19b-5546-a065-67587f139f88", @@ -25,7 +26,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 2}, damage=250, damage_operator="-", - effect=unimplemented, + effect=damage_per(damage_counters_on("self"), -10, base=250), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/FlappleV_143.py b/spirit/game/scripts/cards/SWSH5/FlappleV_143.py index 9bb53e0..2f3dedb 100644 --- a/spirit/game/scripts/cards/SWSH5/FlappleV_143.py +++ b/spirit/game/scripts/cards/SWSH5/FlappleV_143.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import raise_defender_attack_cost_next_turn card = PokemonCardDef( guid="0ea3cc7e-dc72-567f-a12e-2ef2022247c8", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, the Defending Pok\u00e9mon's attacks cost ColorlessColorless more.", cost={PokemonTypes.GRASS: 1}, damage=20, - effect=unimplemented, + effect=raise_defender_attack_cost_next_turn(extra=2), ), Attack( title="Wing Attack", diff --git a/spirit/game/scripts/cards/SWSH5/FlappleV_18.py b/spirit/game/scripts/cards/SWSH5/FlappleV_18.py index 3abe6a4..d8af904 100644 --- a/spirit/game/scripts/cards/SWSH5/FlappleV_18.py +++ b/spirit/game/scripts/cards/SWSH5/FlappleV_18.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import raise_defender_attack_cost_next_turn card = PokemonCardDef( guid="438cc4c7-a3b8-5418-a12f-b4208ae66712", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, the Defending Pok\u00e9mon's attacks cost ColorlessColorless more.", cost={PokemonTypes.GRASS: 1}, damage=20, - effect=unimplemented, + effect=raise_defender_attack_cost_next_turn(extra=2), ), Attack( title="Wing Attack", diff --git a/spirit/game/scripts/cards/SWSH5/Fomantis_14.py b/spirit/game/scripts/cards/SWSH5/Fomantis_14.py index dc41294..2843f7d 100644 --- a/spirit/game/scripts/cards/SWSH5/Fomantis_14.py +++ b/spirit/game/scripts/cards/SWSH5/Fomantis_14.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="0d782dd7-cc22-5314-8bc8-bd96952809f6", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1}, damage=20, damage_operator="x", - effect=unimplemented, + effect=flip_damage(until_tails=True, per_heads=20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/Frillish_41.py b/spirit/game/scripts/cards/SWSH5/Frillish_41.py index 432c1a6..311c456 100644 --- a/spirit/game/scripts/cards/SWSH5/Frillish_41.py +++ b/spirit/game/scripts/cards/SWSH5/Frillish_41.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID + + +async def recover(ctx): + """Discard an Energy from this Pokemon and heal all damage from it.""" + picks = await ctx.discard_energy_from(ctx.source, 1, prompt="Discard an Energy from Frillish") + if not picks: + return + pokemon = ctx.source + await ctx.heal(ctx.max_hp(pokemon) - pokemon.get_attribute(AttrID.HP, 0), pokemon) + card = PokemonCardDef( guid="8e118592-fd9a-5428-8434-5b480f7c206a", @@ -22,7 +32,7 @@ card = PokemonCardDef( title="Recover", game_text="Discard an Energy from this Pok\u00e9mon and heal all damage from it.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=recover, ), Attack( title="Rain Splash", diff --git a/spirit/game/scripts/cards/SWSH5/GalarianMrMime_34.py b/spirit/game/scripts/cards/SWSH5/GalarianMrMime_34.py index 82c4549..713d21c 100644 --- a/spirit/game/scripts/cards/SWSH5/GalarianMrMime_34.py +++ b/spirit/game/scripts/cards/SWSH5/GalarianMrMime_34.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.session.effects import is_item_card card = PokemonCardDef( guid="d4382431-e15b-5c1f-b644-a3680efb5669", @@ -27,7 +29,7 @@ card = PokemonCardDef( title="Find It", game_text="Search your deck for an Item card, reveal it, and put it into your hand. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + effect=search_to_hand(is_item_card, count=1, minimum=0, reveal=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/GalarianMrRime_35.py b/spirit/game/scripts/cards/SWSH5/GalarianMrRime_35.py index bf383bc..af10e0b 100644 --- a/spirit/game/scripts/cards/SWSH5/GalarianMrRime_35.py +++ b/spirit/game/scripts/cards/SWSH5/GalarianMrRime_35.py @@ -1,5 +1,25 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, def_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_item_card + + +def _is_ball_item(card) -> bool: + if not is_item_card(card): + return False + definition = def_for(getattr(card, "archetype_id", None) or "") + return bool(definition and "ball" in (definition.display_name or "").lower()) + + +async def ball_juggling(ctx): + matches = [c for c in ctx.hand() if _is_ball_item(c)] + picks = [] + if matches: + picks = await ctx.discard_from_hand( + len(matches), minimum=0, predicate=_is_ball_item, + prompt="Discard any number of Item cards with \"Ball\" in their name", + ) + await ctx.deal_damage(10 + 40 * len(picks)) + card = PokemonCardDef( guid="41c348c3-92e9-536a-b841-1ac6e84e8ed6", @@ -25,7 +45,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=10, damage_operator="+", - effect=unimplemented, + effect=ball_juggling, ), Attack( title="Frost Smash", diff --git a/spirit/game/scripts/cards/SWSH5/GalarianSlowbro_92.py b/spirit/game/scripts/cards/SWSH5/GalarianSlowbro_92.py index 536132d..faefae3 100644 --- a/spirit/game/scripts/cards/SWSH5/GalarianSlowbro_92.py +++ b/spirit/game/scripts/cards/SWSH5/GalarianSlowbro_92.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions, AttrID +from spirit.game.card_effects.attacks_common import condition_attack, bonus_if + + +def _self_has_special_condition(ctx): + return bool(ctx.attacker.get_attribute(AttrID.SPECIAL_CONDITIONS)) + card = PokemonCardDef( guid="a1dd90b4-4dd3-5f22-9eb4-3c7b1646ae0c", @@ -21,17 +27,17 @@ card = PokemonCardDef( abilities=[ Attack( title="Splattering Poison", - game_text="Both Active Pok\u00e9mon are now Poisoned.", + game_text="Both Active Pokémon are now Poisoned.", cost={PokemonTypes.DARKNESS: 1}, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED, both_actives=True), ), Attack( title="Unhinged Hammer", - game_text="If this Pok\u00e9mon is affected by a Special Condition, this attack does 120 more damage.", + game_text="If this Pokémon is affected by a Special Condition, this attack does 120 more damage.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 2}, damage=100, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_self_has_special_condition, 120), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH5/GalarianSlowpoke_54.py b/spirit/game/scripts/cards/SWSH5/GalarianSlowpoke_54.py index 55fe7c3..60ded8c 100644 --- a/spirit/game/scripts/cards/SWSH5/GalarianSlowpoke_54.py +++ b/spirit/game/scripts/cards/SWSH5/GalarianSlowpoke_54.py @@ -1,6 +1,25 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def tantailizing(ctx): + """Flip a coin. If heads, switch 1 of your opponent's Benched Pokémon + with their Active Pokémon.""" + heads = (await ctx.flip_coins(1, "Tantailizing"))[0] + if not heads: + return + old_active = ctx.opponent_active() + bench = ctx.opponent_bench() + if old_active is None or not bench: + return + if ctx.effects_blocked(old_active): + return + target = await ctx.choose_pokemon( + bench, "Choose the opponent's new Active Pokémon" + ) or bench[0] + await ctx.switch_active(ctx.opponent_id, target) + + card = PokemonCardDef( guid="843f8913-976d-512d-8b32-46b3eafe9068", key="SWSH5", @@ -23,7 +42,7 @@ card = PokemonCardDef( title="Tantailizing", game_text="Flip a coin. If heads, switch 1 of your opponent's Benched Pok\u00e9mon with their Active Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=tantailizing, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/Gliscor_72.py b/spirit/game/scripts/cards/SWSH5/Gliscor_72.py index e57e4b8..b52ad1c 100644 --- a/spirit/game/scripts/cards/SWSH5/Gliscor_72.py +++ b/spirit/game/scripts/cards/SWSH5/Gliscor_72.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="2b79d53e-e191-5810-856b-8b3446ca5b1b", @@ -25,7 +26,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=20, damage_operator="+", - effect=unimplemented, + effect=flip_damage(coins=2, bonus_per_heads=40), ), Attack( title="Guillotine", diff --git a/spirit/game/scripts/cards/SWSH5/Golbat_90.py b/spirit/game/scripts/cards/SWSH5/Golbat_90.py index 030da33..a81ce74 100644 --- a/spirit/game/scripts/cards/SWSH5/Golbat_90.py +++ b/spirit/game/scripts/cards/SWSH5/Golbat_90.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import self_energy_discard_attack + + +async def discreet_draw(ctx): + """On evolve: you may draw 2 cards.""" + if await ctx.ask_yes_no("Draw 2 cards?"): + await ctx.draw_cards(2) + card = PokemonCardDef( guid="3d80cd6a-0006-50ca-b0ab-bf8c218270ac", @@ -22,14 +30,15 @@ card = PokemonCardDef( Ability( title="Discreet Draw", game_text="When you play this Pok\u00e9mon from your hand to evolve 1 of your Pok\u00e9mon during your turn, you may draw 2 cards.", - effect=unimplemented, + trigger=Triggers.ON_EVOLVE, + effect=discreet_draw, ), Attack( title="Air Slash", game_text="Discard an Energy from this Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 2}, damage=50, - effect=unimplemented, + effect=self_energy_discard_attack(count=1), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/Grumpig_56.py b/spirit/game/scripts/cards/SWSH5/Grumpig_56.py index 6f5ce76..7cf0a9a 100644 --- a/spirit/game/scripts/cards/SWSH5/Grumpig_56.py +++ b/spirit/game/scripts/cards/SWSH5/Grumpig_56.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="0471a6bd-57c0-592b-83a0-e3a7c8c92633", @@ -25,7 +26,7 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Confused.", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED), ), Attack( title="Power Gem", diff --git a/spirit/game/scripts/cards/SWSH5/Heatmor_26.py b/spirit/game/scripts/cards/SWSH5/Heatmor_26.py index b54b592..7fde0b7 100644 --- a/spirit/game/scripts/cards/SWSH5/Heatmor_26.py +++ b/spirit/game/scripts/cards/SWSH5/Heatmor_26.py @@ -1,6 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def burning_licks(ctx): + """70 damage; flip 2 coins, discard an Energy from the opponent's Active per heads.""" + await ctx.deal_damage() + heads = await ctx.flip_coins(2, "Burning Licks") + count = sum(heads) + if count and not ctx.effects_blocked(ctx.defender): + await ctx.discard_energy_from(ctx.defender, count) + + card = PokemonCardDef( guid="a0aa636d-2169-5583-ae55-35c24e9d6281", key="SWSH5", @@ -28,7 +38,7 @@ card = PokemonCardDef( game_text="Flip 2 coins. For each heads, discard an Energy from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 3}, damage=70, - effect=unimplemented, + effect=burning_licks, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/Honchkrow_94.py b/spirit/game/scripts/cards/SWSH5/Honchkrow_94.py index f96e5b1..c5724bb 100644 --- a/spirit/game/scripts/cards/SWSH5/Honchkrow_94.py +++ b/spirit/game/scripts/cards/SWSH5/Honchkrow_94.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import bonus_if +from spirit.game.card_effects.passives_common import condition_immunity_passive +from spirit.game.session.effects import is_special_energy + + +def _opponent_active_has_special_energy(ctx): + active = ctx.opponent_active() + if active is None: + return False + return any(is_special_energy(c) for c in ctx.attached_energies(active)) + card = PokemonCardDef( guid="d2565f12-5cb7-5627-832f-8b4b49d4a757", @@ -22,16 +33,16 @@ card = PokemonCardDef( abilities=[ Ability( title="Insomnia", - game_text="This Pok\u00e9mon can't be Asleep.", - effect=unimplemented, + game_text="This Pokémon can't be Asleep.", + passive=condition_immunity_passive(SpecialConditions.ASLEEP), ), Attack( title="Voltage Dive", - game_text="If your opponent's Active Pok\u00e9mon has any Special Energy attached, this attack does 80 more damage.", + game_text="If your opponent's Active Pokémon has any Special Energy attached, this attack does 80 more damage.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 2}, damage=80, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_opponent_active_has_special_energy, 80), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH5/Honedge_105.py b/spirit/game/scripts/cards/SWSH5/Honedge_105.py index 6832fc3..7b382a9 100644 --- a/spirit/game/scripts/cards/SWSH5/Honedge_105.py +++ b/spirit/game/scripts/cards/SWSH5/Honedge_105.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus card = PokemonCardDef( guid="bc539211-c469-595c-be57-69dd517fdd8c", @@ -25,7 +26,7 @@ card = PokemonCardDef( cost={PokemonTypes.METAL: 1}, damage=10, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/Horsea_31.py b/spirit/game/scripts/cards/SWSH5/Horsea_31.py index 604ccc9..51cb230 100644 --- a/spirit/game/scripts/cards/SWSH5/Horsea_31.py +++ b/spirit/game/scripts/cards/SWSH5/Horsea_31.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import smokescreen_attack card = PokemonCardDef( guid="27c92d37-9fe4-5705-840f-41724bb6a2a2", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, if the Defending Pok\u00e9mon tries to attack, your opponent flips a coin. If tails, that attack doesn't happen.", cost={PokemonTypes.WATER: 1}, damage=10, - effect=unimplemented, + effect=smokescreen_attack(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/Houndoom_179.py b/spirit/game/scripts/cards/SWSH5/Houndoom_179.py index ccd766b..7b6e6ac 100644 --- a/spirit/game/scripts/cards/SWSH5/Houndoom_179.py +++ b/spirit/game/scripts/cards/SWSH5/Houndoom_179.py @@ -1,5 +1,43 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations, subtypes_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import is_energy_card + + +def _is_single_strike_energy(card): + return is_energy_card(card) and "Single Strike" in subtypes_for(card.archetype_id) + + +def _is_single_strike_pokemon(pokemon): + return "Single Strike" in subtypes_for(pokemon.archetype_id) + + +async def single_strike_roar(ctx): + """Once per turn: you may search for a Single Strike Energy card and + attach it to 1 of your Single Strike Pokémon; if you did, put 2 damage + counters on that Pokémon.""" + if not await ctx.ask_yes_no( + "Search your deck for a Single Strike Energy card and attach it to " + "1 of your Single Strike Pokémon?" + ): + return + picks = await ctx.search_deck( + _is_single_strike_energy, count=1, minimum=0, + prompt="Choose a Single Strike Energy card to attach.", + ) + await ctx.shuffle_deck() + if not picks: + return + candidates = [p for p in ctx.my_pokemon_in_play() if _is_single_strike_pokemon(p)] + if not candidates: + return + target = await ctx.choose_pokemon( + candidates, "Choose a Single Strike Pokémon to attach the Energy to" + ) + if target is None: + return + await ctx.attach_energy(picks[0], target) + await ctx.deal_damage(20, target=target, apply_modifiers=False, as_counters=True) + card = PokemonCardDef( guid="81c2ef9b-3afd-53ec-b712-ad81e64d944c", @@ -22,7 +60,8 @@ card = PokemonCardDef( Ability( title="Single Strike Roar", game_text="Once during your turn, you may search your deck for a Single Strike Energy card and attach it to 1 of your Single Strike Pok\u00e9mon. Then, shuffle your deck. If you attached Energy to a Pok\u00e9mon in this way, put 2 damage counters on that Pok\u00e9mon.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + effect=single_strike_roar, ), Attack( title="Darkness Fang", diff --git a/spirit/game/scripts/cards/SWSH5/Houndoom_96.py b/spirit/game/scripts/cards/SWSH5/Houndoom_96.py index 7824899..f9676c3 100644 --- a/spirit/game/scripts/cards/SWSH5/Houndoom_96.py +++ b/spirit/game/scripts/cards/SWSH5/Houndoom_96.py @@ -1,5 +1,43 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations, subtypes_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import is_energy_card + + +def _is_single_strike_energy(card): + return is_energy_card(card) and "Single Strike" in subtypes_for(card.archetype_id) + + +def _is_single_strike_pokemon(pokemon): + return "Single Strike" in subtypes_for(pokemon.archetype_id) + + +async def single_strike_roar(ctx): + """Once per turn: you may search for a Single Strike Energy card and + attach it to 1 of your Single Strike Pokémon; if you did, put 2 damage + counters on that Pokémon.""" + if not await ctx.ask_yes_no( + "Search your deck for a Single Strike Energy card and attach it to " + "1 of your Single Strike Pokémon?" + ): + return + picks = await ctx.search_deck( + _is_single_strike_energy, count=1, minimum=0, + prompt="Choose a Single Strike Energy card to attach.", + ) + await ctx.shuffle_deck() + if not picks: + return + candidates = [p for p in ctx.my_pokemon_in_play() if _is_single_strike_pokemon(p)] + if not candidates: + return + target = await ctx.choose_pokemon( + candidates, "Choose a Single Strike Pokémon to attach the Energy to" + ) + if target is None: + return + await ctx.attach_energy(picks[0], target) + await ctx.deal_damage(20, target=target, apply_modifiers=False, as_counters=True) + card = PokemonCardDef( guid="3eff2979-a56e-557e-a44c-9a592c10c030", @@ -22,7 +60,8 @@ card = PokemonCardDef( Ability( title="Single Strike Roar", game_text="Once during your turn, you may search your deck for a Single Strike Energy card and attach it to 1 of your Single Strike Pok\u00e9mon. Then, shuffle your deck. If you attached Energy to a Pok\u00e9mon in this way, put 2 damage counters on that Pok\u00e9mon.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + effect=single_strike_roar, ), Attack( title="Darkness Fang", diff --git a/spirit/game/scripts/cards/SWSH5/Indeedee_120.py b/spirit/game/scripts/cards/SWSH5/Indeedee_120.py index efe38ce..b7080a4 100644 --- a/spirit/game/scripts/cards/SWSH5/Indeedee_120.py +++ b/spirit/game/scripts/cards/SWSH5/Indeedee_120.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import draw_attack +from spirit.game.card_effects.attacks_common import damage_per, count_hand card = PokemonCardDef( guid="bed6476b-7025-5d9e-9663-dcdf39f99c2b", @@ -22,7 +24,7 @@ card = PokemonCardDef( title="Collect", game_text="Draw 2 cards.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=draw_attack(2), ), Attack( title="Hand Kinesis", @@ -30,7 +32,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=10, damage_operator="x", - effect=unimplemented, + effect=damage_per(count_hand("mine"), 10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/Jellicent_42.py b/spirit/game/scripts/cards/SWSH5/Jellicent_42.py index ed4e99a..d52ab56 100644 --- a/spirit/game/scripts/cards/SWSH5/Jellicent_42.py +++ b/spirit/game/scripts/cards/SWSH5/Jellicent_42.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_discard +from spirit.game.card_effects.pokemon import is_energy_card, energy_provides_type + + +def _is_water_energy(card): + return is_energy_card(card) and energy_provides_type(card, PokemonTypes.WATER.value) + card = PokemonCardDef( guid="7ddec2db-fb71-55e3-bf8f-30bf4d005a49", @@ -25,7 +32,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 2}, damage=10, damage_operator="x", - effect=unimplemented, + effect=damage_per(count_discard("mine", _is_water_energy), 10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/Kingdra_33.py b/spirit/game/scripts/cards/SWSH5/Kingdra_33.py index 8238619..de53ec1 100644 --- a/spirit/game/scripts/cards/SWSH5/Kingdra_33.py +++ b/spirit/game/scripts/cards/SWSH5/Kingdra_33.py @@ -1,5 +1,27 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy +from spirit.game.card_effects.pokemon import energy_provides_type + + +async def deep_sea_king(ctx): + """Ally Active KO'd by an opponent's attack: you may move any amount of Water Energy from it to this Pokemon.""" + if not ctx.ko_from_attack or ctx.ko_pokemon is None: + return + if ctx.board.active_pokemon(ctx.player_id) is not ctx.ko_pokemon: + return + pool = [e for e in ctx.attached_energies(ctx.ko_pokemon) + if energy_provides_type(e, PokemonTypes.WATER.value)] + if not pool: + return + if not await ctx.ask_yes_no( + "Move any amount of Water Energy from your Knocked Out Active to this Pokémon?"): + return + await ctx.move_energy_freely( + [ctx.ko_pokemon], [ctx.source], + predicate=lambda e: energy_provides_type(e, PokemonTypes.WATER.value), + ) + card = PokemonCardDef( guid="cd9c1d5b-be03-5187-8910-c7d13d119a83", @@ -22,7 +44,8 @@ card = PokemonCardDef( Ability( title="Deep Sea King", game_text="When your Active Pok\u00e9mon is Knocked Out by damage from an attack from your opponent's Pok\u00e9mon, you may move any amount of Water Energy from that Pok\u00e9mon to this Pok\u00e9mon.", - effect=unimplemented, + trigger=Triggers.ON_ALLY_KNOCKED_OUT, + effect=deep_sea_king, ), Attack( title="Aqua Burst", @@ -30,7 +53,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 1}, damage=40, damage_operator="x", - effect=unimplemented, + effect=damage_per(count_energy("self", energy_type=PokemonTypes.WATER), 40), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/KorrinasFocus_128.py b/spirit/game/scripts/cards/SWSH5/KorrinasFocus_128.py index c1294a6..2397b82 100644 --- a/spirit/game/scripts/cards/SWSH5/KorrinasFocus_128.py +++ b/spirit/game/scripts/cards/SWSH5/KorrinasFocus_128.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.support_common import draw_until_effect card = SupporterCardDef( guid="0b7062fe-ebf3-59d9-b286-d6806e5b288a", @@ -11,5 +12,5 @@ card = SupporterCardDef( collector_number=128, set_code="SWSH5", rarity=Rarities.Uncommon, - effect=unimplemented + effect=draw_until_effect(6) ) diff --git a/spirit/game/scripts/cards/SWSH5/KorrinasFocus_160.py b/spirit/game/scripts/cards/SWSH5/KorrinasFocus_160.py index dc95a64..bee981b 100644 --- a/spirit/game/scripts/cards/SWSH5/KorrinasFocus_160.py +++ b/spirit/game/scripts/cards/SWSH5/KorrinasFocus_160.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.support_common import draw_until_effect card = SupporterCardDef( guid="c4a24f69-9880-5315-bbbf-df00cbd566f4", @@ -11,5 +12,5 @@ card = SupporterCardDef( collector_number=160, set_code="SWSH5", rarity=Rarities.RareUltra, - effect=unimplemented + effect=draw_until_effect(6) ) diff --git a/spirit/game/scripts/cards/SWSH5/KorrinasFocus_174.py b/spirit/game/scripts/cards/SWSH5/KorrinasFocus_174.py index afbed25..b927aad 100644 --- a/spirit/game/scripts/cards/SWSH5/KorrinasFocus_174.py +++ b/spirit/game/scripts/cards/SWSH5/KorrinasFocus_174.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.support_common import draw_until_effect card = SupporterCardDef( guid="7edbc308-3067-581b-99ca-80c62966ca4f", @@ -11,5 +12,5 @@ card = SupporterCardDef( collector_number=174, set_code="SWSH5", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=draw_until_effect(6) ) diff --git a/spirit/game/scripts/cards/SWSH5/KricketuneV_142.py b/spirit/game/scripts/cards/SWSH5/KricketuneV_142.py index 3df1016..d6190fb 100644 --- a/spirit/game/scripts/cards/SWSH5/KricketuneV_142.py +++ b/spirit/game/scripts/cards/SWSH5/KricketuneV_142.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus +from spirit.game.card_effects.pokemon import in_active_spot + + +async def _exciting_stage(ctx): + """Once per turn: draw to 3 (4 if this Pokémon is Active).""" + target = 4 if in_active_spot(ctx.board, ctx.player_id, ctx.source) else 3 + if await ctx.ask_yes_no(f"Draw cards until you have {target} cards in your hand?"): + await ctx.draw_until(target) + + +def _exciting_stage_condition(board, player_id, pokemon): + target = 4 if in_active_spot(board, player_id, pokemon) else 3 + hand = board.find_player_area(player_id, "hand") + hand_size = len(hand.children) if hand else 0 + return hand_size < target card = PokemonCardDef( guid="dabe0c9f-d2b2-578d-8f4a-af842a5ef12a", @@ -21,7 +37,10 @@ card = PokemonCardDef( Ability( title="Exciting Stage", game_text="Once during your turn, you may draw cards until you have 3 cards in your hand. If this Pok\u00e9mon is in the Active Spot, you may draw cards until you have 4 cards in your hand instead. You can't use more than 1 Exciting Stage Ability each turn.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + shared_once_per_turn="Exciting Stage", + condition=_exciting_stage_condition, + effect=_exciting_stage, ), Attack( title="X-Scissor", @@ -29,7 +48,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 2}, damage=80, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/KricketuneV_6.py b/spirit/game/scripts/cards/SWSH5/KricketuneV_6.py index 2ba176f..eb8b0d2 100644 --- a/spirit/game/scripts/cards/SWSH5/KricketuneV_6.py +++ b/spirit/game/scripts/cards/SWSH5/KricketuneV_6.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus +from spirit.game.card_effects.pokemon import in_active_spot + + +async def _exciting_stage(ctx): + """Once per turn: draw to 3 (4 if this Pokémon is Active).""" + target = 4 if in_active_spot(ctx.board, ctx.player_id, ctx.source) else 3 + if await ctx.ask_yes_no(f"Draw cards until you have {target} cards in your hand?"): + await ctx.draw_until(target) + + +def _exciting_stage_condition(board, player_id, pokemon): + target = 4 if in_active_spot(board, player_id, pokemon) else 3 + hand = board.find_player_area(player_id, "hand") + hand_size = len(hand.children) if hand else 0 + return hand_size < target card = PokemonCardDef( guid="1acee573-7bc3-56b1-8219-032f31f3dfca", @@ -21,7 +37,10 @@ card = PokemonCardDef( Ability( title="Exciting Stage", game_text="Once during your turn, you may draw cards until you have 3 cards in your hand. If this Pok\u00e9mon is in the Active Spot, you may draw cards until you have 4 cards in your hand instead. You can't use more than 1 Exciting Stage Ability each turn.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + shared_once_per_turn="Exciting Stage", + condition=_exciting_stage_condition, + effect=_exciting_stage, ), Attack( title="X-Scissor", @@ -29,7 +48,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 2}, damage=80, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/LevelBall_129.py b/spirit/game/scripts/cards/SWSH5/LevelBall_129.py index f3581d3..f039b5b 100644 --- a/spirit/game/scripts/cards/SWSH5/LevelBall_129.py +++ b/spirit/game/scripts/cards/SWSH5/LevelBall_129.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import ItemCardDef +from spirit.game.attributes import AttrID, Rarities +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.session.effects import is_pokemon_card + + +def _hp_90_or_less(card): + return is_pokemon_card(card) and (card.get_attribute(AttrID.HP) or 0) <= 90 + card = ItemCardDef( guid="d8ae26a2-67fb-5124-a882-4eadb99df02c", @@ -11,5 +18,8 @@ card = ItemCardDef( collector_number=129, set_code="SWSH5", rarity=Rarities.Uncommon, - effect=unimplemented + effect=search_to_hand( + _hp_90_or_less, count=1, + prompt="Choose a Pokémon with 90 HP or less to put into your hand.", + ) ) diff --git a/spirit/game/scripts/cards/SWSH5/LevelBall_181.py b/spirit/game/scripts/cards/SWSH5/LevelBall_181.py index a2fc06c..5baf700 100644 --- a/spirit/game/scripts/cards/SWSH5/LevelBall_181.py +++ b/spirit/game/scripts/cards/SWSH5/LevelBall_181.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import ItemCardDef +from spirit.game.attributes import AttrID, Rarities +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.session.effects import is_pokemon_card + + +def _hp_90_or_less(card): + return is_pokemon_card(card) and (card.get_attribute(AttrID.HP) or 0) <= 90 + card = ItemCardDef( guid="311e4f3c-068f-589e-962c-530d05d4a0f1", @@ -11,5 +18,8 @@ card = ItemCardDef( collector_number=181, set_code="SWSH5", rarity=Rarities.RareSecret, - effect=unimplemented + effect=search_to_hand( + _hp_90_or_less, count=1, + prompt="Choose a Pokémon with 90 HP or less to put into your hand.", + ) ) diff --git a/spirit/game/scripts/cards/SWSH5/Lickilicky_114.py b/spirit/game/scripts/cards/SWSH5/Lickilicky_114.py index 856b708..2ad9f33 100644 --- a/spirit/game/scripts/cards/SWSH5/Lickilicky_114.py +++ b/spirit/game/scripts/cards/SWSH5/Lickilicky_114.py @@ -1,5 +1,25 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import opponent_switches + + +async def selickt(ctx): + choice = await ctx.choose( + "Discard the top 3 cards of your deck or discard 3 cards from your hand?", + ["Discard the top 3 cards of your deck", "Discard 3 cards from your hand"], + player_id=ctx.opponent_id, use_panel=False, + ) + if choice == 0: + await ctx.discard_cards(ctx.deck_top(3, player_id=ctx.opponent_id)) + else: + await ctx.discard_from_hand( + 3, player_id=ctx.opponent_id, prompt="Discard 3 cards from your hand" + ) + + +async def pitch(ctx): + await ctx.deal_damage() + await opponent_switches(ctx) card = PokemonCardDef( guid="4357a9e4-cfc0-5a15-9da5-a785f1242b83", @@ -23,14 +43,14 @@ card = PokemonCardDef( title="Selickt", game_text="Your opponent chooses to discard the top 3 cards of their deck or to discard 3 cards from their hand.", cost={PokemonTypes.COLORLESS: 3}, - effect=unimplemented, + effect=selickt, ), Attack( title="Pitch", game_text="Your opponent switches their Active Pok\u00e9mon with 1 of their Benched Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 4}, damage=100, - effect=unimplemented, + effect=pitch, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/Lurantis_15.py b/spirit/game/scripts/cards/SWSH5/Lurantis_15.py index cdb7647..6d73a78 100644 --- a/spirit/game/scripts/cards/SWSH5/Lurantis_15.py +++ b/spirit/game/scripts/cards/SWSH5/Lurantis_15.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import heal_attack card = PokemonCardDef( guid="68216f98-b45d-5e9b-bc3f-77b25a61f392", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Heal 30 damage from this Pok\u00e9mon.", cost={PokemonTypes.GRASS: 1}, damage=30, - effect=unimplemented, + effect=heal_attack(30, target="self"), ), Attack( title="Solar Cutter", diff --git a/spirit/game/scripts/cards/SWSH5/Luxio_47.py b/spirit/game/scripts/cards/SWSH5/Luxio_47.py index 156431f..cd0e651 100644 --- a/spirit/game/scripts/cards/SWSH5/Luxio_47.py +++ b/spirit/game/scripts/cards/SWSH5/Luxio_47.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import snipe_attack card = PokemonCardDef( guid="f026996b-a5e3-5a58-b6a5-470ada9b3c1b", @@ -23,7 +24,7 @@ card = PokemonCardDef( title="Jumping Kick", game_text="This attack does 30 damage to 1 of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.LIGHTNING: 1}, - effect=unimplemented, + effect=snipe_attack(30, pool="any", count=1), ), Attack( title="Head Bolt", diff --git a/spirit/game/scripts/cards/SWSH5/Luxray_48.py b/spirit/game/scripts/cards/SWSH5/Luxray_48.py index 3f82178..02f10e0 100644 --- a/spirit/game/scripts/cards/SWSH5/Luxray_48.py +++ b/spirit/game/scripts/cards/SWSH5/Luxray_48.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import snipe_attack, bonus_if, has_damage + + +async def _switch_self(ctx): + bench = ctx.my_bench() + if not bench: + return + target = await ctx.choose_pokemon(bench, "Choose your new Active Pokémon") + if target is not None: + await ctx.switch_active(ctx.player_id, target) card = PokemonCardDef( guid="ddadfc21-f803-5222-99a7-9fd8856092db", @@ -23,7 +33,7 @@ card = PokemonCardDef( title="Electrostep", game_text="This attack does 40 damage to 1 of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.) Switch this Pok\u00e9mon with 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.LIGHTNING: 1}, - effect=unimplemented, + effect=snipe_attack(40, pool="any", count=1, also=_switch_self), ), Attack( title="Scar Strikes", @@ -31,7 +41,7 @@ card = PokemonCardDef( cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 1}, damage=100, damage_operator="+", - effect=unimplemented, + effect=bonus_if(has_damage("defender"), 100), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/Mankey_66.py b/spirit/game/scripts/cards/SWSH5/Mankey_66.py index 7fe8974..9886928 100644 --- a/spirit/game/scripts/cards/SWSH5/Mankey_66.py +++ b/spirit/game/scripts/cards/SWSH5/Mankey_66.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_or_nothing card = PokemonCardDef( guid="e2f2cf62-93f4-55aa-998a-11f2e0d41e7e", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Flip a coin. If tails, this attack does nothing.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 1}, damage=50, - effect=unimplemented, + effect=flip_or_nothing(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/Marowak_70.py b/spirit/game/scripts/cards/SWSH5/Marowak_70.py index d18570b..9b2ce0d 100644 --- a/spirit/game/scripts/cards/SWSH5/Marowak_70.py +++ b/spirit/game/scripts/cards/SWSH5/Marowak_70.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage +from spirit.game.card_effects.passives_common import takes_less_passive card = PokemonCardDef( guid="e4b2e9ea-3a2f-571d-b418-e854e37cf756", @@ -22,7 +24,7 @@ card = PokemonCardDef( Ability( title="Battle Armor", game_text="This Pok\u00e9mon takes 30 less damage from attacks (after applying Weakness and Resistance).", - effect=unimplemented, + passive=takes_less_passive(30), ), Attack( title="Bonemerang", @@ -30,7 +32,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 1}, damage=90, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=2, per_heads=90), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/Mawile_100.py b/spirit/game/scripts/cards/SWSH5/Mawile_100.py index e000435..994999f 100644 --- a/spirit/game/scripts/cards/SWSH5/Mawile_100.py +++ b/spirit/game/scripts/cards/SWSH5/Mawile_100.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack + + +async def piercing_strike(ctx): + """100 damage, ignoring Weakness, Resistance, and any effects on the opponent's Active.""" + await ctx.deal_damage(apply_modifiers=False, ignore_target_effects=True) card = PokemonCardDef( guid="40754ed9-7817-592b-bd8c-c0a307fbb587", @@ -24,14 +30,14 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.METAL: 1}, damage=20, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), Attack( title="Piercing Strike", game_text="This attack's damage isn't affected by Weakness or Resistance, or by any effects on your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 3}, damage=100, - effect=unimplemented, + effect=piercing_strike, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/Meowstic_61.py b/spirit/game/scripts/cards/SWSH5/Meowstic_61.py index 7e645c9..219eb1d 100644 --- a/spirit/game/scripts/cards/SWSH5/Meowstic_61.py +++ b/spirit/game/scripts/cards/SWSH5/Meowstic_61.py @@ -1,5 +1,34 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.support_common import requires_damaged_pokemon + + +async def ear_moves(ctx): + """Once during your turn, you may move 1 damage counter from 1 of your + Pokemon to 1 of your opponent's Pokemon.""" + candidates = [p for p in ctx.my_pokemon_in_play() + if p.get_attribute(AttrID.HP, 0) < ctx.max_hp(p)] + if not candidates: + return + if not await ctx.ask_yes_no( + "Move 1 damage counter from 1 of your Pokemon to 1 of your " + "opponent's Pokemon?" + ): + return + source = await ctx.choose_pokemon( + candidates, "Choose 1 of your Pokemon to move a damage counter from" + ) + if source is None: + return + opponents = ctx.opponent_pokemon_in_play() + if not opponents: + return + dest = await ctx.choose_pokemon( + opponents, "Choose 1 of your opponent's Pokemon" + ) + if dest is None: + return + await ctx.move_damage_counters(source, dest, max_count=1) card = PokemonCardDef( guid="35126f48-89cc-58e8-ade6-f48d5410a311", @@ -23,7 +52,9 @@ card = PokemonCardDef( Ability( title="Ear Moves", game_text="Once during your turn, you may move 1 damage counter from 1 of your Pok\u00e9mon to 1 of your opponent's Pok\u00e9mon.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=requires_damaged_pokemon("mine"), + effect=ear_moves, ), Attack( title="Psychic Sphere", diff --git a/spirit/game/scripts/cards/SWSH5/Mienfoo_76.py b/spirit/game/scripts/cards/SWSH5/Mienfoo_76.py index 4eda899..d16cf61 100644 --- a/spirit/game/scripts/cards/SWSH5/Mienfoo_76.py +++ b/spirit/game/scripts/cards/SWSH5/Mienfoo_76.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="ee2b5a4b-e65a-5e61-925f-1026e5a8691c", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 1}, damage=30, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=2, per_heads=30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/Mienshao_77.py b/spirit/game/scripts/cards/SWSH5/Mienshao_77.py index 920782b..7086050 100644 --- a/spirit/game/scripts/cards/SWSH5/Mienshao_77.py +++ b/spirit/game/scripts/cards/SWSH5/Mienshao_77.py @@ -1,5 +1,24 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.session.effects import full_stack + + +async def spinning_whip(ctx): + """90. Opponent's Active is now Confused. Shuffle this Pokémon and all + attached cards into your deck.""" + await ctx.deal_damage() + await ctx.apply_special_condition(ctx.defender, SpecialConditions.CONFUSED) + await ctx.shuffle_into_deck(full_stack(ctx.attacker), ctx.player_id) + + async def _promote(): + if not await ctx.session._promote_new_active(ctx.player_id): + screen_name = ctx.session.players[ctx.player_id].screen_name + await ctx.session.end_game( + ctx.opponent_id, f"{screen_name} has no Pokémon left" + ) + + ctx.deferred_actions.append(_promote) + card = PokemonCardDef( guid="a57b82ca-48a8-5ff7-88d1-9a179268b4cc", @@ -29,7 +48,7 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Confused. Shuffle this Pok\u00e9mon and all attached cards into your deck.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 1}, damage=90, - effect=unimplemented, + effect=spinning_whip, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/MimikyuV_148.py b/spirit/game/scripts/cards/SWSH5/MimikyuV_148.py index 3c5caa3..c6bf76e 100644 --- a/spirit/game/scripts/cards/SWSH5/MimikyuV_148.py +++ b/spirit/game/scripts/cards/SWSH5/MimikyuV_148.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import place_counters +from spirit.game.card_effects.passives_common import apply_protection + + +async def dummy_doll(ctx): + """On play from hand: you may prevent all damage to this Pokemon by + opposing attacks until the end of the opponent's next turn.""" + if await ctx.ask_yes_no( + "Prevent all damage done to this Pokemon by attacks from your " + "opponent's Pokemon until the end of your opponent's next turn?" + ): + await apply_protection(ctx, prevent=True) card = PokemonCardDef( guid="f919ad9e-6f4e-586a-8c2b-98ba6acfacc0", @@ -22,13 +34,14 @@ card = PokemonCardDef( Ability( title="Dummy Doll", game_text="When you play this Pok\u00e9mon from your hand onto your Bench during your turn, you may prevent all damage done to this Mimikyu V by attacks from your opponent's Pok\u00e9mon until the end of your opponent's next turn.", - effect=unimplemented, + trigger=Triggers.ON_PLAY, + effect=dummy_doll, ), Attack( title="Jealous Eyes", game_text="Put 3 damage counters on your opponent's Active Pok\u00e9mon for each Prize card your opponent has taken.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=place_counters(lambda ctx: 3 * ctx.prizes_taken(ctx.opponent_id), "opponent_active"), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/MimikyuV_62.py b/spirit/game/scripts/cards/SWSH5/MimikyuV_62.py index f0f2333..afce803 100644 --- a/spirit/game/scripts/cards/SWSH5/MimikyuV_62.py +++ b/spirit/game/scripts/cards/SWSH5/MimikyuV_62.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import place_counters +from spirit.game.card_effects.passives_common import apply_protection + + +async def dummy_doll(ctx): + """On play from hand: you may prevent all damage to this Pokemon by + opposing attacks until the end of the opponent's next turn.""" + if await ctx.ask_yes_no( + "Prevent all damage done to this Pokemon by attacks from your " + "opponent's Pokemon until the end of your opponent's next turn?" + ): + await apply_protection(ctx, prevent=True) card = PokemonCardDef( guid="2c8b6716-937d-5a66-9cd0-6f4edbea579c", @@ -22,13 +34,14 @@ card = PokemonCardDef( Ability( title="Dummy Doll", game_text="When you play this Pok\u00e9mon from your hand onto your Bench during your turn, you may prevent all damage done to this Mimikyu V by attacks from your opponent's Pok\u00e9mon until the end of your opponent's next turn.", - effect=unimplemented, + trigger=Triggers.ON_PLAY, + effect=dummy_doll, ), Attack( title="Jealous Eyes", game_text="Put 3 damage counters on your opponent's Active Pok\u00e9mon for each Prize card your opponent has taken.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=place_counters(lambda ctx: 3 * ctx.prizes_taken(ctx.opponent_id), "opponent_active"), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/Morpeko_98.py b/spirit/game/scripts/cards/SWSH5/Morpeko_98.py index 5030fd4..0af526f 100644 --- a/spirit/game/scripts/cards/SWSH5/Morpeko_98.py +++ b/spirit/game/scripts/cards/SWSH5/Morpeko_98.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if +from spirit.game.card_effects.support_common import recover_from_discard +from spirit.game.session.effects import is_item_card + + +def _empty_hand(ctx) -> bool: + return ctx.hand_size() == 0 + card = PokemonCardDef( guid="8eb645ee-906d-553a-9fd7-c7e8d99094b7", @@ -22,7 +30,7 @@ card = PokemonCardDef( title="Gather Food", game_text="Put an Item card from your discard pile into your hand.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=recover_from_discard(is_item_card, count=1, minimum=1, reveal=False, to="hand"), ), Attack( title="Hangry Tackle", @@ -30,7 +38,7 @@ card = PokemonCardDef( cost={PokemonTypes.DARKNESS: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_empty_hand, 90, base=20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/Murkrow_93.py b/spirit/game/scripts/cards/SWSH5/Murkrow_93.py index 7d12600..e57dee4 100644 --- a/spirit/game/scripts/cards/SWSH5/Murkrow_93.py +++ b/spirit/game/scripts/cards/SWSH5/Murkrow_93.py @@ -1,6 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def scout(ctx): + """Your opponent reveals their hand.""" + await ctx.reveal_hand(of_player=ctx.opponent_id) + card = PokemonCardDef( guid="47a6e42b-bbb0-5f78-aa5d-ebcd43a9d84f", key="SWSH5", @@ -23,7 +28,7 @@ card = PokemonCardDef( title="Scout", game_text="Your opponent reveals their hand.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=scout, ), Attack( title="Peck", diff --git a/spirit/game/scripts/cards/SWSH5/NecrozmaV_149.py b/spirit/game/scripts/cards/SWSH5/NecrozmaV_149.py index 7a64580..b580066 100644 --- a/spirit/game/scripts/cards/SWSH5/NecrozmaV_149.py +++ b/spirit/game/scripts/cards/SWSH5/NecrozmaV_149.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_special_energy +from spirit.game.card_effects.attacks_common import bonus_if, snipe_attack + + +def _has_special_energy(ctx) -> bool: + return any(is_special_energy(e) for e in ctx.attached_energies(ctx.attacker)) + card = PokemonCardDef( guid="2066b050-2649-53bd-8001-c8d968fcd9ec", @@ -24,7 +31,7 @@ card = PokemonCardDef( game_text="This attack also does 20 damage to 2 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.PSYCHIC: 1}, damage=20, - effect=unimplemented, + effect=snipe_attack(20, pool="bench", count=2, also_base=True), ), Attack( title="Special Laser", @@ -32,7 +39,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 2, PokemonTypes.COLORLESS: 1}, damage=100, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_has_special_energy, 120), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/NecrozmaV_63.py b/spirit/game/scripts/cards/SWSH5/NecrozmaV_63.py index 05e342a..ea9b8cd 100644 --- a/spirit/game/scripts/cards/SWSH5/NecrozmaV_63.py +++ b/spirit/game/scripts/cards/SWSH5/NecrozmaV_63.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_special_energy +from spirit.game.card_effects.attacks_common import bonus_if, snipe_attack + + +def _has_special_energy(ctx) -> bool: + return any(is_special_energy(e) for e in ctx.attached_energies(ctx.attacker)) + card = PokemonCardDef( guid="6891b982-6a9d-5ecf-a6f1-f93d9a05d1b9", @@ -24,7 +31,7 @@ card = PokemonCardDef( game_text="This attack also does 20 damage to 2 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.PSYCHIC: 1}, damage=20, - effect=unimplemented, + effect=snipe_attack(20, pool="bench", count=2, also_base=True), ), Attack( title="Special Laser", @@ -32,7 +39,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 2, PokemonTypes.COLORLESS: 1}, damage=100, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_has_special_energy, 120), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/Octillery_178.py b/spirit/game/scripts/cards/SWSH5/Octillery_178.py index bbfcffe..3c58838 100644 --- a/spirit/game/scripts/cards/SWSH5/Octillery_178.py +++ b/spirit/game/scripts/cards/SWSH5/Octillery_178.py @@ -1,6 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations, subtypes_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +def _is_rapid_strike(card) -> bool: + return "Rapid Strike" in subtypes_for(card.archetype_id) + + +async def rapid_strike_search(ctx): + picks = await ctx.search_deck( + _is_rapid_strike, count=1, minimum=0, + prompt="Choose a Rapid Strike card to put into your hand.", + ) + await ctx.put_in_hand(picks, reveal=True) + await ctx.shuffle_deck() + + card = PokemonCardDef( guid="4d5b0649-db90-54c1-9f0e-a33e4b8dcc60", key="SWSH5", @@ -22,7 +36,9 @@ card = PokemonCardDef( Ability( title="Rapid Strike Search", game_text="Once during your turn, you may search your deck for a Rapid Strike card, reveal it, and put it into your hand. Then, shuffle your deck. You can't use more than 1 Rapid Strike Search Ability each turn.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + shared_once_per_turn="Rapid Strike Search", + effect=rapid_strike_search, ), Attack( title="Waterfall", diff --git a/spirit/game/scripts/cards/SWSH5/Octillery_37.py b/spirit/game/scripts/cards/SWSH5/Octillery_37.py index c377aab..c6160cb 100644 --- a/spirit/game/scripts/cards/SWSH5/Octillery_37.py +++ b/spirit/game/scripts/cards/SWSH5/Octillery_37.py @@ -1,6 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations, subtypes_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +def _is_rapid_strike(card) -> bool: + return "Rapid Strike" in subtypes_for(card.archetype_id) + + +async def rapid_strike_search(ctx): + picks = await ctx.search_deck( + _is_rapid_strike, count=1, minimum=0, + prompt="Choose a Rapid Strike card to put into your hand.", + ) + await ctx.put_in_hand(picks, reveal=True) + await ctx.shuffle_deck() + + card = PokemonCardDef( guid="31ac23cb-0844-5086-ae10-9c19b57cc367", key="SWSH5", @@ -22,7 +36,9 @@ card = PokemonCardDef( Ability( title="Rapid Strike Search", game_text="Once during your turn, you may search your deck for a Rapid Strike card, reveal it, and put it into your hand. Then, shuffle your deck. You can't use more than 1 Rapid Strike Search Ability each turn.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + shared_once_per_turn="Rapid Strike Search", + effect=rapid_strike_search, ), Attack( title="Waterfall", diff --git a/spirit/game/scripts/cards/SWSH5/Onix_68.py b/spirit/game/scripts/cards/SWSH5/Onix_68.py index 33deb0b..d916590 100644 --- a/spirit/game/scripts/cards/SWSH5/Onix_68.py +++ b/spirit/game/scripts/cards/SWSH5/Onix_68.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack card = PokemonCardDef( guid="1294e3c1-b0af-5824-a28a-bcce7a252948", @@ -28,7 +29,7 @@ card = PokemonCardDef( game_text="This Pok\u00e9mon also does 60 damage to itself.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 4}, damage=170, - effect=unimplemented, + effect=recoil_attack(60), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/Orbeetle_65.py b/spirit/game/scripts/cards/SWSH5/Orbeetle_65.py index 4a3c346..6015802 100644 --- a/spirit/game/scripts/cards/SWSH5/Orbeetle_65.py +++ b/spirit/game/scripts/cards/SWSH5/Orbeetle_65.py @@ -1,5 +1,33 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_pokemon_card +from spirit.game.card_effects.attacks_common import count_energy +from spirit.game.session.constants import BENCH_CAPACITY + + +def _stage2_not_orbeetle(card): + return ( + is_pokemon_card(card) + and card.get_attribute(AttrID.STAGE) == PokemonStage.STAGE2.value + and card.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) != "Orbeetle" + ) + + +async def evomancy(ctx): + """For each Energy attached to this Pokémon, search a Stage 2 Pokémon + (except Orbeetle) onto the Bench. Then, shuffle the deck.""" + count = count_energy("self")(ctx) + space = BENCH_CAPACITY - len(ctx.my_bench()) + take = min(count, space) + if take > 0: + picks = await ctx.search_deck( + _stage2_not_orbeetle, count=take, minimum=0, + prompt="Choose Stage 2 Pokémon to put onto your Bench.", + ) + for card in picks: + await ctx.bench_pokemon(card) + await ctx.shuffle_deck() + card = PokemonCardDef( guid="cae6af0b-3116-57b0-83bb-99cd9619cae0", @@ -22,9 +50,9 @@ card = PokemonCardDef( abilities=[ Attack( title="Evomancy", - game_text="For each Energy attached to this Pok\u00e9mon, search your deck for a Stage 2 Pok\u00e9mon, except Orbeetle, and put it onto your Bench. Then, shuffle your deck.", + game_text="For each Energy attached to this Pokémon, search your deck for a Stage 2 Pokémon, except Orbeetle, and put it onto your Bench. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=evomancy, ), Attack( title="Zen Headbutt", @@ -32,4 +60,4 @@ card = PokemonCardDef( damage=120, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH5/Pachirisu_49.py b/spirit/game/scripts/cards/SWSH5/Pachirisu_49.py index 29a6fd3..ab2a248 100644 --- a/spirit/game/scripts/cards/SWSH5/Pachirisu_49.py +++ b/spirit/game/scripts/cards/SWSH5/Pachirisu_49.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_pokemon_card +from spirit.game.card_effects.support_common import search_to_hand card = PokemonCardDef( guid="cecca137-aef2-53ee-8091-353610e49d6d", @@ -20,9 +22,10 @@ card = PokemonCardDef( abilities=[ Attack( title="Find a Friend", - game_text="Search your deck for a Pok\u00e9mon, reveal it, and put it into your hand. Then, shuffle your deck.", + game_text="Search your deck for a Pokémon, reveal it, and put it into your hand. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_to_hand(is_pokemon_card, count=1, minimum=0, reveal=True, + prompt="Choose a Pokémon to put into your hand."), ), Attack( title="Gnaw", @@ -30,4 +33,4 @@ card = PokemonCardDef( damage=30, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH5/Pawniard_103.py b/spirit/game/scripts/cards/SWSH5/Pawniard_103.py index 146c2af..753e625 100644 --- a/spirit/game/scripts/cards/SWSH5/Pawniard_103.py +++ b/spirit/game/scripts/cards/SWSH5/Pawniard_103.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import boost_own_next_turn card = PokemonCardDef( guid="15943fd9-b357-54ca-90bc-426f45168ac8", @@ -23,7 +24,7 @@ card = PokemonCardDef( title="Swords Dance", game_text="During your next turn, this Pok\u00e9mon's Slash attack does 70 more damage (before applying Weakness and Resistance).", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=boost_own_next_turn(70, attack_title="Slash"), ), Attack( title="Slash", diff --git a/spirit/game/scripts/cards/SWSH5/Phoebe_130.py b/spirit/game/scripts/cards/SWSH5/Phoebe_130.py index 011e747..fbc4912 100644 --- a/spirit/game/scripts/cards/SWSH5/Phoebe_130.py +++ b/spirit/game/scripts/cards/SWSH5/Phoebe_130.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import is_pokemon_vmax, has_vmax_in_play + + +async def phoebe(ctx): + """This turn, your Pokemon VMAX's attack damage ignores effects on the opponent's Active.""" + for pokemon in ctx.my_pokemon_in_play(): + if is_pokemon_vmax(pokemon.archetype_id): + ctx.ignore_own_target_effects(pokemon) + card = SupporterCardDef( guid="fce91074-8148-5961-a839-e905d8dfb156", @@ -11,5 +20,6 @@ card = SupporterCardDef( collector_number=130, set_code="SWSH5", rarity=Rarities.Uncommon, - effect=unimplemented + effect=phoebe, + condition=has_vmax_in_play ) diff --git a/spirit/game/scripts/cards/SWSH5/Phoebe_161.py b/spirit/game/scripts/cards/SWSH5/Phoebe_161.py index b966fee..4842899 100644 --- a/spirit/game/scripts/cards/SWSH5/Phoebe_161.py +++ b/spirit/game/scripts/cards/SWSH5/Phoebe_161.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import is_pokemon_vmax, has_vmax_in_play + + +async def phoebe(ctx): + """This turn, your Pokemon VMAX's attack damage ignores effects on the opponent's Active.""" + for pokemon in ctx.my_pokemon_in_play(): + if is_pokemon_vmax(pokemon.archetype_id): + ctx.ignore_own_target_effects(pokemon) + card = SupporterCardDef( guid="23da4d0d-4a87-543e-b111-63617e54d630", @@ -11,5 +20,6 @@ card = SupporterCardDef( collector_number=161, set_code="SWSH5", rarity=Rarities.RareUltra, - effect=unimplemented + effect=phoebe, + condition=has_vmax_in_play ) diff --git a/spirit/game/scripts/cards/SWSH5/Phoebe_175.py b/spirit/game/scripts/cards/SWSH5/Phoebe_175.py index af33d8b..03d1ff5 100644 --- a/spirit/game/scripts/cards/SWSH5/Phoebe_175.py +++ b/spirit/game/scripts/cards/SWSH5/Phoebe_175.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import is_pokemon_vmax, has_vmax_in_play + + +async def phoebe(ctx): + """This turn, your Pokemon VMAX's attack damage ignores effects on the opponent's Active.""" + for pokemon in ctx.my_pokemon_in_play(): + if is_pokemon_vmax(pokemon.archetype_id): + ctx.ignore_own_target_effects(pokemon) + card = SupporterCardDef( guid="1dd665c8-7c4f-593d-aa28-9afe91e0f052", @@ -11,5 +20,6 @@ card = SupporterCardDef( collector_number=175, set_code="SWSH5", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=phoebe, + condition=has_vmax_in_play ) diff --git a/spirit/game/scripts/cards/SWSH5/Primeape_67.py b/spirit/game/scripts/cards/SWSH5/Primeape_67.py index 1d8d95e..d0cb852 100644 --- a/spirit/game/scripts/cards/SWSH5/Primeape_67.py +++ b/spirit/game/scripts/cards/SWSH5/Primeape_67.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.attacks_common import damage_per + + +async def field_crush(ctx): + """20 damage; if the opponent has a Stadium in play, discard it.""" + await ctx.deal_damage() + stadium = ctx.stadium_in_play() + if stadium is not None and stadium.owning_player_id == ctx.opponent_id: + await ctx.discard_stadium() + + +def _damaged_bench_count(ctx): + return sum( + 1 for p in ctx.my_bench() if p.get_attribute(AttrID.HP, 0) < ctx.max_hp(p) + ) + card = PokemonCardDef( guid="988bcc33-8ed5-53b4-b0c4-678e5a4a2490", @@ -24,7 +40,7 @@ card = PokemonCardDef( game_text="If your opponent has a Stadium in play, discard it.", cost={PokemonTypes.COLORLESS: 1}, damage=20, - effect=unimplemented, + effect=field_crush, ), Attack( title="Steamin' Mad Strike", @@ -32,7 +48,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 2}, damage=50, damage_operator="x", - effect=unimplemented, + effect=damage_per(_damaged_bench_count, 50), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/Purugly_116.py b/spirit/game/scripts/cards/SWSH5/Purugly_116.py index bd42795..f9b8999 100644 --- a/spirit/game/scripts/cards/SWSH5/Purugly_116.py +++ b/spirit/game/scripts/cards/SWSH5/Purugly_116.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions + + +async def caturday(ctx): + """Draw 3 cards. If you do, this Pokemon is now Asleep.""" + drawn = await ctx.draw_cards(3) + if drawn: + await ctx.apply_special_condition(ctx.attacker, SpecialConditions.ASLEEP) + card = PokemonCardDef( guid="83cfecbd-ff00-52c6-96bc-a6a9f7394578", @@ -23,7 +31,7 @@ card = PokemonCardDef( title="Caturday", game_text="Draw 3 cards. If you do, this Pok\u00e9mon is now Asleep.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=caturday, ), Attack( title="Claw Slash", diff --git a/spirit/game/scripts/cards/SWSH5/RapidStrikeScrollofSwirls_131.py b/spirit/game/scripts/cards/SWSH5/RapidStrikeScrollofSwirls_131.py index 21085a2..77f64b4 100644 --- a/spirit/game/scripts/cards/SWSH5/RapidStrikeScrollofSwirls_131.py +++ b/spirit/game/scripts/cards/SWSH5/RapidStrikeScrollofSwirls_131.py @@ -1,15 +1,29 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import PokemonToolCardDef, Attack, subtypes_for +from spirit.game.attributes import PokemonTypes, Rarities +from spirit.game.card_effects.attacks_common import damage_all_opponents + + +def _rapid_strike_holder(board, player_id, pokemon): + return "Rapid Strike" in subtypes_for(pokemon.archetype_id) + card = PokemonToolCardDef( guid="4e442586-f3f2-5d6a-adb6-c2d6ddc82310", key="SWSH5", name="com.direwolfdigital.cake.data.archetypes.trainer.RapidStrikeScrollofSwirls.Name", display_name="Rapid Strike Scroll of Swirls", - searchable_by=["Rapid Strike Scroll of Swirls", "Pok\u00c3\u00a9mon Tool", "Rapid Strike"], - subtypes=["Pok\u00e9mon Tool", "Rapid Strike"], + searchable_by=["Rapid Strike Scroll of Swirls", "Pokémon Tool", "Rapid Strike"], + subtypes=["Pokémon Tool", "Rapid Strike"], collector_number=131, set_code="SWSH5", rarity=Rarities.Uncommon, - effect=unimplemented + granted_abilities=[ + Attack( + title="Matchless Maelstrom", + game_text="This attack does 30 damage to each of your opponent's Pokémon. (Don't apply Weakness and Resistance for Benched Pokémon.)", + cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 2}, + condition=_rapid_strike_holder, + effect=damage_all_opponents(30), + ), + ], ) diff --git a/spirit/game/scripts/cards/SWSH5/RapidStrikeStyleMustard_132.py b/spirit/game/scripts/cards/SWSH5/RapidStrikeStyleMustard_132.py index 8e60f9d..80e6cba 100644 --- a/spirit/game/scripts/cards/SWSH5/RapidStrikeStyleMustard_132.py +++ b/spirit/game/scripts/cards/SWSH5/RapidStrikeStyleMustard_132.py @@ -1,5 +1,31 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef, subtypes_for from spirit.game.attributes import Rarities +from spirit.game.session.effects import is_pokemon_card + + +def _last_card_in_hand(board, player_id): + hand = board.find_player_area(player_id, "hand") + return hand is not None and len(hand.children) == 1 + + +def _is_rapid_strike_pokemon(card): + return is_pokemon_card(card) and "Rapid Strike" in subtypes_for(card.archetype_id) + + +async def _rapid_strike_style_mustard(ctx): + """Put a Rapid Strike Pokemon from your discard pile onto your Bench. If you do, draw 5 cards.""" + candidates = [c for c in ctx.discard_pile() if _is_rapid_strike_pokemon(c)] + if not candidates: + return + picks = await ctx.choose_cards( + candidates, 1, minimum=1, + prompt="Put a Rapid Strike Pokémon from your discard pile onto your Bench", + ) + if not picks: + return + if await ctx.bench_pokemon(picks[0]): + await ctx.draw_cards(5) + card = SupporterCardDef( guid="4dceb2bf-4206-5709-a2ef-e167507a1ded", @@ -11,5 +37,6 @@ card = SupporterCardDef( collector_number=132, set_code="SWSH5", rarity=Rarities.Uncommon, - effect=unimplemented + effect=_rapid_strike_style_mustard, + condition=_last_card_in_hand, ) diff --git a/spirit/game/scripts/cards/SWSH5/RapidStrikeStyleMustard_162.py b/spirit/game/scripts/cards/SWSH5/RapidStrikeStyleMustard_162.py index eff9bc1..84a66b0 100644 --- a/spirit/game/scripts/cards/SWSH5/RapidStrikeStyleMustard_162.py +++ b/spirit/game/scripts/cards/SWSH5/RapidStrikeStyleMustard_162.py @@ -1,5 +1,31 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef, subtypes_for from spirit.game.attributes import Rarities +from spirit.game.session.effects import is_pokemon_card + + +def _last_card_in_hand(board, player_id): + hand = board.find_player_area(player_id, "hand") + return hand is not None and len(hand.children) == 1 + + +def _is_rapid_strike_pokemon(card): + return is_pokemon_card(card) and "Rapid Strike" in subtypes_for(card.archetype_id) + + +async def _rapid_strike_style_mustard(ctx): + """Put a Rapid Strike Pokemon from your discard pile onto your Bench. If you do, draw 5 cards.""" + candidates = [c for c in ctx.discard_pile() if _is_rapid_strike_pokemon(c)] + if not candidates: + return + picks = await ctx.choose_cards( + candidates, 1, minimum=1, + prompt="Put a Rapid Strike Pokémon from your discard pile onto your Bench", + ) + if not picks: + return + if await ctx.bench_pokemon(picks[0]): + await ctx.draw_cards(5) + card = SupporterCardDef( guid="19ffa8e0-6e28-5ea3-a2ac-7b65e3afb95d", @@ -11,5 +37,6 @@ card = SupporterCardDef( collector_number=162, set_code="SWSH5", rarity=Rarities.RareUltra, - effect=unimplemented + effect=_rapid_strike_style_mustard, + condition=_last_card_in_hand, ) diff --git a/spirit/game/scripts/cards/SWSH5/RapidStrikeStyleMustard_176.py b/spirit/game/scripts/cards/SWSH5/RapidStrikeStyleMustard_176.py index 79c1c89..f2a6ebc 100644 --- a/spirit/game/scripts/cards/SWSH5/RapidStrikeStyleMustard_176.py +++ b/spirit/game/scripts/cards/SWSH5/RapidStrikeStyleMustard_176.py @@ -1,5 +1,31 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef, subtypes_for from spirit.game.attributes import Rarities +from spirit.game.session.effects import is_pokemon_card + + +def _last_card_in_hand(board, player_id): + hand = board.find_player_area(player_id, "hand") + return hand is not None and len(hand.children) == 1 + + +def _is_rapid_strike_pokemon(card): + return is_pokemon_card(card) and "Rapid Strike" in subtypes_for(card.archetype_id) + + +async def _rapid_strike_style_mustard(ctx): + """Put a Rapid Strike Pokemon from your discard pile onto your Bench. If you do, draw 5 cards.""" + candidates = [c for c in ctx.discard_pile() if _is_rapid_strike_pokemon(c)] + if not candidates: + return + picks = await ctx.choose_cards( + candidates, 1, minimum=1, + prompt="Put a Rapid Strike Pokémon from your discard pile onto your Bench", + ) + if not picks: + return + if await ctx.bench_pokemon(picks[0]): + await ctx.draw_cards(5) + card = SupporterCardDef( guid="f79a8690-4b1d-53a6-8d36-e1f21d71c968", @@ -11,5 +37,6 @@ card = SupporterCardDef( collector_number=176, set_code="SWSH5", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=_rapid_strike_style_mustard, + condition=_last_card_in_hand, ) diff --git a/spirit/game/scripts/cards/SWSH5/RapidStrikeUrshifuVMAX_169.py b/spirit/game/scripts/cards/SWSH5/RapidStrikeUrshifuVMAX_169.py index d26d64d..0fda178 100644 --- a/spirit/game/scripts/cards/SWSH5/RapidStrikeUrshifuVMAX_169.py +++ b/spirit/game/scripts/cards/SWSH5/RapidStrikeUrshifuVMAX_169.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, snipe_attack + + +async def gmax_rapid_flow(ctx): + """Discard all Energy from this Pokémon, then 120 to 2 of the opponent's Pokémon (no W/R on Bench).""" + await ctx.discard_energy_from( + ctx.attacker, 99, prompt="Choose Energy to discard from this Pokémon" + ) + await snipe_attack(120, pool="any", count=2)(ctx) + card = PokemonCardDef( guid="167709be-e3e7-50fe-bc56-210b4f2c33cc", @@ -25,13 +35,13 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1}, damage=30, damage_operator="+", - effect=unimplemented, + effect=bonus_if(lambda ctx: ctx.entered_active_this_turn(ctx.attacker), 120), ), Attack( title="G-Max Rapid Flow", game_text="Discard all Energy from this Pok\u00e9mon. This attack does 120 damage to 2 of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=gmax_rapid_flow, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/RapidStrikeUrshifuVMAX_170.py b/spirit/game/scripts/cards/SWSH5/RapidStrikeUrshifuVMAX_170.py index 6bca92d..24e4489 100644 --- a/spirit/game/scripts/cards/SWSH5/RapidStrikeUrshifuVMAX_170.py +++ b/spirit/game/scripts/cards/SWSH5/RapidStrikeUrshifuVMAX_170.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, snipe_attack + + +async def gmax_rapid_flow(ctx): + """Discard all Energy from this Pokémon, then 120 to 2 of the opponent's Pokémon (no W/R on Bench).""" + await ctx.discard_energy_from( + ctx.attacker, 99, prompt="Choose Energy to discard from this Pokémon" + ) + await snipe_attack(120, pool="any", count=2)(ctx) + card = PokemonCardDef( guid="29f52117-c39b-5fbc-be83-3dadca165f5a", @@ -21,17 +31,17 @@ card = PokemonCardDef( abilities=[ Attack( title="Gale Thrust", - game_text="If this Pok\u00e9mon moved from your Bench to the Active Spot this turn, this attack does 120 more damage.", + game_text="If this Pokémon moved from your Bench to the Active Spot this turn, this attack does 120 more damage.", cost={PokemonTypes.FIGHTING: 1}, damage=30, damage_operator="+", - effect=unimplemented, + effect=bonus_if(lambda ctx: ctx.entered_active_this_turn(ctx.attacker), 120), ), Attack( title="G-Max Rapid Flow", - game_text="Discard all Energy from this Pok\u00e9mon. This attack does 120 damage to 2 of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", + game_text="Discard all Energy from this Pokémon. This attack does 120 damage to 2 of your opponent's Pokémon. (Don't apply Weakness and Resistance for Benched Pokémon.)", cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=gmax_rapid_flow, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH5/RapidStrikeUrshifuVMAX_88.py b/spirit/game/scripts/cards/SWSH5/RapidStrikeUrshifuVMAX_88.py index 65a5243..f4e2bee 100644 --- a/spirit/game/scripts/cards/SWSH5/RapidStrikeUrshifuVMAX_88.py +++ b/spirit/game/scripts/cards/SWSH5/RapidStrikeUrshifuVMAX_88.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, snipe_attack + + +async def gmax_rapid_flow(ctx): + """Discard all Energy from this Pokémon, then 120 to 2 of the opponent's Pokémon (no W/R on Bench).""" + await ctx.discard_energy_from( + ctx.attacker, 99, prompt="Choose Energy to discard from this Pokémon" + ) + await snipe_attack(120, pool="any", count=2)(ctx) + card = PokemonCardDef( guid="f732bc5c-f93c-5290-bb63-ca77e024d80a", @@ -21,17 +31,17 @@ card = PokemonCardDef( abilities=[ Attack( title="Gale Thrust", - game_text="If this Pok\u00e9mon moved from your Bench to the Active Spot this turn, this attack does 120 more damage.", + game_text="If this Pokémon moved from your Bench to the Active Spot this turn, this attack does 120 more damage.", cost={PokemonTypes.FIGHTING: 1}, damage=30, damage_operator="+", - effect=unimplemented, + effect=bonus_if(lambda ctx: ctx.entered_active_this_turn(ctx.attacker), 120), ), Attack( title="G-Max Rapid Flow", - game_text="Discard all Energy from this Pok\u00e9mon. This attack does 120 damage to 2 of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", + game_text="Discard all Energy from this Pokémon. This attack does 120 damage to 2 of your opponent's Pokémon. (Don't apply Weakness and Resistance for Benched Pokémon.)", cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=gmax_rapid_flow, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH5/RapidStrikeUrshifuV_152.py b/spirit/game/scripts/cards/SWSH5/RapidStrikeUrshifuV_152.py index cfef348..8b60a48 100644 --- a/spirit/game/scripts/cards/SWSH5/RapidStrikeUrshifuV_152.py +++ b/spirit/game/scripts/cards/SWSH5/RapidStrikeUrshifuV_152.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.support_common import switch_self_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="You may switch this Pok\u00e9mon with 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.FIGHTING: 1}, damage=30, - effect=unimplemented, + effect=switch_self_attack(optional=True), ), Attack( title="Hundred Furious Blows", diff --git a/spirit/game/scripts/cards/SWSH5/RapidStrikeUrshifuV_153.py b/spirit/game/scripts/cards/SWSH5/RapidStrikeUrshifuV_153.py index 54336a6..012eb29 100644 --- a/spirit/game/scripts/cards/SWSH5/RapidStrikeUrshifuV_153.py +++ b/spirit/game/scripts/cards/SWSH5/RapidStrikeUrshifuV_153.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.support_common import switch_self_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="You may switch this Pok\u00e9mon with 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.FIGHTING: 1}, damage=30, - effect=unimplemented, + effect=switch_self_attack(optional=True), ), Attack( title="Hundred Furious Blows", diff --git a/spirit/game/scripts/cards/SWSH5/RapidStrikeUrshifuV_87.py b/spirit/game/scripts/cards/SWSH5/RapidStrikeUrshifuV_87.py index 708b3fd..d0744b3 100644 --- a/spirit/game/scripts/cards/SWSH5/RapidStrikeUrshifuV_87.py +++ b/spirit/game/scripts/cards/SWSH5/RapidStrikeUrshifuV_87.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.support_common import switch_self_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="You may switch this Pok\u00e9mon with 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.FIGHTING: 1}, damage=30, - effect=unimplemented, + effect=switch_self_attack(optional=True), ), Attack( title="Hundred Furious Blows", diff --git a/spirit/game/scripts/cards/SWSH5/Rolycoly_78.py b/spirit/game/scripts/cards/SWSH5/Rolycoly_78.py index b13b00a..8f7a152 100644 --- a/spirit/game/scripts/cards/SWSH5/Rolycoly_78.py +++ b/spirit/game/scripts/cards/SWSH5/Rolycoly_78.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack card = PokemonCardDef( guid="7d9aa636-7878-5db4-a922-595f89fe8d4a", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="This Pok\u00e9mon also does 10 damage to itself.", cost={PokemonTypes.FIGHTING: 1}, damage=30, - effect=unimplemented, + effect=recoil_attack(10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/Salandit_27.py b/spirit/game/scripts/cards/SWSH5/Salandit_27.py index a56053d..a0563e6 100644 --- a/spirit/game/scripts/cards/SWSH5/Salandit_27.py +++ b/spirit/game/scripts/cards/SWSH5/Salandit_27.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.session.effects import is_pokemon_card card = PokemonCardDef( guid="b2fd8338-04ba-5d9b-b287-13edb65beede", @@ -22,7 +24,8 @@ card = PokemonCardDef( title="Call Sign", game_text="Search your deck for a Pok\u00e9mon, reveal it, and put it into your hand. Then, shuffle your deck.", cost={PokemonTypes.FIRE: 1}, - effect=unimplemented, + effect=search_to_hand(is_pokemon_card, count=1, minimum=0, + reveal=True, prompt="Choose a Pokémon."), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/Salazzle_28.py b/spirit/game/scripts/cards/SWSH5/Salazzle_28.py index 78f65fb..5a792ea 100644 --- a/spirit/game/scripts/cards/SWSH5/Salazzle_28.py +++ b/spirit/game/scripts/cards/SWSH5/Salazzle_28.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions, AttrID +from spirit.game.card_effects.attacks_common import condition_attack, damage_per + + +def _defender_condition_count(ctx): + defender = ctx.defender + if defender is None: + return 0 + return len(defender.get_attribute(AttrID.SPECIAL_CONDITIONS) or []) + card = PokemonCardDef( guid="3df6a189-2dcb-5f3e-9b2d-120937b90b9e", @@ -23,7 +32,7 @@ card = PokemonCardDef( title="Perplex", game_text="Your opponent's Active Pok\u00e9mon is now Confused.", cost={PokemonTypes.FIRE: 1}, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED), ), Attack( title="Derisive Roasting", @@ -31,7 +40,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=90, damage_operator="x", - effect=unimplemented, + effect=damage_per(_defender_condition_count, 90), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/Sandaconda_82.py b/spirit/game/scripts/cards/SWSH5/Sandaconda_82.py index 5d89dc2..a257ba4 100644 --- a/spirit/game/scripts/cards/SWSH5/Sandaconda_82.py +++ b/spirit/game/scripts/cards/SWSH5/Sandaconda_82.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import mill_scaled_damage +from spirit.game.card_effects.pokemon import is_energy_card + + +def _is_fighting_energy(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_energy_card(card) and PokemonTypes.FIGHTING.value in types + card = PokemonCardDef( guid="298d1d7f-9206-5fd0-a53d-d09c1173194a", @@ -25,7 +33,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1}, damage=60, damage_operator="x", - effect=unimplemented, + effect=mill_scaled_damage(6, 60, pred=_is_fighting_energy), ), Attack( title="Skull Bash", diff --git a/spirit/game/scripts/cards/SWSH5/Scatterbug_11.py b/spirit/game/scripts/cards/SWSH5/Scatterbug_11.py index 343f667..d989cc8 100644 --- a/spirit/game/scripts/cards/SWSH5/Scatterbug_11.py +++ b/spirit/game/scripts/cards/SWSH5/Scatterbug_11.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_or_nothing card = PokemonCardDef( guid="527bc6c0-f724-5396-8fd1-527d8e9a1e86", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Flip a coin. If tails, this attack does nothing.", cost={PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=flip_or_nothing(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/SingleStrikeScrollofScorn_133.py b/spirit/game/scripts/cards/SWSH5/SingleStrikeScrollofScorn_133.py index 50b0b7c..1e9c44a 100644 --- a/spirit/game/scripts/cards/SWSH5/SingleStrikeScrollofScorn_133.py +++ b/spirit/game/scripts/cards/SWSH5/SingleStrikeScrollofScorn_133.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import PokemonToolCardDef, Attack, subtypes_for +from spirit.game.attributes import PokemonTypes, Rarities +from spirit.game.card_effects.attacks_common import damage_per, damage_counters_on + + +def _is_single_strike(board, player_id, pokemon): + return "Single Strike" in subtypes_for(pokemon.archetype_id) + card = PokemonToolCardDef( guid="5233557e-7d9a-5977-8ea9-92d1bf89796a", @@ -11,5 +17,15 @@ card = PokemonToolCardDef( collector_number=133, set_code="SWSH5", rarity=Rarities.Uncommon, - effect=unimplemented + granted_abilities=[ + Attack( + title="Furious Anger", + game_text="This attack does 10 more damage for each damage counter on this Pok\u00e9mon.", + cost={PokemonTypes.FIGHTING: 1}, + damage=10, + damage_operator="+", + effect=damage_per(damage_counters_on("self"), 10, base=10), + condition=_is_single_strike, + ), + ], ) diff --git a/spirit/game/scripts/cards/SWSH5/SingleStrikeStyleMustard_134.py b/spirit/game/scripts/cards/SWSH5/SingleStrikeStyleMustard_134.py index ee92581..aa638dc 100644 --- a/spirit/game/scripts/cards/SWSH5/SingleStrikeStyleMustard_134.py +++ b/spirit/game/scripts/cards/SWSH5/SingleStrikeStyleMustard_134.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef, subtypes_for from spirit.game.attributes import Rarities +from spirit.game.session.effects import is_pokemon_card +from spirit.game.card_effects.support_common import search_to_bench + + +def _last_card_in_hand(board, player_id): + hand = board.find_player_area(player_id, "hand") + return hand is not None and len(hand.children) == 1 + + +def _is_single_strike_pokemon(card): + return is_pokemon_card(card) and "Single Strike" in subtypes_for(card.archetype_id) + + +async def _draw_five(ctx, benched): + await ctx.draw_cards(5) + card = SupporterCardDef( guid="33b83541-71ea-58f9-803e-e6143d51e6c2", @@ -11,5 +27,9 @@ card = SupporterCardDef( collector_number=134, set_code="SWSH5", rarity=Rarities.Uncommon, - effect=unimplemented + condition=_last_card_in_hand, + effect=search_to_bench( + _is_single_strike_pokemon, count=1, then=_draw_five, + prompt="Choose a Single Strike Pokémon to put onto your Bench.", + ), ) diff --git a/spirit/game/scripts/cards/SWSH5/SingleStrikeStyleMustard_163.py b/spirit/game/scripts/cards/SWSH5/SingleStrikeStyleMustard_163.py index e2489a3..466e71a 100644 --- a/spirit/game/scripts/cards/SWSH5/SingleStrikeStyleMustard_163.py +++ b/spirit/game/scripts/cards/SWSH5/SingleStrikeStyleMustard_163.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef, subtypes_for from spirit.game.attributes import Rarities +from spirit.game.session.effects import is_pokemon_card +from spirit.game.card_effects.support_common import search_to_bench + + +def _last_card_in_hand(board, player_id): + hand = board.find_player_area(player_id, "hand") + return hand is not None and len(hand.children) == 1 + + +def _is_single_strike_pokemon(card): + return is_pokemon_card(card) and "Single Strike" in subtypes_for(card.archetype_id) + + +async def _draw_five(ctx, benched): + await ctx.draw_cards(5) + card = SupporterCardDef( guid="8e05d3b8-3abb-56b9-ab28-276cb3bbaf2d", @@ -11,5 +27,9 @@ card = SupporterCardDef( collector_number=163, set_code="SWSH5", rarity=Rarities.RareUltra, - effect=unimplemented + condition=_last_card_in_hand, + effect=search_to_bench( + _is_single_strike_pokemon, count=1, then=_draw_five, + prompt="Choose a Single Strike Pokémon to put onto your Bench.", + ), ) diff --git a/spirit/game/scripts/cards/SWSH5/SingleStrikeStyleMustard_177.py b/spirit/game/scripts/cards/SWSH5/SingleStrikeStyleMustard_177.py index 325dbb3..c56f6d4 100644 --- a/spirit/game/scripts/cards/SWSH5/SingleStrikeStyleMustard_177.py +++ b/spirit/game/scripts/cards/SWSH5/SingleStrikeStyleMustard_177.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef, subtypes_for from spirit.game.attributes import Rarities +from spirit.game.session.effects import is_pokemon_card +from spirit.game.card_effects.support_common import search_to_bench + + +def _last_card_in_hand(board, player_id): + hand = board.find_player_area(player_id, "hand") + return hand is not None and len(hand.children) == 1 + + +def _is_single_strike_pokemon(card): + return is_pokemon_card(card) and "Single Strike" in subtypes_for(card.archetype_id) + + +async def _draw_five(ctx, benched): + await ctx.draw_cards(5) + card = SupporterCardDef( guid="15d914ee-db10-5f19-aa8d-ce90ebd3b3e7", @@ -11,5 +27,9 @@ card = SupporterCardDef( collector_number=177, set_code="SWSH5", rarity=Rarities.RareRainbow, - effect=unimplemented + condition=_last_card_in_hand, + effect=search_to_bench( + _is_single_strike_pokemon, count=1, then=_draw_five, + prompt="Choose a Single Strike Pokémon to put onto your Bench.", + ), ) diff --git a/spirit/game/scripts/cards/SWSH5/SingleStrikeUrshifuVMAX_167.py b/spirit/game/scripts/cards/SWSH5/SingleStrikeUrshifuVMAX_167.py index 9263a2c..972f566 100644 --- a/spirit/game/scripts/cards/SWSH5/SingleStrikeUrshifuVMAX_167.py +++ b/spirit/game/scripts/cards/SWSH5/SingleStrikeUrshifuVMAX_167.py @@ -1,6 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def gmax_one_blow(ctx): + await ctx.discard_energy_from( + ctx.attacker, 99, prompt="Discard all Energy from this Pokémon" + ) + await ctx.deal_damage(ignore_target_effects=True) + + card = PokemonCardDef( guid="b53ae03b-3831-58e6-82a1-af37317ef96c", key="SWSH5", @@ -29,7 +37,7 @@ card = PokemonCardDef( game_text="Discard all Energy from this Pok\u00e9mon. This attack's damage isn't affected by any effects on your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.FIGHTING: 3, PokemonTypes.COLORLESS: 1}, damage=270, - effect=unimplemented, + effect=gmax_one_blow, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/SingleStrikeUrshifuVMAX_168.py b/spirit/game/scripts/cards/SWSH5/SingleStrikeUrshifuVMAX_168.py index 24974fd..38868e5 100644 --- a/spirit/game/scripts/cards/SWSH5/SingleStrikeUrshifuVMAX_168.py +++ b/spirit/game/scripts/cards/SWSH5/SingleStrikeUrshifuVMAX_168.py @@ -1,6 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def gmax_one_blow(ctx): + await ctx.discard_energy_from( + ctx.attacker, 99, prompt="Discard all Energy from this Pokémon" + ) + await ctx.deal_damage(ignore_target_effects=True) + + card = PokemonCardDef( guid="e07b10b1-f283-5752-8ccf-0dada1aeb856", key="SWSH5", @@ -29,7 +37,7 @@ card = PokemonCardDef( game_text="Discard all Energy from this Pok\u00e9mon. This attack's damage isn't affected by any effects on your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.FIGHTING: 3, PokemonTypes.COLORLESS: 1}, damage=270, - effect=unimplemented, + effect=gmax_one_blow, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/SingleStrikeUrshifuVMAX_86.py b/spirit/game/scripts/cards/SWSH5/SingleStrikeUrshifuVMAX_86.py index 7a84111..0d683d8 100644 --- a/spirit/game/scripts/cards/SWSH5/SingleStrikeUrshifuVMAX_86.py +++ b/spirit/game/scripts/cards/SWSH5/SingleStrikeUrshifuVMAX_86.py @@ -1,6 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def gmax_one_blow(ctx): + await ctx.discard_energy_from( + ctx.attacker, 99, prompt="Discard all Energy from this Pokémon" + ) + await ctx.deal_damage(ignore_target_effects=True) + + card = PokemonCardDef( guid="7f8ae11f-80b2-53f7-86b5-b36c2fe753d3", key="SWSH5", @@ -29,7 +37,7 @@ card = PokemonCardDef( game_text="Discard all Energy from this Pok\u00e9mon. This attack's damage isn't affected by any effects on your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.FIGHTING: 3, PokemonTypes.COLORLESS: 1}, damage=270, - effect=unimplemented, + effect=gmax_one_blow, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/SingleStrikeUrshifuV_150.py b/spirit/game/scripts/cards/SWSH5/SingleStrikeUrshifuV_150.py index 8e9aeaf..01896e3 100644 --- a/spirit/game/scripts/cards/SWSH5/SingleStrikeUrshifuV_150.py +++ b/spirit/game/scripts/cards/SWSH5/SingleStrikeUrshifuV_150.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_attach_energy +from spirit.game.card_effects.pokemon import is_energy_card, energy_provides_type + + +def _is_fighting_energy(card): + return is_energy_card(card) and energy_provides_type(card, PokemonTypes.FIGHTING.value) + + +laser_focus = search_attach_energy( + predicate=_is_fighting_energy, count=2, to_self=True, + prompt="Choose up to 2 Fighting Energy cards to attach.", +) + card = PokemonCardDef( guid="2b4e5379-4f7a-58a8-8ab7-159118723a85", @@ -22,14 +35,14 @@ card = PokemonCardDef( title="Laser Focus", game_text="Search your deck for up to 2 Fighting Energy cards and attach them to this Pok\u00e9mon. Then, shuffle your deck.", cost={PokemonTypes.FIGHTING: 1}, - effect=unimplemented, + effect=laser_focus, ), Attack( title="Impact Blow", game_text="During your next turn, this Pok\u00e9mon can't use Impact Blow.", cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=180, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/SingleStrikeUrshifuV_151.py b/spirit/game/scripts/cards/SWSH5/SingleStrikeUrshifuV_151.py index be34a42..9d4d0bf 100644 --- a/spirit/game/scripts/cards/SWSH5/SingleStrikeUrshifuV_151.py +++ b/spirit/game/scripts/cards/SWSH5/SingleStrikeUrshifuV_151.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_attach_energy +from spirit.game.card_effects.pokemon import is_energy_card, energy_provides_type + + +def _is_fighting_energy(card): + return is_energy_card(card) and energy_provides_type(card, PokemonTypes.FIGHTING.value) + + +laser_focus = search_attach_energy( + predicate=_is_fighting_energy, count=2, to_self=True, + prompt="Choose up to 2 Fighting Energy cards to attach.", +) + card = PokemonCardDef( guid="d480f7f1-7549-5455-9c7e-e7520e7490bf", @@ -22,14 +35,14 @@ card = PokemonCardDef( title="Laser Focus", game_text="Search your deck for up to 2 Fighting Energy cards and attach them to this Pok\u00e9mon. Then, shuffle your deck.", cost={PokemonTypes.FIGHTING: 1}, - effect=unimplemented, + effect=laser_focus, ), Attack( title="Impact Blow", game_text="During your next turn, this Pok\u00e9mon can't use Impact Blow.", cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=180, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/SingleStrikeUrshifuV_85.py b/spirit/game/scripts/cards/SWSH5/SingleStrikeUrshifuV_85.py index 0baee4b..86554bd 100644 --- a/spirit/game/scripts/cards/SWSH5/SingleStrikeUrshifuV_85.py +++ b/spirit/game/scripts/cards/SWSH5/SingleStrikeUrshifuV_85.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_attach_energy +from spirit.game.card_effects.pokemon import is_energy_card, energy_provides_type + + +def _is_fighting_energy(card): + return is_energy_card(card) and energy_provides_type(card, PokemonTypes.FIGHTING.value) + + +laser_focus = search_attach_energy( + predicate=_is_fighting_energy, count=2, to_self=True, + prompt="Choose up to 2 Fighting Energy cards to attach.", +) + card = PokemonCardDef( guid="9ff2cdba-3376-5025-8bf2-cd5086770cb4", @@ -22,14 +35,14 @@ card = PokemonCardDef( title="Laser Focus", game_text="Search your deck for up to 2 Fighting Energy cards and attach them to this Pok\u00e9mon. Then, shuffle your deck.", cost={PokemonTypes.FIGHTING: 1}, - effect=unimplemented, + effect=laser_focus, ), Attack( title="Impact Blow", game_text="During your next turn, this Pok\u00e9mon can't use Impact Blow.", cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=180, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/Sizzlipede_29.py b/spirit/game/scripts/cards/SWSH5/Sizzlipede_29.py index 4685d9b..bed6338 100644 --- a/spirit/game/scripts/cards/SWSH5/Sizzlipede_29.py +++ b/spirit/game/scripts/cards/SWSH5/Sizzlipede_29.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="8829e15a-c84f-557c-9561-1c671d8e5d08", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Burned.", cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 1}, damage=20, - effect=unimplemented, + effect=condition_attack(SpecialConditions.BURNED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/SordwardShielbert_135.py b/spirit/game/scripts/cards/SWSH5/SordwardShielbert_135.py index 4294df7..e5758f7 100644 --- a/spirit/game/scripts/cards/SWSH5/SordwardShielbert_135.py +++ b/spirit/game/scripts/cards/SWSH5/SordwardShielbert_135.py @@ -1,5 +1,26 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.session.effects import is_trainer_card +from spirit.game.card_effects.support_common import requires_discard + + +async def _sordward_shielbert(ctx): + discard = [c for c in ctx.discard_pile() if is_trainer_card(c)] + if not discard: + return + picks = await ctx.choose_cards( + discard, 1, minimum=1, prompt="Choose a Trainer card from your discard pile.", + ) + if not picks: + return + if await ctx.ask_yes_no( + "Your opponent chose a Trainer card from their discard pile. May they put it into their hand?", + player_id=ctx.opponent_id, + ): + await ctx.put_in_hand(picks, reveal=False) + else: + await ctx.draw_cards(3) + card = SupporterCardDef( guid="00d3fea6-aaa9-50dc-9a72-2ea453067678", @@ -11,5 +32,6 @@ card = SupporterCardDef( collector_number=135, set_code="SWSH5", rarity=Rarities.Uncommon, - effect=unimplemented + condition=requires_discard(is_trainer_card), + effect=_sordward_shielbert, ) diff --git a/spirit/game/scripts/cards/SWSH5/Spewpa_12.py b/spirit/game/scripts/cards/SWSH5/Spewpa_12.py index fdca789..39a9c8c 100644 --- a/spirit/game/scripts/cards/SWSH5/Spewpa_12.py +++ b/spirit/game/scripts/cards/SWSH5/Spewpa_12.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import heal_attack card = PokemonCardDef( guid="b9c86443-05ff-531b-894e-d703264dba01", @@ -23,7 +24,7 @@ card = PokemonCardDef( title="Grass Cocooning", game_text="Heal 40 damage from this Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=heal_attack(40, target="self"), ), Attack( title="Bug Bite", diff --git a/spirit/game/scripts/cards/SWSH5/Steelix_99.py b/spirit/game/scripts/cards/SWSH5/Steelix_99.py index 588ef1d..1f76f78 100644 --- a/spirit/game/scripts/cards/SWSH5/Steelix_99.py +++ b/spirit/game/scripts/cards/SWSH5/Steelix_99.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="7dc2d311-f71b-522f-8145-514191d543d4", @@ -26,7 +27,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=80, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=2, per_heads=80), ), Attack( title="Heavy Impact", diff --git a/spirit/game/scripts/cards/SWSH5/Stonjourner_84.py b/spirit/game/scripts/cards/SWSH5/Stonjourner_84.py index 39f594f..09fb661 100644 --- a/spirit/game/scripts/cards/SWSH5/Stonjourner_84.py +++ b/spirit/game/scripts/cards/SWSH5/Stonjourner_84.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if card = PokemonCardDef( guid="96aad365-5d3b-5ffb-9bcc-e95d9c4eeae5", @@ -24,14 +25,14 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 1}, damage=30, damage_operator="+", - effect=unimplemented, + effect=bonus_if(lambda ctx: ctx.stadium_in_play() is not None, 30), ), Attack( title="Giga Hammer", game_text="During your next turn, this Pok\u00e9mon can't use Giga Hammer.", cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/StoutlandV_157.py b/spirit/game/scripts/cards/SWSH5/StoutlandV_157.py index e4f7788..7885162 100644 --- a/spirit/game/scripts/cards/SWSH5/StoutlandV_157.py +++ b/spirit/game/scripts/cards/SWSH5/StoutlandV_157.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import double_dip_fangs, wild_tackle card = PokemonCardDef( guid="3bcd0555-e43f-55c0-b490-d2660a763208", @@ -23,14 +24,14 @@ card = PokemonCardDef( game_text="If your opponent's Basic Pok\u00e9mon is Knocked Out by damage from this attack, take 1 more Prize card.", cost={PokemonTypes.COLORLESS: 3}, damage=40, - effect=unimplemented, + effect=double_dip_fangs, ), Attack( title="Wild Tackle", game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.COLORLESS: 4}, damage=200, - effect=unimplemented, + effect=wild_tackle, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/TapuBulu_16.py b/spirit/game/scripts/cards/SWSH5/TapuBulu_16.py index 99206ac..2cfb89a 100644 --- a/spirit/game/scripts/cards/SWSH5/TapuBulu_16.py +++ b/spirit/game/scripts/cards/SWSH5/TapuBulu_16.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import gust_attack + + +async def natures_judgment(ctx): + """You may discard all Energy from this Pokémon. If you do, this attack + does 80 more damage.""" + bonus = 0 + if ctx.attached_energies(ctx.attacker) and await ctx.ask_yes_no( + "Discard all Energy from this Pokémon?" + ): + await ctx.discard_energy_from(ctx.attacker, 99) + bonus = 80 + await ctx.deal_damage(80 + bonus) + card = PokemonCardDef( guid="26246f22-7dcd-5193-8746-d2170e058d70", @@ -23,7 +37,7 @@ card = PokemonCardDef( game_text="Your opponent switches their Active Pok\u00e9mon with 1 of their Benched Pok\u00e9mon.", cost={PokemonTypes.GRASS: 1}, damage=20, - effect=unimplemented, + effect=gust_attack(opponent_chooses=True), ), Attack( title="Nature's Judgment", @@ -31,7 +45,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 2, PokemonTypes.COLORLESS: 1}, damage=80, damage_operator="+", - effect=unimplemented, + effect=natures_judgment, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/TapuKokoVMAX_166.py b/spirit/game/scripts/cards/SWSH5/TapuKokoVMAX_166.py index 9e8d5e9..8762372 100644 --- a/spirit/game/scripts/cards/SWSH5/TapuKokoVMAX_166.py +++ b/spirit/game/scripts/cards/SWSH5/TapuKokoVMAX_166.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions + + +async def max_shock(ctx): + """Printed damage; if you have more Prizes remaining, paralyze the Active.""" + await ctx.deal_damage() + if ctx.prizes_taken(ctx.player_id) < ctx.prizes_taken(ctx.opponent_id): + await ctx.apply_special_condition(ctx.defender, SpecialConditions.PARALYZED) + card = PokemonCardDef( guid="f247e83c-c051-5d5a-9c73-2611af48bcad", @@ -24,7 +32,7 @@ card = PokemonCardDef( game_text="If you have more Prize cards remaining than your opponent, their Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 1}, damage=180, - effect=unimplemented, + effect=max_shock, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/TapuKokoVMAX_51.py b/spirit/game/scripts/cards/SWSH5/TapuKokoVMAX_51.py index ca963fa..ed42b96 100644 --- a/spirit/game/scripts/cards/SWSH5/TapuKokoVMAX_51.py +++ b/spirit/game/scripts/cards/SWSH5/TapuKokoVMAX_51.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions + + +async def max_shock(ctx): + """Printed damage; if you have more Prizes remaining, paralyze the Active.""" + await ctx.deal_damage() + if ctx.prizes_taken(ctx.player_id) < ctx.prizes_taken(ctx.opponent_id): + await ctx.apply_special_condition(ctx.defender, SpecialConditions.PARALYZED) + card = PokemonCardDef( guid="a1fca07d-648c-588d-8ced-ad5b8527c5c6", @@ -24,7 +32,7 @@ card = PokemonCardDef( game_text="If you have more Prize cards remaining than your opponent, their Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 1}, damage=180, - effect=unimplemented, + effect=max_shock, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/TapuKokoV_147.py b/spirit/game/scripts/cards/SWSH5/TapuKokoV_147.py index 7630689..a39c54c 100644 --- a/spirit/game/scripts/cards/SWSH5/TapuKokoV_147.py +++ b/spirit/game/scripts/cards/SWSH5/TapuKokoV_147.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy card = PokemonCardDef( guid="a60c0709-cc1e-55d5-84b9-8c6a477084ca", @@ -29,7 +30,7 @@ card = PokemonCardDef( cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("opponent"), 40, base=20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/TapuKokoV_50.py b/spirit/game/scripts/cards/SWSH5/TapuKokoV_50.py index 15eff9f..b14776f 100644 --- a/spirit/game/scripts/cards/SWSH5/TapuKokoV_50.py +++ b/spirit/game/scripts/cards/SWSH5/TapuKokoV_50.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy card = PokemonCardDef( guid="c39d5335-b23a-5bb9-bb92-3d6392c947d7", @@ -29,7 +30,7 @@ card = PokemonCardDef( cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("opponent"), 40, base=20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/ToolJammer_136.py b/spirit/game/scripts/cards/SWSH5/ToolJammer_136.py index 26c341b..4cc653b 100644 --- a/spirit/game/scripts/cards/SWSH5/ToolJammer_136.py +++ b/spirit/game/scripts/cards/SWSH5/ToolJammer_136.py @@ -1,15 +1,37 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import PokemonToolCardDef, def_for +from spirit.game.attributes import AttrID, Rarities +from spirit.game.session.passives import Passive, carrier_pokemon + + +def _is_active(pokemon) -> bool: + parent = pokemon.parent if pokemon is not None else None + return bool(parent) and parent.get_attribute(AttrID.NAME) == "activePokemonArea" + + +class ToolJammerPassive(Passive): + """While the holder is Active, Tools attached to the opponent's Active + Pokemon have no effect, except for Tool Jammer.""" + + def suppresses_tool(self, tool, carrier): + if getattr(def_for(tool.archetype_id), "display_name", "") == "Tool Jammer": + return False + holder = carrier_pokemon(carrier) + jammed = carrier_pokemon(tool) + if holder is None or jammed is None \ + or holder.owning_player_id == jammed.owning_player_id: + return False + return _is_active(holder) and _is_active(jammed) + card = PokemonToolCardDef( guid="ee809237-f3fb-54f9-95b2-44978dbf0aaf", key="SWSH5", name="com.direwolfdigital.cake.data.archetypes.trainer.ToolJammer.Name", display_name="Tool Jammer", - searchable_by=["Tool Jammer", "Pok\u00c3\u00a9mon Tool"], - subtypes=["Pok\u00e9mon Tool"], + searchable_by=["Tool Jammer", "Pokémon Tool"], + subtypes=["Pokémon Tool"], collector_number=136, set_code="SWSH5", rarity=Rarities.Uncommon, - effect=unimplemented + passive=ToolJammerPassive(), ) diff --git a/spirit/game/scripts/cards/SWSH5/TowerofDarkness_137.py b/spirit/game/scripts/cards/SWSH5/TowerofDarkness_137.py index 35f0023..7302c89 100644 --- a/spirit/game/scripts/cards/SWSH5/TowerofDarkness_137.py +++ b/spirit/game/scripts/cards/SWSH5/TowerofDarkness_137.py @@ -1,6 +1,29 @@ -from spirit.game.data_utils import StadiumCardDef, unimplemented +from spirit.game.data_utils import StadiumCardDef, Ability, Activations, subtypes_for from spirit.game.attributes import Rarities + +def _is_single_strike_card(card): + return "Single Strike" in subtypes_for(card.archetype_id) + + +def tower_of_darkness_condition(board, player_id, stadium): + hand = board.find_player_area(player_id, "hand") + return bool(hand) and any(_is_single_strike_card(c) for c in hand.children) + + +async def tower_of_darkness_ability(ctx): + """Once during each player's turn, that player may draw 2 cards by + discarding a Single Strike card from their hand.""" + picks = await ctx.choose_cards( + [c for c in ctx.hand() if _is_single_strike_card(c)], 1, minimum=1, + prompt="Discard a Single Strike card to draw 2 cards.", + ) + if not picks: + return + await ctx.discard_cards(picks) + await ctx.draw_cards(2) + + card = StadiumCardDef( guid="97e3003b-223c-5631-88d6-1bdae2588e4e", key="SWSH5", @@ -11,5 +34,11 @@ card = StadiumCardDef( collector_number=137, set_code="SWSH5", rarity=Rarities.Uncommon, - effect=unimplemented + ability=Ability( + title="Tower of Darkness", + game_text="Once during each player's turn, that player may draw 2 cards. In order to use this effect, that player must discard a Single Strike card from their hand.", + activation=Activations.ONCE_PER_TURN, + effect=tower_of_darkness_ability, + condition=tower_of_darkness_condition, + ), ) diff --git a/spirit/game/scripts/cards/SWSH5/TowerofWaters_138.py b/spirit/game/scripts/cards/SWSH5/TowerofWaters_138.py index ab1b560..2b3bdf0 100644 --- a/spirit/game/scripts/cards/SWSH5/TowerofWaters_138.py +++ b/spirit/game/scripts/cards/SWSH5/TowerofWaters_138.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import StadiumCardDef, unimplemented +from spirit.game.data_utils import StadiumCardDef, subtypes_for from spirit.game.attributes import Rarities +from spirit.game.card_effects.passives_common import retreat_discount card = StadiumCardDef( guid="94bb45dd-7c82-57ef-ba64-f25f0a4c2e85", @@ -11,5 +12,7 @@ card = StadiumCardDef( collector_number=138, set_code="SWSH5", rarity=Rarities.Uncommon, - effect=unimplemented + passive=retreat_discount( + 2, target_pred=lambda p, c: "Rapid Strike" in subtypes_for(p.archetype_id) + ), ) diff --git a/spirit/game/scripts/cards/SWSH5/TyranitarV_154.py b/spirit/game/scripts/cards/SWSH5/TyranitarV_154.py index e70b262..91d7da3 100644 --- a/spirit/game/scripts/cards/SWSH5/TyranitarV_154.py +++ b/spirit/game/scripts/cards/SWSH5/TyranitarV_154.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import mill_attack card = PokemonCardDef( guid="2cf2263a-26d7-5bf4-9742-506c20ddad33", @@ -23,14 +24,14 @@ card = PokemonCardDef( game_text="Discard the top 2 cards of your opponent's deck.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 2}, damage=60, - effect=unimplemented, + effect=mill_attack(2, opponent=True), ), Attack( title="Single Strike Crush", game_text="Discard the top 4 cards of your deck.", cost={PokemonTypes.DARKNESS: 2, PokemonTypes.COLORLESS: 2}, damage=240, - effect=unimplemented, + effect=mill_attack(4, opponent=False), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH5/TyranitarV_155.py b/spirit/game/scripts/cards/SWSH5/TyranitarV_155.py index cb3e690..05689ed 100644 --- a/spirit/game/scripts/cards/SWSH5/TyranitarV_155.py +++ b/spirit/game/scripts/cards/SWSH5/TyranitarV_155.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import mill_attack card = PokemonCardDef( guid="1285adcc-d2c7-540f-b918-8ebf9d2a7e71", @@ -23,14 +24,14 @@ card = PokemonCardDef( game_text="Discard the top 2 cards of your opponent's deck.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 2}, damage=60, - effect=unimplemented, + effect=mill_attack(2, opponent=True), ), Attack( title="Single Strike Crush", game_text="Discard the top 4 cards of your deck.", cost={PokemonTypes.DARKNESS: 2, PokemonTypes.COLORLESS: 2}, damage=240, - effect=unimplemented, + effect=mill_attack(4, opponent=False), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH5/TyranitarV_97.py b/spirit/game/scripts/cards/SWSH5/TyranitarV_97.py index 3241a7e..d1fb13e 100644 --- a/spirit/game/scripts/cards/SWSH5/TyranitarV_97.py +++ b/spirit/game/scripts/cards/SWSH5/TyranitarV_97.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import mill_attack card = PokemonCardDef( guid="ed01b5eb-45b3-5158-b1cf-8a282c8b353d", @@ -23,14 +24,14 @@ card = PokemonCardDef( game_text="Discard the top 2 cards of your opponent's deck.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 2}, damage=60, - effect=unimplemented, + effect=mill_attack(2, opponent=True), ), Attack( title="Single Strike Crush", game_text="Discard the top 4 cards of your deck.", cost={PokemonTypes.DARKNESS: 2, PokemonTypes.COLORLESS: 2}, damage=240, - effect=unimplemented, + effect=mill_attack(4, opponent=False), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH5/UrnofVitality_139.py b/spirit/game/scripts/cards/SWSH5/UrnofVitality_139.py index dcf8624..5567584 100644 --- a/spirit/game/scripts/cards/SWSH5/UrnofVitality_139.py +++ b/spirit/game/scripts/cards/SWSH5/UrnofVitality_139.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef, subtypes_for from spirit.game.attributes import Rarities +from spirit.game.card_effects.support_common import recover_from_discard, requires_discard + + +def _is_single_strike_energy(card): + return "Single Strike" in subtypes_for(card.archetype_id) + card = ItemCardDef( guid="ba13ea01-e36c-5ae8-8d5b-2c10ca6f45d7", @@ -11,5 +17,9 @@ card = ItemCardDef( collector_number=139, set_code="SWSH5", rarity=Rarities.Uncommon, - effect=unimplemented + condition=requires_discard(_is_single_strike_energy), + effect=recover_from_discard( + _is_single_strike_energy, count=2, minimum=1, reveal=False, to="deck_shuffle", + prompt="Choose up to 2 Single Strike Energy cards to shuffle into your deck.", + ), ) diff --git a/spirit/game/scripts/cards/SWSH5/VictiniVMAX_165.py b/spirit/game/scripts/cards/SWSH5/VictiniVMAX_165.py index d7f7ef6..d8cc46e 100644 --- a/spirit/game/scripts/cards/SWSH5/VictiniVMAX_165.py +++ b/spirit/game/scripts/cards/SWSH5/VictiniVMAX_165.py @@ -1,5 +1,31 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, defender_is_v +from spirit.game.card_effects.pokemon import energy_provides_type +from spirit.game.card_effects.support_common import distribute_energy + + +def _is_fire_energy(card): + return energy_provides_type(card, PokemonTypes.FIRE.value) + + +async def spreading_flames(ctx): + """Attach up to 3 Fire Energy cards from your discard pile to your + Pokemon, any way you like.""" + cards = [c for c in ctx.discard_pile() if _is_fire_energy(c)] + if not cards: + return + picks = await ctx.choose_cards( + cards, 3, minimum=0, + prompt="Choose up to 3 Fire Energy cards from your discard pile to attach.", + ) + if not picks: + return + candidates = ctx.my_pokemon_in_play() + if not candidates: + return + await distribute_energy(ctx, picks, candidates) + card = PokemonCardDef( guid="03ce083f-0c51-57cb-a688-1267edd478f5", @@ -23,7 +49,7 @@ card = PokemonCardDef( title="Spreading Flames", game_text="Attach up to 3 Fire Energy cards from your discard pile to your Pok\u00e9mon in any way you like.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=spreading_flames, ), Attack( title="Max Victory", @@ -31,7 +57,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 1}, damage=100, damage_operator="+", - effect=unimplemented, + effect=bonus_if(defender_is_v, 120), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/VictiniVMAX_22.py b/spirit/game/scripts/cards/SWSH5/VictiniVMAX_22.py index 6ff3703..afd120e 100644 --- a/spirit/game/scripts/cards/SWSH5/VictiniVMAX_22.py +++ b/spirit/game/scripts/cards/SWSH5/VictiniVMAX_22.py @@ -1,5 +1,31 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, defender_is_v +from spirit.game.card_effects.pokemon import energy_provides_type +from spirit.game.card_effects.support_common import distribute_energy + + +def _is_fire_energy(card): + return energy_provides_type(card, PokemonTypes.FIRE.value) + + +async def spreading_flames(ctx): + """Attach up to 3 Fire Energy cards from your discard pile to your + Pokemon, any way you like.""" + cards = [c for c in ctx.discard_pile() if _is_fire_energy(c)] + if not cards: + return + picks = await ctx.choose_cards( + cards, 3, minimum=0, + prompt="Choose up to 3 Fire Energy cards from your discard pile to attach.", + ) + if not picks: + return + candidates = ctx.my_pokemon_in_play() + if not candidates: + return + await distribute_energy(ctx, picks, candidates) + card = PokemonCardDef( guid="8fe4abce-cc29-5fc3-a8fa-74321eb2742e", @@ -23,7 +49,7 @@ card = PokemonCardDef( title="Spreading Flames", game_text="Attach up to 3 Fire Energy cards from your discard pile to your Pok\u00e9mon in any way you like.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=spreading_flames, ), Attack( title="Max Victory", @@ -31,7 +57,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 1}, damage=100, damage_operator="+", - effect=unimplemented, + effect=bonus_if(defender_is_v, 120), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/VictiniV_144.py b/spirit/game/scripts/cards/SWSH5/VictiniV_144.py index af591ea..f9ca29e 100644 --- a/spirit/game/scripts/cards/SWSH5/VictiniV_144.py +++ b/spirit/game/scripts/cards/SWSH5/VictiniV_144.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import ( + bonus_if, defender_is_v, self_energy_discard_attack, +) card = PokemonCardDef( guid="4e35c2d0-1962-57ac-9718-e227df1280cd", @@ -24,14 +27,14 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 1}, damage=10, damage_operator="+", - effect=unimplemented, + effect=bonus_if(defender_is_v, 50), ), Attack( title="Flare Shot", game_text="Discard all Energy from this Pok\u00e9mon.", cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + effect=self_energy_discard_attack(all_energy=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/VictiniV_21.py b/spirit/game/scripts/cards/SWSH5/VictiniV_21.py index 76fc323..3346d1f 100644 --- a/spirit/game/scripts/cards/SWSH5/VictiniV_21.py +++ b/spirit/game/scripts/cards/SWSH5/VictiniV_21.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import ( + bonus_if, defender_is_v, self_energy_discard_attack, +) card = PokemonCardDef( guid="e368e757-543c-5505-bffe-d017bd259890", @@ -24,14 +27,14 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 1}, damage=10, damage_operator="+", - effect=unimplemented, + effect=bonus_if(defender_is_v, 50), ), Attack( title="Flare Shot", game_text="Discard all Energy from this Pok\u00e9mon.", cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + effect=self_energy_discard_attack(all_energy=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH5/Victreebel_3.py b/spirit/game/scripts/cards/SWSH5/Victreebel_3.py index 75fb823..944f1ce 100644 --- a/spirit/game/scripts/cards/SWSH5/Victreebel_3.py +++ b/spirit/game/scripts/cards/SWSH5/Victreebel_3.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="6d0cca12-e843-53a7-84ba-b4c66aa0ba18", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Confused. During your opponent's next turn, that Pok\u00e9mon can't retreat.", cost={PokemonTypes.GRASS: 1}, damage=40, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED, no_retreat=True), ), Attack( title="Solar Beam", diff --git a/spirit/game/scripts/cards/SWSH5/Vivillon_13.py b/spirit/game/scripts/cards/SWSH5/Vivillon_13.py index 0235aa8..ba9342d 100644 --- a/spirit/game/scripts/cards/SWSH5/Vivillon_13.py +++ b/spirit/game/scripts/cards/SWSH5/Vivillon_13.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities + + +async def vital_powder(ctx): + """Heal all damage from 2 of your Benched Pokémon.""" + bench = ctx.my_bench() + if not bench: + return + picks = await ctx.choose_cards( + bench, 2, prompt="Choose 2 Benched Pokémon to heal" + ) + for pokemon in picks: + heal_amount = ctx.max_hp(pokemon) - pokemon.get_attribute(AttrID.HP, 0) + if heal_amount > 0: + await ctx.heal(heal_amount, pokemon) card = PokemonCardDef( guid="059e1647-e553-54bc-9375-392956f1a637", @@ -23,7 +37,7 @@ card = PokemonCardDef( title="Vital Powder", game_text="Heal all damage from 2 of your Benched Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=vital_powder, ), Attack( title="Gust", diff --git a/spirit/game/scripts/cards/SWSH5/Weepinbell_2.py b/spirit/game/scripts/cards/SWSH5/Weepinbell_2.py index 42406e9..6de7a46 100644 --- a/spirit/game/scripts/cards/SWSH5/Weepinbell_2.py +++ b/spirit/game/scripts/cards/SWSH5/Weepinbell_2.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions + + +async def dangerous_mucus(ctx): + """On evolve: you may make opponent's Active Burned and Poisoned.""" + if await ctx.ask_yes_no("Make your opponent's Active Pokémon Burned and Poisoned?"): + await ctx.apply_special_condition(ctx.defender, SpecialConditions.BURNED) + await ctx.apply_special_condition(ctx.defender, SpecialConditions.POISONED) + card = PokemonCardDef( guid="799ddb96-0b03-5a3d-a7e5-6c7cbe9b5905", @@ -22,7 +30,8 @@ card = PokemonCardDef( Ability( title="Dangerous Mucus", game_text="When you play this Pok\u00e9mon from your hand to evolve 1 of your Pok\u00e9mon during your turn, you may make your opponent's Active Pok\u00e9mon Burned and Poisoned.", - effect=unimplemented, + trigger=Triggers.ON_EVOLVE, + effect=dangerous_mucus, ), Attack( title="Vine Whip", diff --git a/spirit/game/scripts/cards/SWSH5/Yamper_52.py b/spirit/game/scripts/cards/SWSH5/Yamper_52.py index 07e529c..134c9a7 100644 --- a/spirit/game/scripts/cards/SWSH5/Yamper_52.py +++ b/spirit/game/scripts/cards/SWSH5/Yamper_52.py @@ -1,6 +1,40 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers, def_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +def _named(card, name): + d = def_for(card.archetype_id) + return bool(d) and d.display_name == name + + +async def ball_search(ctx): + """On play: may put a Poké Ball, a Great Ball, or 1 of each from your + discard pile into your hand.""" + discard = ctx.discard_pile() + poke_balls = [c for c in discard if _named(c, "Poké Ball")] + great_balls = [c for c in discard if _named(c, "Great Ball")] + if not poke_balls and not great_balls: + return + options = [] + if poke_balls: + options.append(("Poké Ball", poke_balls[:1])) + if great_balls: + options.append(("Great Ball", great_balls[:1])) + if poke_balls and great_balls: + options.append(("1 of each", poke_balls[:1] + great_balls[:1])) + if not await ctx.ask_yes_no( + "Put a Poké Ball, a Great Ball, or 1 of each from your discard pile into your hand?" + ): + return + if len(options) == 1: + idx = 0 + else: + idx = await ctx.choose( + "Choose which to put into your hand.", [label for label, _ in options] + ) + await ctx.put_in_hand(options[idx][1], reveal=False) + + card = PokemonCardDef( guid="bc46f911-8081-5ffe-a712-08b5fcc2d194", key="SWSH5", @@ -21,7 +55,8 @@ card = PokemonCardDef( Ability( title="Ball Search", game_text="When you play this Pok\u00e9mon from your hand onto your Bench during your turn, you may put a Pok\u00e9 Ball card, a Great Ball card, or 1 of each from your discard pile into your hand.", - effect=unimplemented, + trigger=Triggers.ON_PLAY, + effect=ball_search, ), Attack( title="Flop", diff --git a/spirit/game/scripts/cards/SWSH5/Zubat_89.py b/spirit/game/scripts/cards/SWSH5/Zubat_89.py index f3a2eb5..647a58c 100644 --- a/spirit/game/scripts/cards/SWSH5/Zubat_89.py +++ b/spirit/game/scripts/cards/SWSH5/Zubat_89.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import switch_self_attack card = PokemonCardDef( guid="c2a70948-e5af-5440-b9f9-1e58211fe2e2", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Hide in Shadows", game_text="Switch this Pok\u00e9mon with 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=switch_self_attack(), ), Attack( title="Speed Dive", diff --git a/spirit/game/scripts/cards/SWSH6/Abomasnow_10.py b/spirit/game/scripts/cards/SWSH6/Abomasnow_10.py index 8879783..5ab87f5 100644 --- a/spirit/game/scripts/cards/SWSH6/Abomasnow_10.py +++ b/spirit/game/scripts/cards/SWSH6/Abomasnow_10.py @@ -1,5 +1,23 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, def_for, subtypes_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.passives import Passive + + +class ToughnessBoostPassive(Passive): + """Your Single Strike Pokemon (except any Abomasnow) get +50 HP.""" + + stacking_key = "ToughnessBoost" + + def max_hp_bonus(self, pokemon, carrier): + if pokemon.owning_player_id != carrier.owning_player_id: + return 0 + pdef = def_for(pokemon.archetype_id) + if pdef is not None and pdef.name.endswith("pokemon.Abomasnow.Name"): + return 0 + if "Single Strike" not in subtypes_for(pokemon.archetype_id): + return 0 + return 50 + card = PokemonCardDef( guid="8ac70bbc-422e-568d-90f0-d3d358fb423f", @@ -22,7 +40,7 @@ card = PokemonCardDef( Ability( title="Toughness Boost", game_text="Your Single Strike Pok\u00e9mon in play, except any Abomasnow, get +50 HP. You can't apply more than 1 Toughness Boost Ability at a time.", - effect=unimplemented, + passive=ToughnessBoostPassive(), ), Attack( title="Mega Punch", diff --git a/spirit/game/scripts/cards/SWSH6/Agatha_129.py b/spirit/game/scripts/cards/SWSH6/Agatha_129.py index ef6b8c1..303241b 100644 --- a/spirit/game/scripts/cards/SWSH6/Agatha_129.py +++ b/spirit/game/scripts/cards/SWSH6/Agatha_129.py @@ -1,6 +1,16 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities + +async def agatha(ctx): + """Move up to 3 damage counters from your Active to your opponent's Active.""" + active = ctx.my_active() + opponent_active = ctx.opponent_active() + if active is None or opponent_active is None: + return + await ctx.move_damage_counters(active, opponent_active, max_count=3) + + card = SupporterCardDef( guid="06ec9a55-f51d-5802-8716-476c3505f2e8", key="SWSH6", @@ -11,5 +21,5 @@ card = SupporterCardDef( collector_number=129, set_code="SWSH6", rarity=Rarities.Uncommon, - effect=unimplemented + effect=agatha ) diff --git a/spirit/game/scripts/cards/SWSH6/Agatha_186.py b/spirit/game/scripts/cards/SWSH6/Agatha_186.py index 4a5fd3e..175c8d4 100644 --- a/spirit/game/scripts/cards/SWSH6/Agatha_186.py +++ b/spirit/game/scripts/cards/SWSH6/Agatha_186.py @@ -1,6 +1,16 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities + +async def agatha(ctx): + """Move up to 3 damage counters from your Active to your opponent's Active.""" + active = ctx.my_active() + opponent_active = ctx.opponent_active() + if active is None or opponent_active is None: + return + await ctx.move_damage_counters(active, opponent_active, max_count=3) + + card = SupporterCardDef( guid="fb2eb864-2803-5933-967d-299f057f565f", key="SWSH6", @@ -11,5 +21,5 @@ card = SupporterCardDef( collector_number=186, set_code="SWSH6", rarity=Rarities.RareUltra, - effect=unimplemented + effect=agatha ) diff --git a/spirit/game/scripts/cards/SWSH6/Agatha_210.py b/spirit/game/scripts/cards/SWSH6/Agatha_210.py index ffdc003..feb1ead 100644 --- a/spirit/game/scripts/cards/SWSH6/Agatha_210.py +++ b/spirit/game/scripts/cards/SWSH6/Agatha_210.py @@ -1,6 +1,16 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities + +async def agatha(ctx): + """Move up to 3 damage counters from your Active to your opponent's Active.""" + active = ctx.my_active() + opponent_active = ctx.opponent_active() + if active is None or opponent_active is None: + return + await ctx.move_damage_counters(active, opponent_active, max_count=3) + + card = SupporterCardDef( guid="970774b9-32fa-5f60-bff7-ffc32bdac0af", key="SWSH6", @@ -11,5 +21,5 @@ card = SupporterCardDef( collector_number=210, set_code="SWSH6", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=agatha ) diff --git a/spirit/game/scripts/cards/SWSH6/Aggron_111.py b/spirit/game/scripts/cards/SWSH6/Aggron_111.py index f353be8..dc3b7a0 100644 --- a/spirit/game/scripts/cards/SWSH6/Aggron_111.py +++ b/spirit/game/scripts/cards/SWSH6/Aggron_111.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import protect_next_turn +from spirit.game.card_effects.attacks_common import spread_damage card = PokemonCardDef( guid="8deb433a-229d-57b7-84f7-44b899dafcc9", @@ -25,14 +27,14 @@ card = PokemonCardDef( game_text="During your opponent's next turn, this Pok\u00e9mon takes 30 less damage from attacks (after applying Weakness and Resistance).", cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 2}, damage=100, - effect=unimplemented, + effect=protect_next_turn(reduce=30), ), Attack( title="Seismic Rift", game_text="This attack also does 30 damage to each of your Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.METAL: 2, PokemonTypes.COLORLESS: 2}, damage=240, - effect=unimplemented, + effect=spread_damage(30, side="mine", include_active=True, also_base=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Ampharos_49.py b/spirit/game/scripts/cards/SWSH6/Ampharos_49.py index 138aadf..7419743 100644 --- a/spirit/game/scripts/cards/SWSH6/Ampharos_49.py +++ b/spirit/game/scripts/cards/SWSH6/Ampharos_49.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.pokemon import is_energy_card + + +async def searchlight_tail(ctx): + """90 damage, +90 more if the opponent's revealed hand has an Energy card.""" + cards = await ctx.reveal_hand(ctx.opponent_id, ctx.player_id) + amount = 90 + if any(is_energy_card(c) for c in cards): + amount += 90 + await ctx.deal_damage(amount) + card = PokemonCardDef( guid="857c9944-4f37-5316-a64f-e92e9019d867", @@ -24,7 +36,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.LIGHTNING: 1}, damage=50, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), Attack( title="Searchlight Tail", @@ -32,7 +44,7 @@ card = PokemonCardDef( cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 1}, damage=90, damage_operator="+", - effect=unimplemented, + effect=searchlight_tail, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Avery_130.py b/spirit/game/scripts/cards/SWSH6/Avery_130.py index 3012d28..22696b6 100644 --- a/spirit/game/scripts/cards/SWSH6/Avery_130.py +++ b/spirit/game/scripts/cards/SWSH6/Avery_130.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.session.effects import full_stack + + +async def avery(ctx): + drawn = await ctx.draw_cards(3) + if not drawn: + return + while len(ctx.opponent_bench()) > 3: + bench = ctx.opponent_bench() + target = await ctx.choose_pokemon( + bench, "Choose a Benched Pokémon to discard", player_id=ctx.opponent_id + ) + if target is None: + break + await ctx.discard_cards(full_stack(target)) + card = SupporterCardDef( guid="b29d5965-a073-5469-814a-9b2ed08002e7", @@ -11,5 +27,5 @@ card = SupporterCardDef( collector_number=130, set_code="SWSH6", rarity=Rarities.Uncommon, - effect=unimplemented + effect=avery, ) diff --git a/spirit/game/scripts/cards/SWSH6/Avery_187.py b/spirit/game/scripts/cards/SWSH6/Avery_187.py index 648e319..e74ebd4 100644 --- a/spirit/game/scripts/cards/SWSH6/Avery_187.py +++ b/spirit/game/scripts/cards/SWSH6/Avery_187.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.session.effects import full_stack + + +async def avery(ctx): + drawn = await ctx.draw_cards(3) + if not drawn: + return + while len(ctx.opponent_bench()) > 3: + bench = ctx.opponent_bench() + target = await ctx.choose_pokemon( + bench, "Choose a Benched Pokémon to discard", player_id=ctx.opponent_id + ) + if target is None: + break + await ctx.discard_cards(full_stack(target)) + card = SupporterCardDef( guid="7a3c6665-662f-5552-b466-42f7200e19e0", @@ -11,5 +27,5 @@ card = SupporterCardDef( collector_number=187, set_code="SWSH6", rarity=Rarities.RareUltra, - effect=unimplemented + effect=avery, ) diff --git a/spirit/game/scripts/cards/SWSH6/Avery_211.py b/spirit/game/scripts/cards/SWSH6/Avery_211.py index 82385bd..2daff86 100644 --- a/spirit/game/scripts/cards/SWSH6/Avery_211.py +++ b/spirit/game/scripts/cards/SWSH6/Avery_211.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.session.effects import full_stack + + +async def avery(ctx): + drawn = await ctx.draw_cards(3) + if not drawn: + return + while len(ctx.opponent_bench()) > 3: + bench = ctx.opponent_bench() + target = await ctx.choose_pokemon( + bench, "Choose a Benched Pokémon to discard", player_id=ctx.opponent_id + ) + if target is None: + break + await ctx.discard_cards(full_stack(target)) + card = SupporterCardDef( guid="f5c2122f-e807-5e14-857f-bdbb7f24364d", @@ -11,5 +27,5 @@ card = SupporterCardDef( collector_number=211, set_code="SWSH6", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=avery, ) diff --git a/spirit/game/scripts/cards/SWSH6/Banette_63.py b/spirit/game/scripts/cards/SWSH6/Banette_63.py index 71f1446..1213eaa 100644 --- a/spirit/game/scripts/cards/SWSH6/Banette_63.py +++ b/spirit/game/scripts/cards/SWSH6/Banette_63.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.data_utils import PokemonCardDef, Attack +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions + + +async def resolute_spite(ctx): + """Put up to 7 damage counters on this Pokemon; 20 damage per counter placed.""" + n = await ctx.choose( + "Put up to 7 damage counters on this Pokémon.", + [str(i) for i in range(8)], + ) + if n > 0: + await ctx.deal_damage(n * 10, target=ctx.attacker, as_counters=True) + await ctx.deal_damage(20 * n) + card = PokemonCardDef( guid="d2706007-e561-56fb-86e2-0ddc9c9b81dc", @@ -22,18 +35,18 @@ card = PokemonCardDef( abilities=[ Attack( title="Resolute Spite", - game_text="Put up to 7 damage counters on this Pok\u00e9mon. This attack does 20 damage for each damage counter you placed in this way.", + game_text="Put up to 7 damage counters on this Pokémon. This attack does 20 damage for each damage counter you placed in this way.", cost={PokemonTypes.PSYCHIC: 1}, damage=20, damage_operator="x", - effect=unimplemented, + effect=resolute_spite, ), Attack( title="Eerie Light", - game_text="Your opponent's Active Pok\u00e9mon is now Confused.", + game_text="Your opponent's Active Pokémon is now Confused.", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=50, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH6/Beedrill_3.py b/spirit/game/scripts/cards/SWSH6/Beedrill_3.py index c2d7cd0..cfdfc8d 100644 --- a/spirit/game/scripts/cards/SWSH6/Beedrill_3.py +++ b/spirit/game/scripts/cards/SWSH6/Beedrill_3.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import self_energy_discard_attack +from spirit.game.session.effects import is_special_energy + + +async def persist_sting(ctx): + """If the opponent's Active has any Special Energy attached, it is Knocked Out.""" + defender = ctx.defender + if defender is not None and any( + is_special_energy(e) for e in ctx.attached_energies(defender) + ): + await ctx.knock_out(defender) + card = PokemonCardDef( guid="3e741793-cbf8-50c9-9947-4d303a67c51d", @@ -21,16 +33,16 @@ card = PokemonCardDef( abilities=[ Attack( title="Persist Sting", - game_text="If your opponent's Active Pok\u00e9mon has any Special Energy attached, it is Knocked Out.", + game_text="If your opponent's Active Pokémon has any Special Energy attached, it is Knocked Out.", cost={PokemonTypes.GRASS: 1}, - effect=unimplemented, + effect=persist_sting, ), Attack( title="Jet Spear", - game_text="Discard an Energy from this Pok\u00e9mon.", + game_text="Discard an Energy from this Pokémon.", cost={PokemonTypes.GRASS: 1}, damage=110, - effect=unimplemented, + effect=self_energy_discard_attack(count=1), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH6/BlazikenVMAX_200.py b/spirit/game/scripts/cards/SWSH6/BlazikenVMAX_200.py index f7a26de..27b16ad 100644 --- a/spirit/game/scripts/cards/SWSH6/BlazikenVMAX_200.py +++ b/spirit/game/scripts/cards/SWSH6/BlazikenVMAX_200.py @@ -1,5 +1,34 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, subtypes_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.trainers import is_energy_card + + +async def max_blaze(ctx): + """130 damage. Choose up to 2 Benched Rapid Strike Pokémon and attach a + discard-pile Energy card to each of them.""" + await ctx.deal_damage() + bench = [p for p in ctx.my_bench() if "Rapid Strike" in subtypes_for(p.archetype_id)] + if not bench: + return + targets = await ctx.choose_cards( + bench, min(2, len(bench)), minimum=0, + prompt="Choose up to 2 of your Benched Rapid Strike Pokémon", + ) + for target in targets: + energies = [c for c in ctx.discard_pile() if is_energy_card(c)] + if not energies: + break + picks = await ctx.choose_cards( + energies, 1, minimum=1, + prompt="Choose an Energy card to attach", + ) + if not picks: + continue + if target.entity_id not in ctx.visual_targets: + ctx.visual_targets.append(target.entity_id) + await ctx.attach_energy(picks[0], target) + card = PokemonCardDef( guid="d448e396-b71f-536f-8fbe-eb2b6646635e", @@ -21,17 +50,17 @@ card = PokemonCardDef( abilities=[ Attack( title="Clutch", - game_text="During your opponent's next turn, the Defending Pok\u00e9mon can't retreat.", + game_text="During your opponent's next turn, the Defending Pokémon can't retreat.", cost={PokemonTypes.FIRE: 1}, damage=60, - effect=unimplemented, + effect=condition_attack(no_retreat=True), ), Attack( title="Max Blaze", - game_text="Choose up to 2 of your Benched Rapid Strike Pok\u00e9mon and attach an Energy card from your discard pile to each of them.", + game_text="Choose up to 2 of your Benched Rapid Strike Pokémon and attach an Energy card from your discard pile to each of them.", cost={PokemonTypes.COLORLESS: 2}, damage=130, - effect=unimplemented, + effect=max_blaze, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH6/BlazikenVMAX_201.py b/spirit/game/scripts/cards/SWSH6/BlazikenVMAX_201.py index 7777edc..a7ec58c 100644 --- a/spirit/game/scripts/cards/SWSH6/BlazikenVMAX_201.py +++ b/spirit/game/scripts/cards/SWSH6/BlazikenVMAX_201.py @@ -1,5 +1,34 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, subtypes_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.trainers import is_energy_card + + +async def max_blaze(ctx): + """130 damage. Choose up to 2 Benched Rapid Strike Pokémon and attach a + discard-pile Energy card to each of them.""" + await ctx.deal_damage() + bench = [p for p in ctx.my_bench() if "Rapid Strike" in subtypes_for(p.archetype_id)] + if not bench: + return + targets = await ctx.choose_cards( + bench, min(2, len(bench)), minimum=0, + prompt="Choose up to 2 of your Benched Rapid Strike Pokémon", + ) + for target in targets: + energies = [c for c in ctx.discard_pile() if is_energy_card(c)] + if not energies: + break + picks = await ctx.choose_cards( + energies, 1, minimum=1, + prompt="Choose an Energy card to attach", + ) + if not picks: + continue + if target.entity_id not in ctx.visual_targets: + ctx.visual_targets.append(target.entity_id) + await ctx.attach_energy(picks[0], target) + card = PokemonCardDef( guid="a75f4e68-8a43-59d1-a037-dc931adaae4a", @@ -21,17 +50,17 @@ card = PokemonCardDef( abilities=[ Attack( title="Clutch", - game_text="During your opponent's next turn, the Defending Pok\u00e9mon can't retreat.", + game_text="During your opponent's next turn, the Defending Pokémon can't retreat.", cost={PokemonTypes.FIRE: 1}, damage=60, - effect=unimplemented, + effect=condition_attack(no_retreat=True), ), Attack( title="Max Blaze", - game_text="Choose up to 2 of your Benched Rapid Strike Pok\u00e9mon and attach an Energy card from your discard pile to each of them.", + game_text="Choose up to 2 of your Benched Rapid Strike Pokémon and attach an Energy card from your discard pile to each of them.", cost={PokemonTypes.COLORLESS: 2}, damage=130, - effect=unimplemented, + effect=max_blaze, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH6/BlazikenVMAX_21.py b/spirit/game/scripts/cards/SWSH6/BlazikenVMAX_21.py index 7788180..541b037 100644 --- a/spirit/game/scripts/cards/SWSH6/BlazikenVMAX_21.py +++ b/spirit/game/scripts/cards/SWSH6/BlazikenVMAX_21.py @@ -1,5 +1,34 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, subtypes_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.trainers import is_energy_card + + +async def max_blaze(ctx): + """130 damage. Choose up to 2 Benched Rapid Strike Pokémon and attach a + discard-pile Energy card to each of them.""" + await ctx.deal_damage() + bench = [p for p in ctx.my_bench() if "Rapid Strike" in subtypes_for(p.archetype_id)] + if not bench: + return + targets = await ctx.choose_cards( + bench, min(2, len(bench)), minimum=0, + prompt="Choose up to 2 of your Benched Rapid Strike Pokémon", + ) + for target in targets: + energies = [c for c in ctx.discard_pile() if is_energy_card(c)] + if not energies: + break + picks = await ctx.choose_cards( + energies, 1, minimum=1, + prompt="Choose an Energy card to attach", + ) + if not picks: + continue + if target.entity_id not in ctx.visual_targets: + ctx.visual_targets.append(target.entity_id) + await ctx.attach_energy(picks[0], target) + card = PokemonCardDef( guid="25ed89e9-39a3-5bab-83e4-16f101a043c1", @@ -21,17 +50,17 @@ card = PokemonCardDef( abilities=[ Attack( title="Clutch", - game_text="During your opponent's next turn, the Defending Pok\u00e9mon can't retreat.", + game_text="During your opponent's next turn, the Defending Pokémon can't retreat.", cost={PokemonTypes.FIRE: 1}, damage=60, - effect=unimplemented, + effect=condition_attack(no_retreat=True), ), Attack( title="Max Blaze", - game_text="Choose up to 2 of your Benched Rapid Strike Pok\u00e9mon and attach an Energy card from your discard pile to each of them.", + game_text="Choose up to 2 of your Benched Rapid Strike Pokémon and attach an Energy card from your discard pile to each of them.", cost={PokemonTypes.COLORLESS: 2}, damage=130, - effect=unimplemented, + effect=max_blaze, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH6/BlazikenV_161.py b/spirit/game/scripts/cards/SWSH6/BlazikenV_161.py index 17795df..8db45b2 100644 --- a/spirit/game/scripts/cards/SWSH6/BlazikenV_161.py +++ b/spirit/game/scripts/cards/SWSH6/BlazikenV_161.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import self_energy_discard_attack card = PokemonCardDef( guid="ffeca5b0-67fa-5d31-9252-542983134b8b", @@ -25,10 +26,10 @@ card = PokemonCardDef( ), Attack( title="Fire Spin", - game_text="Discard 2 Energy from this Pok\u00e9mon.", + game_text="Discard 2 Energy from this Pokémon.", cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 2}, damage=210, - effect=unimplemented, + effect=self_energy_discard_attack(count=2), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH6/BlazikenV_20.py b/spirit/game/scripts/cards/SWSH6/BlazikenV_20.py index 6f125d5..389665a 100644 --- a/spirit/game/scripts/cards/SWSH6/BlazikenV_20.py +++ b/spirit/game/scripts/cards/SWSH6/BlazikenV_20.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import self_energy_discard_attack card = PokemonCardDef( guid="54adf96b-050c-5de6-a9f9-0e3331b46549", @@ -25,10 +26,10 @@ card = PokemonCardDef( ), Attack( title="Fire Spin", - game_text="Discard 2 Energy from this Pok\u00e9mon.", + game_text="Discard 2 Energy from this Pokémon.", cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 2}, damage=210, - effect=unimplemented, + effect=self_energy_discard_attack(count=2), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH6/BlisseyV_119.py b/spirit/game/scripts/cards/SWSH6/BlisseyV_119.py index d77cb4c..20f96ac 100644 --- a/spirit/game/scripts/cards/SWSH6/BlisseyV_119.py +++ b/spirit/game/scripts/cards/SWSH6/BlisseyV_119.py @@ -1,5 +1,31 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import count_energy +from spirit.game.card_effects.pokemon import is_energy_card + + +async def natural_cure(ctx): + """Whenever you attach an Energy card from your hand to this Pokémon, remove all Special Conditions from it.""" + if ctx.attaching_player_id != ctx.player_id or ctx.energy_receiver is not ctx.source: + return + await ctx.cure_all_conditions(ctx.source) + + +async def blissful_blast(ctx): + """10, plus 30 more for each Energy attached to this Pokémon. If you did any damage, you may attach up to 3 Energy from your discard pile to this Pokémon.""" + amount = 10 + 30 * count_energy("self")(ctx) + dealt = await ctx.deal_damage(amount) + if dealt <= 0: + return + if not await ctx.ask_yes_no("Attach up to 3 Energy cards from your discard pile to this Pokémon?"): + return + energies = [c for c in ctx.discard_pile() if is_energy_card(c)] + picks = await ctx.choose_cards( + energies, 3, minimum=1, + prompt="Choose up to 3 Energy cards to attach to this Pokémon.") + for card in picks: + await ctx.attach_energy(card, ctx.attacker) + card = PokemonCardDef( guid="5f5a84cd-0975-5d4f-80d3-74e35ea05a66", @@ -21,7 +47,8 @@ card = PokemonCardDef( Ability( title="Natural Cure", game_text="Whenever you attach an Energy card from your hand to this Pok\u00e9mon, remove all Special Conditions from it.", - effect=unimplemented, + trigger=Triggers.ON_ENERGY_ATTACHED, + effect=natural_cure, ), Attack( title="Blissful Blast", @@ -29,7 +56,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 1}, damage=10, damage_operator="+", - effect=unimplemented, + effect=blissful_blast, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/BlisseyV_182.py b/spirit/game/scripts/cards/SWSH6/BlisseyV_182.py index a77ad86..920bedd 100644 --- a/spirit/game/scripts/cards/SWSH6/BlisseyV_182.py +++ b/spirit/game/scripts/cards/SWSH6/BlisseyV_182.py @@ -1,5 +1,31 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import count_energy +from spirit.game.card_effects.pokemon import is_energy_card + + +async def natural_cure(ctx): + """Whenever you attach an Energy card from your hand to this Pokémon, remove all Special Conditions from it.""" + if ctx.attaching_player_id != ctx.player_id or ctx.energy_receiver is not ctx.source: + return + await ctx.cure_all_conditions(ctx.source) + + +async def blissful_blast(ctx): + """10, plus 30 more for each Energy attached to this Pokémon. If you did any damage, you may attach up to 3 Energy from your discard pile to this Pokémon.""" + amount = 10 + 30 * count_energy("self")(ctx) + dealt = await ctx.deal_damage(amount) + if dealt <= 0: + return + if not await ctx.ask_yes_no("Attach up to 3 Energy cards from your discard pile to this Pokémon?"): + return + energies = [c for c in ctx.discard_pile() if is_energy_card(c)] + picks = await ctx.choose_cards( + energies, 3, minimum=1, + prompt="Choose up to 3 Energy cards to attach to this Pokémon.") + for card in picks: + await ctx.attach_energy(card, ctx.attacker) + card = PokemonCardDef( guid="58ed01c6-6cb0-540e-b62b-d7dfc9bd25d5", @@ -21,7 +47,8 @@ card = PokemonCardDef( Ability( title="Natural Cure", game_text="Whenever you attach an Energy card from your hand to this Pok\u00e9mon, remove all Special Conditions from it.", - effect=unimplemented, + trigger=Triggers.ON_ENERGY_ATTACHED, + effect=natural_cure, ), Attack( title="Blissful Blast", @@ -29,7 +56,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 1}, damage=10, damage_operator="+", - effect=unimplemented, + effect=blissful_blast, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/BlisseyV_183.py b/spirit/game/scripts/cards/SWSH6/BlisseyV_183.py index aa43597..f1ef71a 100644 --- a/spirit/game/scripts/cards/SWSH6/BlisseyV_183.py +++ b/spirit/game/scripts/cards/SWSH6/BlisseyV_183.py @@ -1,5 +1,31 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import count_energy +from spirit.game.card_effects.pokemon import is_energy_card + + +async def natural_cure(ctx): + """Whenever you attach an Energy card from your hand to this Pokémon, remove all Special Conditions from it.""" + if ctx.attaching_player_id != ctx.player_id or ctx.energy_receiver is not ctx.source: + return + await ctx.cure_all_conditions(ctx.source) + + +async def blissful_blast(ctx): + """10, plus 30 more for each Energy attached to this Pokémon. If you did any damage, you may attach up to 3 Energy from your discard pile to this Pokémon.""" + amount = 10 + 30 * count_energy("self")(ctx) + dealt = await ctx.deal_damage(amount) + if dealt <= 0: + return + if not await ctx.ask_yes_no("Attach up to 3 Energy cards from your discard pile to this Pokémon?"): + return + energies = [c for c in ctx.discard_pile() if is_energy_card(c)] + picks = await ctx.choose_cards( + energies, 3, minimum=1, + prompt="Choose up to 3 Energy cards to attach to this Pokémon.") + for card in picks: + await ctx.attach_energy(card, ctx.attacker) + card = PokemonCardDef( guid="431bd38a-b900-57f7-b9a5-bbb26630e66d", @@ -21,7 +47,8 @@ card = PokemonCardDef( Ability( title="Natural Cure", game_text="Whenever you attach an Energy card from your hand to this Pok\u00e9mon, remove all Special Conditions from it.", - effect=unimplemented, + trigger=Triggers.ON_ENERGY_ATTACHED, + effect=natural_cure, ), Attack( title="Blissful Blast", @@ -29,7 +56,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 1}, damage=10, damage_operator="+", - effect=unimplemented, + effect=blissful_blast, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Blitzle_50.py b/spirit/game/scripts/cards/SWSH6/Blitzle_50.py index 6dd5b61..66ed226 100644 --- a/spirit/game/scripts/cards/SWSH6/Blitzle_50.py +++ b/spirit/game/scripts/cards/SWSH6/Blitzle_50.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import snipe_attack card = PokemonCardDef( guid="15f3dbb5-667c-5dd5-8ab9-b84ee49d7dae", @@ -20,9 +21,9 @@ card = PokemonCardDef( abilities=[ Attack( title="Thunder Spear", - game_text="This attack does 10 damage to 1 of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", + game_text="This attack does 10 damage to 1 of your opponent's Pokémon. (Don't apply Weakness and Resistance for Benched Pokémon.)", cost={PokemonTypes.LIGHTNING: 1}, - effect=unimplemented, + effect=snipe_attack(10, pool="any", count=1), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH6/Brawly_131.py b/spirit/game/scripts/cards/SWSH6/Brawly_131.py index f7b40ae..68204b7 100644 --- a/spirit/game/scripts/cards/SWSH6/Brawly_131.py +++ b/spirit/game/scripts/cards/SWSH6/Brawly_131.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef, subtypes_for from spirit.game.attributes import Rarities +from spirit.game.session.effects import is_basic_pokemon +from spirit.game.card_effects.support_common import search_to_bench + + +def _is_basic_rapid_strike(card): + return is_basic_pokemon(card) and "Rapid Strike" in subtypes_for(card.archetype_id) + card = SupporterCardDef( guid="b8b1ec19-fc2b-57c6-ad29-8be8590d7c49", @@ -11,5 +18,5 @@ card = SupporterCardDef( collector_number=131, set_code="SWSH6", rarity=Rarities.Uncommon, - effect=unimplemented + effect=search_to_bench(_is_basic_rapid_strike, count=3), ) diff --git a/spirit/game/scripts/cards/SWSH6/Brawly_188.py b/spirit/game/scripts/cards/SWSH6/Brawly_188.py index 08b7c5d..1fdfc74 100644 --- a/spirit/game/scripts/cards/SWSH6/Brawly_188.py +++ b/spirit/game/scripts/cards/SWSH6/Brawly_188.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef, subtypes_for from spirit.game.attributes import Rarities +from spirit.game.session.effects import is_basic_pokemon +from spirit.game.card_effects.support_common import search_to_bench + + +def _is_basic_rapid_strike(card): + return is_basic_pokemon(card) and "Rapid Strike" in subtypes_for(card.archetype_id) + card = SupporterCardDef( guid="6d30053d-22c1-55a9-bb85-f766a6804537", @@ -11,5 +18,5 @@ card = SupporterCardDef( collector_number=188, set_code="SWSH6", rarity=Rarities.RareUltra, - effect=unimplemented + effect=search_to_bench(_is_basic_rapid_strike, count=3), ) diff --git a/spirit/game/scripts/cards/SWSH6/Brawly_212.py b/spirit/game/scripts/cards/SWSH6/Brawly_212.py index d1e5217..0b6dfe8 100644 --- a/spirit/game/scripts/cards/SWSH6/Brawly_212.py +++ b/spirit/game/scripts/cards/SWSH6/Brawly_212.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef, subtypes_for from spirit.game.attributes import Rarities +from spirit.game.session.effects import is_basic_pokemon +from spirit.game.card_effects.support_common import search_to_bench + + +def _is_basic_rapid_strike(card): + return is_basic_pokemon(card) and "Rapid Strike" in subtypes_for(card.archetype_id) + card = SupporterCardDef( guid="f63f409b-895c-5959-b17e-95d32b3fbb04", @@ -11,5 +18,5 @@ card = SupporterCardDef( collector_number=212, set_code="SWSH6", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=search_to_bench(_is_basic_rapid_strike, count=3), ) diff --git a/spirit/game/scripts/cards/SWSH6/Bronzong_223.py b/spirit/game/scripts/cards/SWSH6/Bronzong_223.py index 61fba73..2209fd6 100644 --- a/spirit/game/scripts/cards/SWSH6/Bronzong_223.py +++ b/spirit/game/scripts/cards/SWSH6/Bronzong_223.py @@ -1,5 +1,40 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import energy_provides_type + + +def _metal_transfer_condition(board, player_id, pokemon): + pkmn = board.pokemon_in_play(player_id) + if len(pkmn) < 2: + return False + return any( + energy_provides_type(e, PokemonTypes.METAL) + for p in pkmn for e in board.attached_energies(p) + ) + + +async def metal_transfer(ctx): + pool = [ + (e, p) for p in ctx.my_pokemon_in_play() + for e in ctx.attached_energies(p) + if energy_provides_type(e, PokemonTypes.METAL) + ] + if not pool: + return + picked = await ctx.choose_cards( + [e for e, _ in pool], 1, prompt="Choose a Metal Energy to move" + ) + if not picked: + return + energy = picked[0] + holder = next(p for e, p in pool if e is energy) + targets = [p for p in ctx.my_pokemon_in_play() if p is not holder] + if not targets: + return + target = await ctx.choose_pokemon(targets, "Choose a Pokémon to move the Energy to") + if target is not None: + await ctx.move_energy(energy, target) + card = PokemonCardDef( guid="df6b4ac1-746d-514f-b8c5-02172778513b", @@ -23,7 +58,9 @@ card = PokemonCardDef( Ability( title="Metal Transfer", game_text="As often as you like during your turn, you may move a Metal Energy from 1 of your Pok\u00e9mon to another of your Pok\u00e9mon.", - effect=unimplemented, + activation=Activations.UNLIMITED, + condition=_metal_transfer_condition, + effect=metal_transfer, ), Attack( title="Zen Headbutt", diff --git a/spirit/game/scripts/cards/SWSH6/Caitlin_132.py b/spirit/game/scripts/cards/SWSH6/Caitlin_132.py index 2f6f481..e7d16f7 100644 --- a/spirit/game/scripts/cards/SWSH6/Caitlin_132.py +++ b/spirit/game/scripts/cards/SWSH6/Caitlin_132.py @@ -1,6 +1,24 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities + +async def caitlin(ctx): + """Put any number of hand cards on the bottom of the deck in any order, + then draw that many cards.""" + hand = ctx.hand() + if not hand: + return + picks = await ctx.choose_cards( + hand, len(hand), minimum=0, ordered=True, + prompt="Choose any number of cards to put on the bottom of your deck, in order.", + ) + if not picks: + return + for card in picks: + await ctx.put_on_bottom_of_deck(card) + await ctx.draw_cards(len(picks)) + + card = SupporterCardDef( guid="4a09afb2-c434-55ac-a2d7-4bd366e0ded5", key="SWSH6", @@ -11,5 +29,5 @@ card = SupporterCardDef( collector_number=132, set_code="SWSH6", rarity=Rarities.Uncommon, - effect=unimplemented + effect=caitlin ) diff --git a/spirit/game/scripts/cards/SWSH6/Caitlin_189.py b/spirit/game/scripts/cards/SWSH6/Caitlin_189.py index 4b02f6f..366d7af 100644 --- a/spirit/game/scripts/cards/SWSH6/Caitlin_189.py +++ b/spirit/game/scripts/cards/SWSH6/Caitlin_189.py @@ -1,6 +1,24 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities + +async def caitlin(ctx): + """Put any number of hand cards on the bottom of the deck in any order, + then draw that many cards.""" + hand = ctx.hand() + if not hand: + return + picks = await ctx.choose_cards( + hand, len(hand), minimum=0, ordered=True, + prompt="Choose any number of cards to put on the bottom of your deck, in order.", + ) + if not picks: + return + for card in picks: + await ctx.put_on_bottom_of_deck(card) + await ctx.draw_cards(len(picks)) + + card = SupporterCardDef( guid="ba755403-7be0-5457-8923-c58fc662dddf", key="SWSH6", @@ -11,5 +29,5 @@ card = SupporterCardDef( collector_number=189, set_code="SWSH6", rarity=Rarities.RareUltra, - effect=unimplemented + effect=caitlin ) diff --git a/spirit/game/scripts/cards/SWSH6/Caitlin_213.py b/spirit/game/scripts/cards/SWSH6/Caitlin_213.py index 78bb6d3..bd7b981 100644 --- a/spirit/game/scripts/cards/SWSH6/Caitlin_213.py +++ b/spirit/game/scripts/cards/SWSH6/Caitlin_213.py @@ -1,6 +1,24 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities + +async def caitlin(ctx): + """Put any number of hand cards on the bottom of the deck in any order, + then draw that many cards.""" + hand = ctx.hand() + if not hand: + return + picks = await ctx.choose_cards( + hand, len(hand), minimum=0, ordered=True, + prompt="Choose any number of cards to put on the bottom of your deck, in order.", + ) + if not picks: + return + for card in picks: + await ctx.put_on_bottom_of_deck(card) + await ctx.draw_cards(len(picks)) + + card = SupporterCardDef( guid="24cf1f0f-cd1b-5d16-b9af-3a9760de0801", key="SWSH6", @@ -11,5 +29,5 @@ card = SupporterCardDef( collector_number=213, set_code="SWSH6", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=caitlin ) diff --git a/spirit/game/scripts/cards/SWSH6/CastformRainyForm_33.py b/spirit/game/scripts/cards/SWSH6/CastformRainyForm_33.py index 4d34e59..bae0b72 100644 --- a/spirit/game/scripts/cards/SWSH6/CastformRainyForm_33.py +++ b/spirit/game/scripts/cards/SWSH6/CastformRainyForm_33.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID, TrainerType +from spirit.game.session.passives import Passive, carrier_pokemon +from spirit.game.card_effects.attacks_common import damage_all_opponents + + +class WeatherReadingPassive(Passive): + def modify_attack_cost(self, cost, pokemon, carrier, board): + if carrier_pokemon(carrier) is not pokemon: + return cost + discard = board.find_player_area(carrier.owning_player_id, "discard") + stadiums = sum( + 1 for c in (discard.children if discard else []) + if c.get_attribute(AttrID.TRAINER_TYPE) == TrainerType.STADIUM.value + ) + if stadiums >= 8: + return {} + return cost + card = PokemonCardDef( guid="cf23d7f0-f5cf-5233-ad80-8aba42de925e", @@ -20,14 +37,14 @@ card = PokemonCardDef( abilities=[ Ability( title="Weather Reading", - game_text="If you have 8 or more Stadium cards in your discard pile, ignore all Energy in this Pok\u00e9mon's attack costs.", - effect=unimplemented, + game_text="If you have 8 or more Stadium cards in your discard pile, ignore all Energy in this Pokémon's attack costs.", + passive=WeatherReadingPassive(), ), Attack( title="Rainfall", - game_text="This attack does 20 damage to each of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", + game_text="This attack does 20 damage to each of your opponent's Pokémon. (Don't apply Weakness and Resistance for Benched Pokémon.)", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=damage_all_opponents(20), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH6/CastformSnowyForm_34.py b/spirit/game/scripts/cards/SWSH6/CastformSnowyForm_34.py index 91d6cac..6de3eae 100644 --- a/spirit/game/scripts/cards/SWSH6/CastformSnowyForm_34.py +++ b/spirit/game/scripts/cards/SWSH6/CastformSnowyForm_34.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID, TrainerType +from spirit.game.session.passives import Passive, carrier_pokemon + + +class WeatherReadingPassive(Passive): + def modify_attack_cost(self, cost, pokemon, carrier, board): + if carrier_pokemon(carrier) is not pokemon: + return cost + discard = board.find_player_area(carrier.owning_player_id, "discard") + stadiums = sum( + 1 for c in (discard.children if discard else []) + if c.get_attribute(AttrID.TRAINER_TYPE) == TrainerType.STADIUM.value + ) + if stadiums >= 8: + return {} + return cost + card = PokemonCardDef( guid="99baf2e4-0d9d-5c59-99f3-70cd909468a0", @@ -20,15 +36,15 @@ card = PokemonCardDef( abilities=[ Ability( title="Weather Reading", - game_text="If you have 8 or more Stadium cards in your discard pile, ignore all Energy in this Pok\u00e9mon's attack costs.", - effect=unimplemented, + game_text="If you have 8 or more Stadium cards in your discard pile, ignore all Energy in this Pokémon's attack costs.", + passive=WeatherReadingPassive(), ), Attack( title="Frosty Typhoon", - game_text="During your next turn, this Pok\u00e9mon can't use Frosty Typhoon.", + game_text="During your next turn, this Pokémon can't use Frosty Typhoon.", cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + locks_next_turn=True, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH6/CastformSunnyForm_22.py b/spirit/game/scripts/cards/SWSH6/CastformSunnyForm_22.py index 94080dd..435df7d 100644 --- a/spirit/game/scripts/cards/SWSH6/CastformSunnyForm_22.py +++ b/spirit/game/scripts/cards/SWSH6/CastformSunnyForm_22.py @@ -1,5 +1,29 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID, TrainerType +from spirit.game.session.passives import Passive, carrier_pokemon + + +class WeatherReadingPassive(Passive): + def modify_attack_cost(self, cost, pokemon, carrier, board): + if carrier_pokemon(carrier) is not pokemon: + return cost + discard = board.find_player_area(carrier.owning_player_id, "discard") + stadiums = sum( + 1 for c in (discard.children if discard else []) + if c.get_attribute(AttrID.TRAINER_TYPE) == TrainerType.STADIUM.value + ) + if stadiums >= 8: + return {} + return cost + + +async def high_pressure_blast(ctx): + """Discard a Stadium in play. If you can't, this attack does nothing.""" + discarded = await ctx.discard_stadium() + if discarded is None: + return + await ctx.deal_damage() + card = PokemonCardDef( guid="bba38fdc-6da1-54d9-9aa0-68e8f324be68", @@ -21,14 +45,14 @@ card = PokemonCardDef( Ability( title="Weather Reading", game_text="If you have 8 or more Stadium cards in your discard pile, ignore all Energy in this Pok\u00e9mon's attack costs.", - effect=unimplemented, + passive=WeatherReadingPassive(), ), Attack( title="High-Pressure Blast", game_text="Discard a Stadium in play. If you can't, this attack does nothing.", cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=150, - effect=unimplemented, + effect=high_pressure_blast, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Castform_121.py b/spirit/game/scripts/cards/SWSH6/Castform_121.py index 7b3dda8..d325ba0 100644 --- a/spirit/game/scripts/cards/SWSH6/Castform_121.py +++ b/spirit/game/scripts/cards/SWSH6/Castform_121.py @@ -1,5 +1,28 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID, TrainerType +from spirit.game.session.passives import Passive +from spirit.game.session.effects import is_trainer_card + + +class WeatherReadingPassive(Passive): + def modify_attack_cost(self, cost, pokemon, carrier, board): + if pokemon is not carrier: + return cost + discard = board.find_player_area(carrier.owning_player_id, "discard") + count = sum( + 1 for c in (discard.children if discard else []) + if is_trainer_card(c) + and c.get_attribute(AttrID.TRAINER_TYPE) == TrainerType.STADIUM.value + ) + if count >= 8: + return {} + return cost + + +async def _weather_force(ctx): + await ctx.deal_damage() + await ctx.draw_until(6) + card = PokemonCardDef( guid="cb70d3b6-aa5d-581f-96c5-5b1c7fff0eb9", @@ -21,14 +44,14 @@ card = PokemonCardDef( Ability( title="Weather Reading", game_text="If you have 8 or more Stadium cards in your discard pile, ignore all Energy in this Pok\u00e9mon's attack costs.", - effect=unimplemented, + passive=WeatherReadingPassive(), ), Attack( title="Weather Force", game_text="Draw cards until you have 6 cards in your hand.", cost={PokemonTypes.COLORLESS: 3}, damage=80, - effect=unimplemented, + effect=_weather_force, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/CelebiVMAX_199.py b/spirit/game/scripts/cards/SWSH6/CelebiVMAX_199.py index 364283e..6946a7f 100644 --- a/spirit/game/scripts/cards/SWSH6/CelebiVMAX_199.py +++ b/spirit/game/scripts/cards/SWSH6/CelebiVMAX_199.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.session.effects import is_pokemon_card +from spirit.game.card_effects.support_common import search_to_hand + + +async def _curative_forest(ctx): + if not await ctx.ask_yes_no("Heal 20 damage from each of your Grass Pokémon?"): + return + for pokemon in ctx.my_pokemon_in_play(): + types = pokemon.get_attribute(AttrID.POKEMON_TYPES) or [] + if PokemonTypes.GRASS.value in types: + await ctx.heal(20, target=pokemon) + card = PokemonCardDef( guid="773f9e15-7825-5315-8363-1b98fd893992", @@ -22,14 +34,18 @@ card = PokemonCardDef( Ability( title="Curative Forest", game_text="Once during your turn, you may heal 20 damage from each of your Grass Pok\u00e9mon.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + effect=_curative_forest, ), Attack( title="Max Plant", game_text="Search your deck for up to 2 Pok\u00e9mon, reveal them, and put them into your hand. Then, shuffle your deck.", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=130, - effect=unimplemented, + effect=search_to_hand( + is_pokemon_card, count=2, + prompt="Choose up to 2 Pok\u00e9mon to put into your hand.", + ), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/CelebiVMAX_8.py b/spirit/game/scripts/cards/SWSH6/CelebiVMAX_8.py index 2d9a69f..4600089 100644 --- a/spirit/game/scripts/cards/SWSH6/CelebiVMAX_8.py +++ b/spirit/game/scripts/cards/SWSH6/CelebiVMAX_8.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.session.effects import is_pokemon_card +from spirit.game.card_effects.support_common import search_to_hand + + +async def _curative_forest(ctx): + if not await ctx.ask_yes_no("Heal 20 damage from each of your Grass Pokémon?"): + return + for pokemon in ctx.my_pokemon_in_play(): + types = pokemon.get_attribute(AttrID.POKEMON_TYPES) or [] + if PokemonTypes.GRASS.value in types: + await ctx.heal(20, target=pokemon) + card = PokemonCardDef( guid="deeda815-d569-599f-809f-c0ed2b07c786", @@ -22,14 +34,18 @@ card = PokemonCardDef( Ability( title="Curative Forest", game_text="Once during your turn, you may heal 20 damage from each of your Grass Pok\u00e9mon.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + effect=_curative_forest, ), Attack( title="Max Plant", game_text="Search your deck for up to 2 Pok\u00e9mon, reveal them, and put them into your hand. Then, shuffle your deck.", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=130, - effect=unimplemented, + effect=search_to_hand( + is_pokemon_card, count=2, + prompt="Choose up to 2 Pok\u00e9mon to put into your hand.", + ), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/CelebiV_160.py b/spirit/game/scripts/cards/SWSH6/CelebiV_160.py index 49fd482..c976ea2 100644 --- a/spirit/game/scripts/cards/SWSH6/CelebiV_160.py +++ b/spirit/game/scripts/cards/SWSH6/CelebiV_160.py @@ -1,6 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.support_common import distribute_energy, switch_self_attack +from spirit.game.card_effects.trainers import is_grass_energy_card +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def leaflet_dance(ctx): + """Attach any number of Grass Energy cards from your hand, any way you like.""" + grass = [c for c in ctx.hand() if is_grass_energy_card(c)] + if not grass: + return + picks = await ctx.choose_cards( + grass, len(grass), minimum=0, + prompt="Choose any number of Grass Energy cards to attach to your Pokémon.", + ) + if picks: + await distribute_energy(ctx, picks, ctx.my_pokemon_in_play()) + card = PokemonCardDef( guid="09f7a2ab-996d-5982-9f9c-328358e14cdb", key="SWSH6", @@ -22,14 +37,14 @@ card = PokemonCardDef( title="Leaflet Dance", game_text="Attach any number of Grass Energy cards from your hand to your Pok\u00e9mon in any way you like.", cost={PokemonTypes.GRASS: 1}, - effect=unimplemented, + effect=leaflet_dance, ), Attack( title="Slash Back", game_text="Switch this Pok\u00e9mon with 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=60, - effect=unimplemented, + effect=switch_self_attack(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/CelebiV_7.py b/spirit/game/scripts/cards/SWSH6/CelebiV_7.py index 146cbc3..16275fc 100644 --- a/spirit/game/scripts/cards/SWSH6/CelebiV_7.py +++ b/spirit/game/scripts/cards/SWSH6/CelebiV_7.py @@ -1,6 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.support_common import distribute_energy, switch_self_attack +from spirit.game.card_effects.trainers import is_grass_energy_card +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def leaflet_dance(ctx): + """Attach any number of Grass Energy cards from your hand, any way you like.""" + grass = [c for c in ctx.hand() if is_grass_energy_card(c)] + if not grass: + return + picks = await ctx.choose_cards( + grass, len(grass), minimum=0, + prompt="Choose any number of Grass Energy cards to attach to your Pokémon.", + ) + if picks: + await distribute_energy(ctx, picks, ctx.my_pokemon_in_play()) + card = PokemonCardDef( guid="141e1f6b-4c05-57b6-92df-5e0361c92ac9", key="SWSH6", @@ -22,14 +37,14 @@ card = PokemonCardDef( title="Leaflet Dance", game_text="Attach any number of Grass Energy cards from your hand to your Pok\u00e9mon in any way you like.", cost={PokemonTypes.GRASS: 1}, - effect=unimplemented, + effect=leaflet_dance, ), Attack( title="Slash Back", game_text="Switch this Pok\u00e9mon with 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=60, - effect=unimplemented, + effect=switch_self_attack(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Cinderace_28.py b/spirit/game/scripts/cards/SWSH6/Cinderace_28.py index 6cf7e98..1f8bf7b 100644 --- a/spirit/game/scripts/cards/SWSH6/Cinderace_28.py +++ b/spirit/game/scripts/cards/SWSH6/Cinderace_28.py @@ -1,5 +1,29 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.attacks_common import lock_all_attacks +from spirit.game.session.passives import Passive + + +class CrisisPowerPassive(Passive): + def modify_damage_dealt(self, calc, carrier): + if calc.attacker is not carrier: + return + if not (calc.is_attack and calc.is_opposing and calc.to_active): + return + opponent = next( + (p for p in calc.board.player_ids if p != carrier.owning_player_id), None + ) + if opponent is None: + return + bonus = 30 * calc.board.prizes_taken(opponent) + if bonus: + calc.amount += bonus + + +async def fireball_shot(ctx): + await ctx.deal_damage() + lock_all_attacks(ctx, ctx.attacker) + card = PokemonCardDef( guid="96ee16d4-f31f-5ef0-a193-a69d8f7cab4b", @@ -22,14 +46,14 @@ card = PokemonCardDef( Ability( title="Crisis Power", game_text="This Pok\u00e9mon's attacks do 30 more damage to your opponent's Active Pok\u00e9mon for each Prize card your opponent has taken (before applying Weakness and Resistance).", - effect=unimplemented, + passive=CrisisPowerPassive(), ), Attack( title="Fireball Shot", game_text="During your next turn, this Pok\u00e9mon can't attack.", cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 1}, damage=150, - effect=unimplemented, + effect=fireball_shot, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Cobalion_114.py b/spirit/game/scripts/cards/SWSH6/Cobalion_114.py index 4620320..15acf42 100644 --- a/spirit/game/scripts/cards/SWSH6/Cobalion_114.py +++ b/spirit/game/scripts/cards/SWSH6/Cobalion_114.py @@ -1,4 +1,4 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -29,7 +29,7 @@ card = PokemonCardDef( game_text="During your next turn, this Pok\u00e9mon can't attack.", cost={PokemonTypes.METAL: 2, PokemonTypes.COLORLESS: 1}, damage=130, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Crabominable_85.py b/spirit/game/scripts/cards/SWSH6/Crabominable_85.py index 9c05c03..a7fbe7c 100644 --- a/spirit/game/scripts/cards/SWSH6/Crabominable_85.py +++ b/spirit/game/scripts/cards/SWSH6/Crabominable_85.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import flip_damage +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -25,7 +26,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 2}, damage=90, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=2, per_heads=90), ), Attack( title="Crabhammer", diff --git a/spirit/game/scripts/cards/SWSH6/Crabrawler_84.py b/spirit/game/scripts/cards/SWSH6/Crabrawler_84.py index 4f73fe7..8ca3038 100644 --- a/spirit/game/scripts/cards/SWSH6/Crabrawler_84.py +++ b/spirit/game/scripts/cards/SWSH6/Crabrawler_84.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import flip_damage +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -29,7 +30,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 1}, damage=40, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=2, per_heads=40), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Cresselia_64.py b/spirit/game/scripts/cards/SWSH6/Cresselia_64.py index 720aad5..21d8a59 100644 --- a/spirit/game/scripts/cards/SWSH6/Cresselia_64.py +++ b/spirit/game/scripts/cards/SWSH6/Cresselia_64.py @@ -1,6 +1,30 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import bonus_if, count_energy +from spirit.game.card_effects.pokemon import energy_provides_type +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def crescent_glow(ctx): + """Search a Psychic Energy card and attach it to 1 of your Pokémon (up + to 3 if you go second and it's your first turn). Then, shuffle.""" + going_second = ctx.player_id != ctx.session.first_player_id + count = 3 if going_second and ctx.session.turn_state.turn_number == 2 else 1 + picks = await ctx.search_deck( + lambda c: energy_provides_type(c, PokemonTypes.PSYCHIC.value), + count=count, minimum=0, + prompt=f"Choose up to {count} Psychic Energy card(s) to attach.", + ) + if picks: + candidates = ctx.my_pokemon_in_play() + if candidates: + target = await ctx.choose_pokemon( + candidates, "Choose a Pokémon to attach the Energy to") + if target is not None: + for card in picks: + await ctx.attach_energy(card, target) + await ctx.shuffle_deck() + + card = PokemonCardDef( guid="a1cd0ba3-b309-555b-b5e3-8985a6a772a2", key="SWSH6", @@ -23,7 +47,7 @@ card = PokemonCardDef( title="Crescent Glow", game_text="Search your deck for a Psychic Energy card and attach it to 1 of your Pok\u00e9mon. If you go second and it's your first turn, instead search for up to 3 Psychic Energy cards and attach them to 1 of your Pok\u00e9mon. Then, shuffle your deck.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=crescent_glow, ), Attack( title="Photon Laser", @@ -31,7 +55,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 2}, damage=30, damage_operator="+", - effect=unimplemented, + effect=bonus_if(lambda ctx: count_energy("mine")(ctx) >= 5, 90), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/CrushingGloves_133.py b/spirit/game/scripts/cards/SWSH6/CrushingGloves_133.py index e280f49..fd343ed 100644 --- a/spirit/game/scripts/cards/SWSH6/CrushingGloves_133.py +++ b/spirit/game/scripts/cards/SWSH6/CrushingGloves_133.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import PokemonToolCardDef +from spirit.game.attributes import Rarities, PokemonTypes +from spirit.game.card_effects.passives_common import typed_damage_boost_tool card = PokemonToolCardDef( guid="0e54da48-550c-5772-a434-30181fbbd2ab", @@ -11,5 +12,5 @@ card = PokemonToolCardDef( collector_number=133, set_code="SWSH6", rarity=Rarities.Uncommon, - effect=unimplemented + passive=typed_damage_boost_tool(PokemonTypes.METAL, 30) ) diff --git a/spirit/game/scripts/cards/SWSH6/Deerling_11.py b/spirit/game/scripts/cards/SWSH6/Deerling_11.py index 1010c34..dd2e42c 100644 --- a/spirit/game/scripts/cards/SWSH6/Deerling_11.py +++ b/spirit/game/scripts/cards/SWSH6/Deerling_11.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import debuff_defender_attacks card = PokemonCardDef( guid="64e26c9c-88de-530f-b405-e62b361ee833", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Growl", game_text="During your opponent's next turn, the Defending Pok\u00e9mon's attacks do 20 less damage (before applying Weakness and Resistance).", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=debuff_defender_attacks(20), ), Attack( title="Rear Kick", diff --git a/spirit/game/scripts/cards/SWSH6/Delibird_32.py b/spirit/game/scripts/cards/SWSH6/Delibird_32.py index 418bab9..70cc934 100644 --- a/spirit/game/scripts/cards/SWSH6/Delibird_32.py +++ b/spirit/game/scripts/cards/SWSH6/Delibird_32.py @@ -1,5 +1,28 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import full_stack + + +async def package_delivery(ctx): + """Put this Pokemon and all attached cards into the deck. If you do, + search your deck for a card and put it into your hand. Shuffle.""" + await ctx.shuffle_into_deck(full_stack(ctx.source), ctx.player_id) + + async def _promote(): + if not await ctx.session._promote_new_active(ctx.player_id): + screen_name = ctx.session.players[ctx.player_id].screen_name + await ctx.session.end_game( + ctx.opponent_id, f"{screen_name} has no Pokémon left" + ) + + ctx.deferred_actions.append(_promote) + picks = await ctx.search_deck( + None, count=1, minimum=0, + prompt="Choose a card to put into your hand.", + ) + await ctx.put_in_hand(picks, reveal=False) + await ctx.shuffle_deck() + card = PokemonCardDef( guid="6557db9b-47b6-5c5d-93d6-bfe7e1e65662", @@ -27,7 +50,7 @@ card = PokemonCardDef( title="Package Delivery", game_text="Put this Pok\u00e9mon and all attached cards into your deck. If you do, search your deck for a card and put it into your hand. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + effect=package_delivery, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Diglett_76.py b/spirit/game/scripts/cards/SWSH6/Diglett_76.py index 17655e7..3343a7e 100644 --- a/spirit/game/scripts/cards/SWSH6/Diglett_76.py +++ b/spirit/game/scripts/cards/SWSH6/Diglett_76.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import flip_protection card = PokemonCardDef( guid="436fc543-c940-5727-8235-db412a96b366", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, during your opponent's next turn, prevent all damage from and effects of attacks done to this Pok\u00e9mon.", cost={PokemonTypes.FIGHTING: 1}, damage=10, - effect=unimplemented, + effect=flip_protection(prevent=True, effects_too=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Doctor_134.py b/spirit/game/scripts/cards/SWSH6/Doctor_134.py index 2195b84..8599cde 100644 --- a/spirit/game/scripts/cards/SWSH6/Doctor_134.py +++ b/spirit/game/scripts/cards/SWSH6/Doctor_134.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.pokemon import is_pokemon_vmax + + +async def doctor(ctx): + """Draw 2 cards. If your opponent's Active Pokemon is a Pokemon VMAX, draw 2 more cards.""" + await ctx.draw_cards(2) + active = ctx.opponent_active() + if active is not None and is_pokemon_vmax(active.archetype_id): + await ctx.draw_cards(2) + card = SupporterCardDef( guid="5a8208f7-08ca-51ce-93c6-c21148e2bb8a", @@ -11,5 +21,5 @@ card = SupporterCardDef( collector_number=134, set_code="SWSH6", rarity=Rarities.Uncommon, - effect=unimplemented + effect=doctor, ) diff --git a/spirit/game/scripts/cards/SWSH6/Doctor_190.py b/spirit/game/scripts/cards/SWSH6/Doctor_190.py index b4d1d0d..12bf1b8 100644 --- a/spirit/game/scripts/cards/SWSH6/Doctor_190.py +++ b/spirit/game/scripts/cards/SWSH6/Doctor_190.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.pokemon import is_pokemon_vmax + + +async def doctor(ctx): + """Draw 2 cards. If your opponent's Active Pokemon is a Pokemon VMAX, draw 2 more cards.""" + await ctx.draw_cards(2) + active = ctx.opponent_active() + if active is not None and is_pokemon_vmax(active.archetype_id): + await ctx.draw_cards(2) + card = SupporterCardDef( guid="38af8d35-b09d-5f39-bb2b-2162636f717d", @@ -11,5 +21,5 @@ card = SupporterCardDef( collector_number=190, set_code="SWSH6", rarity=Rarities.RareUltra, - effect=unimplemented + effect=doctor, ) diff --git a/spirit/game/scripts/cards/SWSH6/Doctor_214.py b/spirit/game/scripts/cards/SWSH6/Doctor_214.py index 4038810..e0683fd 100644 --- a/spirit/game/scripts/cards/SWSH6/Doctor_214.py +++ b/spirit/game/scripts/cards/SWSH6/Doctor_214.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.pokemon import is_pokemon_vmax + + +async def doctor(ctx): + """Draw 2 cards. If your opponent's Active Pokemon is a Pokemon VMAX, draw 2 more cards.""" + await ctx.draw_cards(2) + active = ctx.opponent_active() + if active is not None and is_pokemon_vmax(active.archetype_id): + await ctx.draw_cards(2) + card = SupporterCardDef( guid="ac7deda3-133e-513e-8529-a4d3d394b9c5", @@ -11,5 +21,5 @@ card = SupporterCardDef( collector_number=214, set_code="SWSH6", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=doctor, ) diff --git a/spirit/game/scripts/cards/SWSH6/Drizzile_42.py b/spirit/game/scripts/cards/SWSH6/Drizzile_42.py index aa982f6..5076e26 100644 --- a/spirit/game/scripts/cards/SWSH6/Drizzile_42.py +++ b/spirit/game/scripts/cards/SWSH6/Drizzile_42.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.support_common import switch_self_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Switch this Pok\u00e9mon with 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 2}, damage=40, - effect=unimplemented, + effect=switch_self_attack(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Dugtrio_77.py b/spirit/game/scripts/cards/SWSH6/Dugtrio_77.py index e5dee1e..9afde40 100644 --- a/spirit/game/scripts/cards/SWSH6/Dugtrio_77.py +++ b/spirit/game/scripts/cards/SWSH6/Dugtrio_77.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import apply_protection + + +async def triple_heads(ctx): + """Flip 3 coins, 60 damage per heads; all heads also shields the user next turn.""" + results = await ctx.flip_coins(3, "Triple Heads") + heads = sum(1 for r in results if r) + if heads: + await ctx.deal_damage(60 * heads) + if heads == 3: + await apply_protection(ctx, prevent=True, effects_too=True) card = PokemonCardDef( guid="7070e3c5-17cc-51d0-9a79-f938fafb8e1e", @@ -25,7 +36,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 1}, damage=60, damage_operator="x", - effect=unimplemented, + effect=triple_heads, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/DynaTreeHill_135.py b/spirit/game/scripts/cards/SWSH6/DynaTreeHill_135.py index f223f19..f4b5a38 100644 --- a/spirit/game/scripts/cards/SWSH6/DynaTreeHill_135.py +++ b/spirit/game/scripts/cards/SWSH6/DynaTreeHill_135.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import StadiumCardDef, unimplemented +from spirit.game.data_utils import StadiumCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.passives_common import healing_block_passive card = StadiumCardDef( guid="af0a7546-b634-5b1c-a755-85676a0a267c", @@ -11,5 +12,5 @@ card = StadiumCardDef( collector_number=135, set_code="SWSH6", rarity=Rarities.Uncommon, - effect=unimplemented + passive=healing_block_passive(lambda target, carrier: True), ) diff --git a/spirit/game/scripts/cards/SWSH6/EchoingHorn_136.py b/spirit/game/scripts/cards/SWSH6/EchoingHorn_136.py index 2885e20..8d387f1 100644 --- a/spirit/game/scripts/cards/SWSH6/EchoingHorn_136.py +++ b/spirit/game/scripts/cards/SWSH6/EchoingHorn_136.py @@ -1,5 +1,34 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef from spirit.game.attributes import Rarities +from spirit.game.session.effects import is_basic_pokemon +from spirit.game.session.passives import effective_bench_capacity + + +def _opponent_id(board, player_id): + return next((p for p in board.player_ids if p != player_id), None) + + +def echoing_horn_condition(board, player_id): + opponent = _opponent_id(board, player_id) + if not opponent: + return False + discard = board.find_player_area(opponent, "discard") + if not discard or not any(is_basic_pokemon(c) for c in discard.children): + return False + bench = board.find_player_area(opponent, "bench") + return bench is not None and len(bench.children) < effective_bench_capacity(board, opponent) + + +async def echoing_horn(ctx): + """Put a Basic Pokemon from your opponent's discard pile onto their Bench.""" + candidates = [c for c in ctx.discard_pile(ctx.opponent_id) if is_basic_pokemon(c)] + picks = await ctx.choose_cards( + candidates, 1, minimum=1, + prompt="Choose a Basic Pokémon from your opponent's discard pile to put onto their Bench.", + ) + for card in picks: + await ctx.bench_pokemon(card) + card = ItemCardDef( guid="fbff3416-7ec6-55c2-ab57-5e32c844dcbe", @@ -11,5 +40,6 @@ card = ItemCardDef( collector_number=136, set_code="SWSH6", rarity=Rarities.Uncommon, - effect=unimplemented + effect=echoing_horn, + condition=echoing_horn_condition, ) diff --git a/spirit/game/scripts/cards/SWSH6/EchoingHorn_225.py b/spirit/game/scripts/cards/SWSH6/EchoingHorn_225.py index 5fa7d60..e86838d 100644 --- a/spirit/game/scripts/cards/SWSH6/EchoingHorn_225.py +++ b/spirit/game/scripts/cards/SWSH6/EchoingHorn_225.py @@ -1,5 +1,34 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef from spirit.game.attributes import Rarities +from spirit.game.session.effects import is_basic_pokemon +from spirit.game.session.passives import effective_bench_capacity + + +def _opponent_id(board, player_id): + return next((p for p in board.player_ids if p != player_id), None) + + +def echoing_horn_condition(board, player_id): + opponent = _opponent_id(board, player_id) + if not opponent: + return False + discard = board.find_player_area(opponent, "discard") + if not discard or not any(is_basic_pokemon(c) for c in discard.children): + return False + bench = board.find_player_area(opponent, "bench") + return bench is not None and len(bench.children) < effective_bench_capacity(board, opponent) + + +async def echoing_horn(ctx): + """Put a Basic Pokemon from your opponent's discard pile onto their Bench.""" + candidates = [c for c in ctx.discard_pile(ctx.opponent_id) if is_basic_pokemon(c)] + picks = await ctx.choose_cards( + candidates, 1, minimum=1, + prompt="Choose a Basic Pokémon from your opponent's discard pile to put onto their Bench.", + ) + for card in picks: + await ctx.bench_pokemon(card) + card = ItemCardDef( guid="0f436eba-266c-5569-9f0e-7bbf3e369626", @@ -11,5 +40,6 @@ card = ItemCardDef( collector_number=225, set_code="SWSH6", rarity=Rarities.RareSecret, - effect=unimplemented + effect=echoing_horn, + condition=echoing_horn_condition, ) diff --git a/spirit/game/scripts/cards/SWSH6/Electrode_222.py b/spirit/game/scripts/cards/SWSH6/Electrode_222.py index 72d8943..f0ce2d1 100644 --- a/spirit/game/scripts/cards/SWSH6/Electrode_222.py +++ b/spirit/game/scripts/cards/SWSH6/Electrode_222.py @@ -1,5 +1,35 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import is_lightning_energy +from spirit.game.card_effects.support_common import distribute_energy + + +def _is_lightning_pokemon(p): + return PokemonTypes.LIGHTNING.value in (p.get_attribute(AttrID.POKEMON_TYPES) or []) + + +def _on_bench(board, player_id, pokemon): + return pokemon is not board.active_pokemon(player_id) + + +async def buzzap_generator(ctx): + """Once per turn, if benched: you may search up to 2 Lightning Energy + cards and attach them freely, then shuffle. If you did, this Pokémon is + Knocked Out.""" + if not await ctx.ask_yes_no( + "Search your deck for up to 2 Lightning Energy cards to attach? " + "This Pokémon will be Knocked Out." + ): + return + picks = await ctx.search_deck( + is_lightning_energy, count=2, minimum=0, + prompt="Choose up to 2 Lightning Energy cards to attach.", + ) + candidates = [p for p in ctx.my_pokemon_in_play() if _is_lightning_pokemon(p)] + if picks and candidates: + await distribute_energy(ctx, picks, candidates) + await ctx.shuffle_deck() + await ctx.knock_out(ctx.source) card = PokemonCardDef( guid="d2cf459d-4809-5903-92bb-3048461a3422", @@ -22,7 +52,9 @@ card = PokemonCardDef( Ability( title="Buzzap Generator", game_text="Once during your turn, if this Pok\u00e9mon is on your Bench, you may search your deck for up to 2 Lightning Energy cards and attach them to your Lightning Pok\u00e9mon in any way you like. Then, shuffle your deck. If you searched your deck in this way, this Pok\u00e9mon is Knocked Out.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=_on_bench, + effect=buzzap_generator, ), Attack( title="Electric Ball", diff --git a/spirit/game/scripts/cards/SWSH6/ExpeditionUniform_137.py b/spirit/game/scripts/cards/SWSH6/ExpeditionUniform_137.py index 6c42924..ee2db9d 100644 --- a/spirit/game/scripts/cards/SWSH6/ExpeditionUniform_137.py +++ b/spirit/game/scripts/cards/SWSH6/ExpeditionUniform_137.py @@ -1,6 +1,21 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef from spirit.game.attributes import Rarities + +async def expedition_uniform(ctx): + """Look at the bottom 3 cards of your deck and put them on top of your + deck in any order.""" + bottom = ctx.deck()[:3] + if not bottom: + return + picks = await ctx.choose_cards( + bottom, len(bottom), minimum=len(bottom), ordered=True, + prompt="Put these cards on top of your deck, in order", + ) + for card in picks: + await ctx.put_on_top_of_deck(card) + + card = ItemCardDef( guid="bbe8c187-08f1-533a-9ec5-e1306bc2067b", key="SWSH6", @@ -11,5 +26,5 @@ card = ItemCardDef( collector_number=137, set_code="SWSH6", rarity=Rarities.Uncommon, - effect=unimplemented + effect=expedition_uniform, ) diff --git a/spirit/game/scripts/cards/SWSH6/FireResistantGloves_138.py b/spirit/game/scripts/cards/SWSH6/FireResistantGloves_138.py index 00eb547..89c1147 100644 --- a/spirit/game/scripts/cards/SWSH6/FireResistantGloves_138.py +++ b/spirit/game/scripts/cards/SWSH6/FireResistantGloves_138.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import PokemonToolCardDef +from spirit.game.attributes import Rarities, PokemonTypes +from spirit.game.card_effects.passives_common import typed_damage_boost_tool card = PokemonToolCardDef( guid="6f87ca9b-9918-5450-875e-c3a58747d324", @@ -11,5 +12,5 @@ card = PokemonToolCardDef( collector_number=138, set_code="SWSH6", rarity=Rarities.Uncommon, - effect=unimplemented + passive=typed_damage_boost_tool(PokemonTypes.FIRE, 30), ) diff --git a/spirit/game/scripts/cards/SWSH6/Flannery_139.py b/spirit/game/scripts/cards/SWSH6/Flannery_139.py index 716b5f8..e3aa0b4 100644 --- a/spirit/game/scripts/cards/SWSH6/Flannery_139.py +++ b/spirit/game/scripts/cards/SWSH6/Flannery_139.py @@ -1,5 +1,40 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import is_special_energy + + +def _other_player(board, player_id): + return next((p for p in board.player_ids if p != player_id), None) + + +def _opponent_special_energies(board, player_id): + opponent = _other_player(board, player_id) + if not opponent: + return [] + out = [] + for pokemon in board.pokemon_in_play(opponent): + out.extend(c for c in pokemon.children if is_special_energy(c)) + return out + + +def _flannery_condition(board, player_id): + if _opponent_special_energies(board, player_id): + return True + stadium_area = board.find_global_area("activeStadium") + return bool(stadium_area and stadium_area.children) + + +async def flannery(ctx): + """Discard a Special Energy from 1 of your opponent's Pokemon, and + discard a Stadium in play.""" + targets = _opponent_special_energies(ctx.board, ctx.player_id) + if targets: + picks = await ctx.choose_cards( + targets, 1, minimum=1, prompt="Choose a Special Energy to discard" + ) + await ctx.discard_cards(picks) + await ctx.discard_stadium() + card = SupporterCardDef( guid="aea34f9a-0064-5d11-9613-e1bd7455c2e1", @@ -11,5 +46,6 @@ card = SupporterCardDef( collector_number=139, set_code="SWSH6", rarity=Rarities.Uncommon, - effect=unimplemented + effect=flannery, + condition=_flannery_condition, ) diff --git a/spirit/game/scripts/cards/SWSH6/Flannery_191.py b/spirit/game/scripts/cards/SWSH6/Flannery_191.py index 563dc1a..cc66584 100644 --- a/spirit/game/scripts/cards/SWSH6/Flannery_191.py +++ b/spirit/game/scripts/cards/SWSH6/Flannery_191.py @@ -1,5 +1,40 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import is_special_energy + + +def _other_player(board, player_id): + return next((p for p in board.player_ids if p != player_id), None) + + +def _opponent_special_energies(board, player_id): + opponent = _other_player(board, player_id) + if not opponent: + return [] + out = [] + for pokemon in board.pokemon_in_play(opponent): + out.extend(c for c in pokemon.children if is_special_energy(c)) + return out + + +def _flannery_condition(board, player_id): + if _opponent_special_energies(board, player_id): + return True + stadium_area = board.find_global_area("activeStadium") + return bool(stadium_area and stadium_area.children) + + +async def flannery(ctx): + """Discard a Special Energy from 1 of your opponent's Pokemon, and + discard a Stadium in play.""" + targets = _opponent_special_energies(ctx.board, ctx.player_id) + if targets: + picks = await ctx.choose_cards( + targets, 1, minimum=1, prompt="Choose a Special Energy to discard" + ) + await ctx.discard_cards(picks) + await ctx.discard_stadium() + card = SupporterCardDef( guid="80093bf3-0adb-5960-87fa-e07ad55443a4", @@ -11,5 +46,6 @@ card = SupporterCardDef( collector_number=191, set_code="SWSH6", rarity=Rarities.RareUltra, - effect=unimplemented + effect=flannery, + condition=_flannery_condition, ) diff --git a/spirit/game/scripts/cards/SWSH6/Flannery_215.py b/spirit/game/scripts/cards/SWSH6/Flannery_215.py index 9a574d2..e2fc5b2 100644 --- a/spirit/game/scripts/cards/SWSH6/Flannery_215.py +++ b/spirit/game/scripts/cards/SWSH6/Flannery_215.py @@ -1,5 +1,40 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import is_special_energy + + +def _other_player(board, player_id): + return next((p for p in board.player_ids if p != player_id), None) + + +def _opponent_special_energies(board, player_id): + opponent = _other_player(board, player_id) + if not opponent: + return [] + out = [] + for pokemon in board.pokemon_in_play(opponent): + out.extend(c for c in pokemon.children if is_special_energy(c)) + return out + + +def _flannery_condition(board, player_id): + if _opponent_special_energies(board, player_id): + return True + stadium_area = board.find_global_area("activeStadium") + return bool(stadium_area and stadium_area.children) + + +async def flannery(ctx): + """Discard a Special Energy from 1 of your opponent's Pokemon, and + discard a Stadium in play.""" + targets = _opponent_special_energies(ctx.board, ctx.player_id) + if targets: + picks = await ctx.choose_cards( + targets, 1, minimum=1, prompt="Choose a Special Energy to discard" + ) + await ctx.discard_cards(picks) + await ctx.discard_stadium() + card = SupporterCardDef( guid="e5b68dc4-f095-5797-a4a9-5c87f236f562", @@ -11,5 +46,6 @@ card = SupporterCardDef( collector_number=215, set_code="SWSH6", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=flannery, + condition=_flannery_condition, ) diff --git a/spirit/game/scripts/cards/SWSH6/FogCrystal_140.py b/spirit/game/scripts/cards/SWSH6/FogCrystal_140.py index 6f4f27d..76174ce 100644 --- a/spirit/game/scripts/cards/SWSH6/FogCrystal_140.py +++ b/spirit/game/scripts/cards/SWSH6/FogCrystal_140.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import ItemCardDef +from spirit.game.attributes import Rarities, AttrID, PokemonTypes +from spirit.game.session.effects import is_basic_pokemon +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.card_effects.support_common import search_to_hand + + +def _psychic_energy_or_basic(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + if PokemonTypes.PSYCHIC.value not in types: + return False + return is_basic_energy_card(card) or is_basic_pokemon(card) + card = ItemCardDef( guid="bbdbfe30-0cb0-5d68-92c7-9fc784d29fe4", @@ -11,5 +22,8 @@ card = ItemCardDef( collector_number=140, set_code="SWSH6", rarity=Rarities.Uncommon, - effect=unimplemented + effect=search_to_hand( + _psychic_energy_or_basic, count=1, minimum=0, reveal=True, + prompt="Choose a Psychic Energy card or a Basic Psychic Pokémon.", + ), ) diff --git a/spirit/game/scripts/cards/SWSH6/FogCrystal_227.py b/spirit/game/scripts/cards/SWSH6/FogCrystal_227.py index add8eb7..78b873a 100644 --- a/spirit/game/scripts/cards/SWSH6/FogCrystal_227.py +++ b/spirit/game/scripts/cards/SWSH6/FogCrystal_227.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import ItemCardDef +from spirit.game.attributes import Rarities, AttrID, PokemonTypes +from spirit.game.session.effects import is_basic_pokemon +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.card_effects.support_common import search_to_hand + + +def _psychic_energy_or_basic(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + if PokemonTypes.PSYCHIC.value not in types: + return False + return is_basic_energy_card(card) or is_basic_pokemon(card) + card = ItemCardDef( guid="77a30184-06e1-5cf9-9a1d-d248f9c37bac", @@ -11,5 +22,8 @@ card = ItemCardDef( collector_number=227, set_code="SWSH6", rarity=Rarities.RareSecret, - effect=unimplemented + effect=search_to_hand( + _psychic_energy_or_basic, count=1, minimum=0, reveal=True, + prompt="Choose a Psychic Energy card or a Basic Psychic Pokémon.", + ), ) diff --git a/spirit/game/scripts/cards/SWSH6/Froslass_36.py b/spirit/game/scripts/cards/SWSH6/Froslass_36.py index 708d285..fada310 100644 --- a/spirit/game/scripts/cards/SWSH6/Froslass_36.py +++ b/spirit/game/scripts/cards/SWSH6/Froslass_36.py @@ -1,5 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.support_common import attach_from_discard +from spirit.game.card_effects.pokemon import is_energy_card +from spirit.game.card_effects.attacks_common import lock_all_attacks + + +def _is_water_energy(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_energy_card(card) and PokemonTypes.WATER.value in types + + +async def crystal_breath(ctx): + """During your next turn, this Pokemon can't attack.""" + await ctx.deal_damage() + lock_all_attacks(ctx, ctx.attacker) + card = PokemonCardDef( guid="b93b22e8-efed-509d-a3d8-058ff11c3084", @@ -22,14 +37,18 @@ card = PokemonCardDef( Ability( title="Frost Over", game_text="When you play this Pok\u00e9mon from your hand to evolve 1 of your Pok\u00e9mon during your turn, you may attach a Water Energy card from your discard pile to 1 of your Pok\u00e9mon.", - effect=unimplemented, + trigger=Triggers.ON_EVOLVE, + effect=attach_from_discard( + predicate=_is_water_energy, count=1, minimum=0, target="choice", + prompt="Choose a Water Energy card to attach", + ), ), Attack( title="Crystal Breath", game_text="During your next turn, this Pok\u00e9mon can't attack.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 1}, damage=90, - effect=unimplemented, + effect=crystal_breath, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Furfrou_126.py b/spirit/game/scripts/cards/SWSH6/Furfrou_126.py index 1c87b60..c3a0e6a 100644 --- a/spirit/game/scripts/cards/SWSH6/Furfrou_126.py +++ b/spirit/game/scripts/cards/SWSH6/Furfrou_126.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.card_effects.passives_common import protect_next_turn +from spirit.game.session.effects import is_pokemon_card card = PokemonCardDef( guid="c0e79807-d33b-54de-95cc-13f2c62b67f3", @@ -22,14 +25,14 @@ card = PokemonCardDef( title="Find a Friend", game_text="Search your deck for a Pok\u00e9mon, reveal it, and put it into your hand. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_to_hand(is_pokemon_card, count=1, minimum=0, reveal=True), ), Attack( title="Fur Attack", game_text="During your opponent's next turn, this Pok\u00e9mon takes 20 less damage from attacks (after applying Weakness and Resistance).", cost={PokemonTypes.COLORLESS: 3}, damage=90, - effect=unimplemented, + effect=protect_next_turn(reduce=20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/GalarianArticunoV_169.py b/spirit/game/scripts/cards/SWSH6/GalarianArticunoV_169.py index af28ce1..12d69e8 100644 --- a/spirit/game/scripts/cards/SWSH6/GalarianArticunoV_169.py +++ b/spirit/game/scripts/cards/SWSH6/GalarianArticunoV_169.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.support_common import requires_hand + + +async def reconstitute(ctx): + """Discard 2 cards from your hand. Once during your turn, you may draw a + card.""" + await ctx.discard_from_hand(2, prompt="Discard 2 cards to use Reconstitute") + if await ctx.ask_yes_no("Draw a card?"): + await ctx.draw_cards(1) + card = PokemonCardDef( guid="25585034-455b-5161-9f69-570cf701b499", @@ -22,14 +33,16 @@ card = PokemonCardDef( Ability( title="Reconstitute", game_text="You must discard 2 cards from your hand in order to use this Ability. Once during your turn, you may draw a card.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=requires_hand(n=2), + effect=reconstitute, ), Attack( title="Psyray", - game_text="Your opponent's Active Pok\u00e9mon is now Confused.", + game_text="Your opponent's Active Pokémon is now Confused.", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 2}, damage=110, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH6/GalarianArticunoV_170.py b/spirit/game/scripts/cards/SWSH6/GalarianArticunoV_170.py index aee986f..396024d 100644 --- a/spirit/game/scripts/cards/SWSH6/GalarianArticunoV_170.py +++ b/spirit/game/scripts/cards/SWSH6/GalarianArticunoV_170.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.support_common import requires_hand + + +async def reconstitute(ctx): + """Discard 2 cards from your hand. Once during your turn, you may draw a + card.""" + await ctx.discard_from_hand(2, prompt="Discard 2 cards to use Reconstitute") + if await ctx.ask_yes_no("Draw a card?"): + await ctx.draw_cards(1) + card = PokemonCardDef( guid="1a8c5a7b-4e5d-53c7-861b-e3be502a7269", @@ -22,14 +33,16 @@ card = PokemonCardDef( Ability( title="Reconstitute", game_text="You must discard 2 cards from your hand in order to use this Ability. Once during your turn, you may draw a card.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=requires_hand(n=2), + effect=reconstitute, ), Attack( title="Psyray", game_text="Your opponent's Active Pok\u00e9mon is now Confused.", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 2}, damage=110, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/GalarianArticunoV_58.py b/spirit/game/scripts/cards/SWSH6/GalarianArticunoV_58.py index ae13d06..2ca6a26 100644 --- a/spirit/game/scripts/cards/SWSH6/GalarianArticunoV_58.py +++ b/spirit/game/scripts/cards/SWSH6/GalarianArticunoV_58.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.support_common import requires_hand + + +async def reconstitute(ctx): + """Discard 2 cards from your hand. Once during your turn, you may draw a + card.""" + await ctx.discard_from_hand(2, prompt="Discard 2 cards to use Reconstitute") + if await ctx.ask_yes_no("Draw a card?"): + await ctx.draw_cards(1) + card = PokemonCardDef( guid="599da55f-31bb-5804-832f-57611541a50d", @@ -22,14 +33,16 @@ card = PokemonCardDef( Ability( title="Reconstitute", game_text="You must discard 2 cards from your hand in order to use this Ability. Once during your turn, you may draw a card.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=requires_hand(n=2), + effect=reconstitute, ), Attack( title="Psyray", game_text="Your opponent's Active Pok\u00e9mon is now Confused.", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 2}, damage=110, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/GalarianChestplate_141.py b/spirit/game/scripts/cards/SWSH6/GalarianChestplate_141.py index 61cbb13..28cec70 100644 --- a/spirit/game/scripts/cards/SWSH6/GalarianChestplate_141.py +++ b/spirit/game/scripts/cards/SWSH6/GalarianChestplate_141.py @@ -1,15 +1,37 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented +from spirit.game.data_utils import PokemonToolCardDef, def_for from spirit.game.attributes import Rarities +from spirit.game.session.passives import Passive, carrier_pokemon + + +def _is_galarian(carrier): + pokemon = carrier_pokemon(carrier) + if pokemon is None: + return False + definition = def_for(pokemon.archetype_id) + name = getattr(definition, "display_name", "") or "" + return "Galarian" in name + + +class GalarianChestplatePassive(Passive): + def modify_damage_taken(self, calc, carrier): + if not (calc.is_attack and calc.is_opposing): + return + if calc.target is not carrier_pokemon(carrier): + return + if not _is_galarian(carrier): + return + calc.amount = max(0, calc.amount - 30) + card = PokemonToolCardDef( guid="8034931b-4252-5430-aeb2-a15ae21d31dc", key="SWSH6", name="com.direwolfdigital.cake.data.archetypes.trainer.GalarianChestplate.Name", display_name="Galarian Chestplate", - searchable_by=["Galarian Chestplate", "Pok\u00c3\u00a9mon Tool"], - subtypes=["Pok\u00e9mon Tool"], + searchable_by=["Galarian Chestplate", "Pokémon Tool"], + subtypes=["Pokémon Tool"], collector_number=141, set_code="SWSH6", rarity=Rarities.Uncommon, - effect=unimplemented + passive=GalarianChestplatePassive() ) diff --git a/spirit/game/scripts/cards/SWSH6/GalarianFarfetchd_78.py b/spirit/game/scripts/cards/SWSH6/GalarianFarfetchd_78.py index 1b553be..81a27c6 100644 --- a/spirit/game/scripts/cards/SWSH6/GalarianFarfetchd_78.py +++ b/spirit/game/scripts/cards/SWSH6/GalarianFarfetchd_78.py @@ -1,6 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def puncture(ctx): + """This attack's damage isn't affected by Resistance.""" + await ctx.deal_damage(ignore_resistance=True) + + card = PokemonCardDef( guid="c34abac5-9916-5499-afe3-3b6849265dae", key="SWSH6", @@ -23,7 +29,7 @@ card = PokemonCardDef( game_text="This attack's damage isn't affected by Resistance.", cost={PokemonTypes.FIGHTING: 1}, damage=20, - effect=unimplemented, + effect=puncture, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/GalarianMoltresV_176.py b/spirit/game/scripts/cards/SWSH6/GalarianMoltresV_176.py index a0ba20d..49de6ba 100644 --- a/spirit/game/scripts/cards/SWSH6/GalarianMoltresV_176.py +++ b/spirit/game/scripts/cards/SWSH6/GalarianMoltresV_176.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack +from spirit.game.card_effects.pokemon import is_energy_card +from spirit.game.card_effects.support_common import attach_from_discard, requires_discard + + +def is_darkness_energy_card(card): + return is_energy_card(card) and PokemonTypes.DARKNESS.value in (card.get_attribute(AttrID.POKEMON_TYPES) or []) + card = PokemonCardDef( guid="41075035-7467-53a6-95bd-1bdf7862570c", @@ -21,14 +29,17 @@ card = PokemonCardDef( Ability( title="Direflame Wings", game_text="Once during your turn, you may attach a Darkness Energy card from your discard pile to this Pok\u00e9mon. You can't use more than 1 Direflame Wings Ability each turn.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + shared_once_per_turn="Direflame Wings", + condition=requires_discard(is_darkness_energy_card), + effect=attach_from_discard(predicate=is_darkness_energy_card, count=1, target="self"), ), Attack( title="Aura Burn", game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.DARKNESS: 2, PokemonTypes.COLORLESS: 1}, damage=190, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/GalarianMoltresV_177.py b/spirit/game/scripts/cards/SWSH6/GalarianMoltresV_177.py index faca200..f25143d 100644 --- a/spirit/game/scripts/cards/SWSH6/GalarianMoltresV_177.py +++ b/spirit/game/scripts/cards/SWSH6/GalarianMoltresV_177.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack +from spirit.game.card_effects.pokemon import is_energy_card +from spirit.game.card_effects.support_common import attach_from_discard, requires_discard + + +def is_darkness_energy_card(card): + return is_energy_card(card) and PokemonTypes.DARKNESS.value in (card.get_attribute(AttrID.POKEMON_TYPES) or []) + card = PokemonCardDef( guid="8558dea5-2390-538a-9da6-c209f0b312c5", @@ -21,14 +29,17 @@ card = PokemonCardDef( Ability( title="Direflame Wings", game_text="Once during your turn, you may attach a Darkness Energy card from your discard pile to this Pok\u00e9mon. You can't use more than 1 Direflame Wings Ability each turn.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + shared_once_per_turn="Direflame Wings", + condition=requires_discard(is_darkness_energy_card), + effect=attach_from_discard(predicate=is_darkness_energy_card, count=1, target="self"), ), Attack( title="Aura Burn", game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.DARKNESS: 2, PokemonTypes.COLORLESS: 1}, damage=190, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/GalarianMoltresV_97.py b/spirit/game/scripts/cards/SWSH6/GalarianMoltresV_97.py index 43651e9..ec68b0b 100644 --- a/spirit/game/scripts/cards/SWSH6/GalarianMoltresV_97.py +++ b/spirit/game/scripts/cards/SWSH6/GalarianMoltresV_97.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack +from spirit.game.card_effects.pokemon import is_energy_card +from spirit.game.card_effects.support_common import attach_from_discard, requires_discard + + +def is_darkness_energy_card(card): + return is_energy_card(card) and PokemonTypes.DARKNESS.value in (card.get_attribute(AttrID.POKEMON_TYPES) or []) + card = PokemonCardDef( guid="cdac4f88-0afe-5c44-b0a4-997224129fab", @@ -21,14 +29,17 @@ card = PokemonCardDef( Ability( title="Direflame Wings", game_text="Once during your turn, you may attach a Darkness Energy card from your discard pile to this Pok\u00e9mon. You can't use more than 1 Direflame Wings Ability each turn.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + shared_once_per_turn="Direflame Wings", + condition=requires_discard(is_darkness_energy_card), + effect=attach_from_discard(predicate=is_darkness_energy_card, count=1, target="self"), ), Attack( title="Aura Burn", game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.DARKNESS: 2, PokemonTypes.COLORLESS: 1}, damage=190, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/GalarianRapidashV_167.py b/spirit/game/scripts/cards/SWSH6/GalarianRapidashV_167.py index d80f37c..8bb1e4c 100644 --- a/spirit/game/scripts/cards/SWSH6/GalarianRapidashV_167.py +++ b/spirit/game/scripts/cards/SWSH6/GalarianRapidashV_167.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy + + +async def libra_horn(ctx): + """Put damage counters on 1 of the opponent's Pokemon until its + remaining HP is 100.""" + candidates = ctx.opponent_pokemon_in_play() + if not candidates: + return + target = await ctx.choose_pokemon(candidates, "Choose 1 of your opponent's Pokémon") + if target is None: + return + current = target.get_attribute(AttrID.HP, ctx.max_hp(target)) + if current > 100: + await ctx.deal_damage(current - 100, target=target, apply_modifiers=False, + as_counters=True) + card = PokemonCardDef( guid="44d23c3a-91a2-58d8-ae6e-0d502fd69bf6", @@ -23,7 +40,7 @@ card = PokemonCardDef( title="Libra Horn", game_text="Put damage counters on 1 of your opponent's Pok\u00e9mon until its remaining HP is 100.", cost={PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + effect=libra_horn, ), Attack( title="Psychic", @@ -31,7 +48,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 2}, damage=60, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("defender"), 30, base=60), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/GalarianRapidashV_168.py b/spirit/game/scripts/cards/SWSH6/GalarianRapidashV_168.py index 2927763..5f46a8d 100644 --- a/spirit/game/scripts/cards/SWSH6/GalarianRapidashV_168.py +++ b/spirit/game/scripts/cards/SWSH6/GalarianRapidashV_168.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy + + +async def libra_horn(ctx): + """Put damage counters on 1 of the opponent's Pokemon until its + remaining HP is 100.""" + candidates = ctx.opponent_pokemon_in_play() + if not candidates: + return + target = await ctx.choose_pokemon(candidates, "Choose 1 of your opponent's Pokémon") + if target is None: + return + current = target.get_attribute(AttrID.HP, ctx.max_hp(target)) + if current > 100: + await ctx.deal_damage(current - 100, target=target, apply_modifiers=False, + as_counters=True) + card = PokemonCardDef( guid="4251601d-8c27-583a-bc6e-2d5ce97f7c6a", @@ -23,7 +40,7 @@ card = PokemonCardDef( title="Libra Horn", game_text="Put damage counters on 1 of your opponent's Pok\u00e9mon until its remaining HP is 100.", cost={PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + effect=libra_horn, ), Attack( title="Psychic", @@ -31,7 +48,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 2}, damage=60, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("defender"), 30, base=60), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/GalarianRunerigus_83.py b/spirit/game/scripts/cards/SWSH6/GalarianRunerigus_83.py index d7f661e..a6f415d 100644 --- a/spirit/game/scripts/cards/SWSH6/GalarianRunerigus_83.py +++ b/spirit/game/scripts/cards/SWSH6/GalarianRunerigus_83.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy +from spirit.game.card_effects.pokemon import in_active_spot, is_pokemon_vmax + + +async def spiteful_slate(ctx): + """Active Spot only: if damaged by an attack from an opposing Pokemon + VMAX (even if Knocked Out), put damage counters on the Attacking + Pokemon equal to the damage done.""" + if not in_active_spot(ctx.board, ctx.player_id, ctx.source): + return + attacker = ctx.damaged_by + if attacker is None or not is_pokemon_vmax(attacker.archetype_id): + return + if ctx.damage_amount: + await ctx.deal_damage(ctx.damage_amount, target=attacker, apply_modifiers=False, + as_counters=True) + card = PokemonCardDef( guid="7e34bc62-a849-5422-8c34-435bde640931", @@ -22,7 +39,8 @@ card = PokemonCardDef( Ability( title="Spiteful Slate", game_text="If this Pok\u00e9mon is in the Active Spot and is damaged by an attack from your opponent's Pok\u00e9mon VMAX (even if this Pok\u00e9mon is Knocked Out), put damage counters on the Attacking Pok\u00e9mon equal to the damage done to this Pok\u00e9mon.", - effect=unimplemented, + trigger=Triggers.ON_DAMAGED_BY_ATTACK, + effect=spiteful_slate, ), Attack( title="Energy Press", @@ -30,7 +48,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=60, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("defender"), 20, base=60), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/GalarianSirfetchd_79.py b/spirit/game/scripts/cards/SWSH6/GalarianSirfetchd_79.py index 7af0d97..e6531a9 100644 --- a/spirit/game/scripts/cards/SWSH6/GalarianSirfetchd_79.py +++ b/spirit/game/scripts/cards/SWSH6/GalarianSirfetchd_79.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import has_tool + + +async def leek_strike(ctx): + tooled = has_tool(ctx.attacker) + await ctx.deal_damage(70 + (90 if tooled else 0), ignore_resistance=tooled) + card = PokemonCardDef( guid="e64ac4a8-abea-5946-aabb-5a8a8cbecb16", @@ -30,7 +37,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 2}, damage=70, damage_operator="+", - effect=unimplemented, + effect=leek_strike, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/GalarianSlowkingVMAX_100.py b/spirit/game/scripts/cards/SWSH6/GalarianSlowkingVMAX_100.py index 78f2203..7565e3a 100644 --- a/spirit/game/scripts/cards/SWSH6/GalarianSlowkingVMAX_100.py +++ b/spirit/game/scripts/cards/SWSH6/GalarianSlowkingVMAX_100.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="bd2bd566-3e66-53ee-b4cd-462321f92781", @@ -21,10 +22,10 @@ card = PokemonCardDef( abilities=[ Attack( title="Max Toxify", - game_text="Your opponent's Active Pok\u00e9mon is now Poisoned. During Pok\u00e9mon Checkup, put 12 damage counters on that Pok\u00e9mon instead of 1.", + game_text="Your opponent's Active Pokémon is now Poisoned. During Pokémon Checkup, put 12 damage counters on that Pokémon instead of 1.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 1}, damage=10, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED, counters=12), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH6/GalarianSlowkingVMAX_207.py b/spirit/game/scripts/cards/SWSH6/GalarianSlowkingVMAX_207.py index 0e8b7cd..165e88f 100644 --- a/spirit/game/scripts/cards/SWSH6/GalarianSlowkingVMAX_207.py +++ b/spirit/game/scripts/cards/SWSH6/GalarianSlowkingVMAX_207.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="62da2fb9-770a-5562-8bbf-f6b22b9091af", @@ -21,10 +22,10 @@ card = PokemonCardDef( abilities=[ Attack( title="Max Toxify", - game_text="Your opponent's Active Pok\u00e9mon is now Poisoned. During Pok\u00e9mon Checkup, put 12 damage counters on that Pok\u00e9mon instead of 1.", + game_text="Your opponent's Active Pokémon is now Poisoned. During Pokémon Checkup, put 12 damage counters on that Pokémon instead of 1.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 1}, damage=10, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED, counters=12), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH6/GalarianSlowkingV_178.py b/spirit/game/scripts/cards/SWSH6/GalarianSlowkingV_178.py index f34ddb9..b20ea09 100644 --- a/spirit/game/scripts/cards/SWSH6/GalarianSlowkingV_178.py +++ b/spirit/game/scripts/cards/SWSH6/GalarianSlowkingV_178.py @@ -1,5 +1,6 @@ from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import delayed_knockout, read_the_wind card = PokemonCardDef( guid="24a1dabb-b3a9-561e-8997-2e0bc2479dad", @@ -22,13 +23,13 @@ card = PokemonCardDef( title="Concoction", game_text="Discard a card from your hand. If you do, draw 3 cards.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=read_the_wind, ), Attack( title="Word of Ruin", game_text="At the end of your opponent's next turn, the Defending Pok\u00e9mon will be Knocked Out.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=delayed_knockout, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/GalarianSlowkingV_179.py b/spirit/game/scripts/cards/SWSH6/GalarianSlowkingV_179.py index 61f0e72..1db1931 100644 --- a/spirit/game/scripts/cards/SWSH6/GalarianSlowkingV_179.py +++ b/spirit/game/scripts/cards/SWSH6/GalarianSlowkingV_179.py @@ -1,5 +1,6 @@ from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import delayed_knockout, read_the_wind card = PokemonCardDef( guid="1686faaa-4016-5cdd-a33f-3d9dc757d411", @@ -22,13 +23,13 @@ card = PokemonCardDef( title="Concoction", game_text="Discard a card from your hand. If you do, draw 3 cards.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=read_the_wind, ), Attack( title="Word of Ruin", game_text="At the end of your opponent's next turn, the Defending Pok\u00e9mon will be Knocked Out.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=delayed_knockout, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/GalarianSlowkingV_99.py b/spirit/game/scripts/cards/SWSH6/GalarianSlowkingV_99.py index ee019b9..c10ee7d 100644 --- a/spirit/game/scripts/cards/SWSH6/GalarianSlowkingV_99.py +++ b/spirit/game/scripts/cards/SWSH6/GalarianSlowkingV_99.py @@ -1,5 +1,6 @@ from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import delayed_knockout, read_the_wind card = PokemonCardDef( guid="8cacaf2d-8771-56d9-9dc6-22313137a545", @@ -22,13 +23,13 @@ card = PokemonCardDef( title="Concoction", game_text="Discard a card from your hand. If you do, draw 3 cards.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=read_the_wind, ), Attack( title="Word of Ruin", game_text="At the end of your opponent's next turn, the Defending Pok\u00e9mon will be Knocked Out.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=delayed_knockout, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/GalarianSlowking_98.py b/spirit/game/scripts/cards/SWSH6/GalarianSlowking_98.py index 399dc4e..1cac333 100644 --- a/spirit/game/scripts/cards/SWSH6/GalarianSlowking_98.py +++ b/spirit/game/scripts/cards/SWSH6/GalarianSlowking_98.py @@ -1,6 +1,24 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def mysterious_potion(ctx): + """Once per turn: you may choose 1 of your Pokémon and flip a coin. Heads + heals 90 from it; tails puts 3 damage counters on it.""" + if not await ctx.ask_yes_no("Choose 1 of your Pokémon and flip a coin?"): + return + target = await ctx.choose_pokemon( + ctx.my_pokemon_in_play(), "Choose 1 of your Pokémon" + ) + if target is None: + return + heads = (await ctx.flip_coins(1, "Mysterious Potion"))[0] + if heads: + await ctx.heal(90, target=target) + else: + await ctx.deal_damage(30, target=target, apply_modifiers=False, as_counters=True) + + card = PokemonCardDef( guid="d944645a-0420-5347-b863-d5f6a19e1208", key="SWSH6", @@ -22,7 +40,8 @@ card = PokemonCardDef( Ability( title="Mysterious Potion", game_text="Once during your turn, you may choose 1 of your Pok\u00e9mon and flip a coin. If heads, heal 90 damage from that Pok\u00e9mon. If tails, put 3 damage counters on that Pok\u00e9mon.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + effect=mysterious_potion, ), Attack( title="Spray Fluid", diff --git a/spirit/game/scripts/cards/SWSH6/GalarianWeezing_96.py b/spirit/game/scripts/cards/SWSH6/GalarianWeezing_96.py index 96be7af..c6dadeb 100644 --- a/spirit/game/scripts/cards/SWSH6/GalarianWeezing_96.py +++ b/spirit/game/scripts/cards/SWSH6/GalarianWeezing_96.py @@ -1,5 +1,32 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, def_for +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.pokemon import energy_provides_type +from spirit.game.session.passives import Passive, active_passives + + +def _is_weezing_named(pokemon): + d = def_for(pokemon.archetype_id) + return d is not None and "Weezing" in (d.display_name or "") + + +class EnergyFactoryPassive(Passive): + def modify_energy_provided(self, options, energy, holder, board): + if holder is None or energy.get_attribute(AttrID.IS_SPECIAL_ENERGY): + return options + if not energy_provides_type(energy, PokemonTypes.DARKNESS.value): + return options + if not _is_weezing_named(holder): + return options + if any(len(option) >= 2 for option in options): + return options + active_here = any( + isinstance(p, EnergyFactoryPassive) and c.owning_player_id == holder.owning_player_id + for p, c in active_passives(board) + ) + if not active_here: + return options + return [list(option) * 2 for option in options] + card = PokemonCardDef( guid="8b3f9833-676f-5184-a3d1-028d2a83db1d", @@ -22,7 +49,7 @@ card = PokemonCardDef( Ability( title="Energy Factory", game_text="Each basic Darkness Energy attached to your Pok\u00e9mon that have \"Weezing\" in their name provides DarknessDarkness Energy. You can't apply more than 1 Energy Factory Ability at a time.", - effect=unimplemented, + passive=EnergyFactoryPassive(), ), Attack( title="Suffocating Gas", diff --git a/spirit/game/scripts/cards/SWSH6/GalarianYamask_82.py b/spirit/game/scripts/cards/SWSH6/GalarianYamask_82.py index d1d20d7..843c65d 100644 --- a/spirit/game/scripts/cards/SWSH6/GalarianYamask_82.py +++ b/spirit/game/scripts/cards/SWSH6/GalarianYamask_82.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus card = PokemonCardDef( guid="63d04c0f-0dd3-5e59-b07a-2e5e17776b66", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=10, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/GalarianZapdosV_173.py b/spirit/game/scripts/cards/SWSH6/GalarianZapdosV_173.py index 52dd206..9538eed 100644 --- a/spirit/game/scripts/cards/SWSH6/GalarianZapdosV_173.py +++ b/spirit/game/scripts/cards/SWSH6/GalarianZapdosV_173.py @@ -1,5 +1,29 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, is_pokemon_v from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import discard_opponent_energy_attack +from spirit.game.session.passives import Passive + + +class FightingInstinctPassive(Passive): + def modify_attack_cost(self, cost, pokemon, carrier, board): + if pokemon is not carrier: + return cost + owner = carrier.owning_player_id + opponent = next((pid for pid in board.player_ids if pid != owner), None) + if opponent is None or "Colorless" not in cost: + return cost + discount = sum( + 1 for p in board.pokemon_in_play(opponent) if is_pokemon_v(p.archetype_id) + ) + if discount <= 0: + return cost + remaining = cost["Colorless"] - discount + if remaining > 0: + cost["Colorless"] = remaining + else: + del cost["Colorless"] + return cost + card = PokemonCardDef( guid="d7aa9b50-2113-5b61-9981-4c62ec695e82", @@ -21,14 +45,14 @@ card = PokemonCardDef( Ability( title="Fighting Instinct", game_text="This Pok\u00e9mon's attacks cost Colorless less for each of your opponent's Pok\u00e9mon V in play.", - effect=unimplemented, + passive=FightingInstinctPassive(), ), Attack( title="Thunderous Kick", game_text="Before doing damage, discard a Special Energy from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 3}, damage=170, - effect=unimplemented, + effect=discard_opponent_energy_attack(special_only=True, after_damage=False), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/GalarianZapdosV_174.py b/spirit/game/scripts/cards/SWSH6/GalarianZapdosV_174.py index b80272d..5acbf2d 100644 --- a/spirit/game/scripts/cards/SWSH6/GalarianZapdosV_174.py +++ b/spirit/game/scripts/cards/SWSH6/GalarianZapdosV_174.py @@ -1,5 +1,29 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, is_pokemon_v from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import discard_opponent_energy_attack +from spirit.game.session.passives import Passive + + +class FightingInstinctPassive(Passive): + def modify_attack_cost(self, cost, pokemon, carrier, board): + if pokemon is not carrier: + return cost + owner = carrier.owning_player_id + opponent = next((pid for pid in board.player_ids if pid != owner), None) + if opponent is None or "Colorless" not in cost: + return cost + discount = sum( + 1 for p in board.pokemon_in_play(opponent) if is_pokemon_v(p.archetype_id) + ) + if discount <= 0: + return cost + remaining = cost["Colorless"] - discount + if remaining > 0: + cost["Colorless"] = remaining + else: + del cost["Colorless"] + return cost + card = PokemonCardDef( guid="99391ac2-9439-531d-8e43-9ff88d19e5c9", @@ -21,14 +45,14 @@ card = PokemonCardDef( Ability( title="Fighting Instinct", game_text="This Pok\u00e9mon's attacks cost Colorless less for each of your opponent's Pok\u00e9mon V in play.", - effect=unimplemented, + passive=FightingInstinctPassive(), ), Attack( title="Thunderous Kick", game_text="Before doing damage, discard a Special Energy from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 3}, damage=170, - effect=unimplemented, + effect=discard_opponent_energy_attack(special_only=True, after_damage=False), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/GalarianZapdosV_80.py b/spirit/game/scripts/cards/SWSH6/GalarianZapdosV_80.py index a63038d..5f26294 100644 --- a/spirit/game/scripts/cards/SWSH6/GalarianZapdosV_80.py +++ b/spirit/game/scripts/cards/SWSH6/GalarianZapdosV_80.py @@ -1,5 +1,29 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, is_pokemon_v from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import discard_opponent_energy_attack +from spirit.game.session.passives import Passive + + +class FightingInstinctPassive(Passive): + def modify_attack_cost(self, cost, pokemon, carrier, board): + if pokemon is not carrier: + return cost + owner = carrier.owning_player_id + opponent = next((pid for pid in board.player_ids if pid != owner), None) + if opponent is None or "Colorless" not in cost: + return cost + discount = sum( + 1 for p in board.pokemon_in_play(opponent) if is_pokemon_v(p.archetype_id) + ) + if discount <= 0: + return cost + remaining = cost["Colorless"] - discount + if remaining > 0: + cost["Colorless"] = remaining + else: + del cost["Colorless"] + return cost + card = PokemonCardDef( guid="9db74d2d-4cae-5a15-b837-26eaa9dc9b5d", @@ -21,14 +45,14 @@ card = PokemonCardDef( Ability( title="Fighting Instinct", game_text="This Pok\u00e9mon's attacks cost Colorless less for each of your opponent's Pok\u00e9mon V in play.", - effect=unimplemented, + passive=FightingInstinctPassive(), ), Attack( title="Thunderous Kick", game_text="Before doing damage, discard a Special Energy from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 3}, damage=170, - effect=unimplemented, + effect=discard_opponent_energy_attack(special_only=True, after_damage=False), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Gallade_81.py b/spirit/game/scripts/cards/SWSH6/Gallade_81.py index 4412657..1783631 100644 --- a/spirit/game/scripts/cards/SWSH6/Gallade_81.py +++ b/spirit/game/scripts/cards/SWSH6/Gallade_81.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, is_pokemon_v from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_in_play + + +async def feint(ctx): + """60. This attack's damage isn't affected by Resistance.""" + await ctx.deal_damage(ignore_resistance=True) + card = PokemonCardDef( guid="a88eba63-348e-5a6e-857d-bf7fe0050775", @@ -24,7 +31,7 @@ card = PokemonCardDef( game_text="This attack's damage isn't affected by Resistance.", cost={PokemonTypes.PSYCHIC: 1}, damage=60, - effect=unimplemented, + effect=feint, ), Attack( title="Dynablade", @@ -32,7 +39,10 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=60, damage_operator="x", - effect=unimplemented, + effect=damage_per( + count_in_play("opponent", pred=lambda c: is_pokemon_v(c.archetype_id)), + 60, + ), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Gardevoir_61.py b/spirit/game/scripts/cards/SWSH6/Gardevoir_61.py index 0254af5..40957eb 100644 --- a/spirit/game/scripts/cards/SWSH6/Gardevoir_61.py +++ b/spirit/game/scripts/cards/SWSH6/Gardevoir_61.py @@ -1,5 +1,29 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import distribute_energy +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.card_effects.attacks_common import damage_per, count_energy + + +async def shining_arcana(ctx): + if not await ctx.ask_yes_no("Look at the top 2 cards of your deck?"): + return + top = ctx.deck_top(2) + if not top: + return + energies = [c for c in top if is_basic_energy_card(c)] + picks = [] + if energies: + picks = await ctx.choose_cards( + energies, len(energies), minimum=0, + prompt="Choose basic Energy cards to attach to your Pokémon.", + ) + if picks: + await distribute_energy(ctx, picks, ctx.my_pokemon_in_play()) + remaining = [c for c in top if c not in picks] + if remaining: + await ctx.put_in_hand(remaining, reveal=False) + card = PokemonCardDef( guid="a40159a4-cfe1-5475-8c4d-ead32f6ed1f0", @@ -22,7 +46,8 @@ card = PokemonCardDef( Ability( title="Shining Arcana", game_text="Once during your turn, you may look at the top 2 cards of your deck and attach any number of basic Energy cards you find there to your Pok\u00e9mon in any way you like. Put the other cards into your hand.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + effect=shining_arcana, ), Attack( title="Brainwave", @@ -30,7 +55,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=60, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("self", energy_type=PokemonTypes.PSYCHIC), 30, base=60), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Gastly_55.py b/spirit/game/scripts/cards/SWSH6/Gastly_55.py index e083247..b1baca9 100644 --- a/spirit/game/scripts/cards/SWSH6/Gastly_55.py +++ b/spirit/game/scripts/cards/SWSH6/Gastly_55.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="f9a7927c-c688-5bbb-b6c9-9ea75e3cb833", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Asleep.", cost={PokemonTypes.PSYCHIC: 1}, damage=10, - effect=unimplemented, + effect=condition_attack(SpecialConditions.ASLEEP, flip=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Gengar_57.py b/spirit/game/scripts/cards/SWSH6/Gengar_57.py index 1995a4b..fc5c566 100644 --- a/spirit/game/scripts/cards/SWSH6/Gengar_57.py +++ b/spirit/game/scripts/cards/SWSH6/Gengar_57.py @@ -1,6 +1,7 @@ from spirit.game.card_effects.pokemon import last_gift -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, damage_counters_on card = PokemonCardDef( guid="c8dc45e4-a227-51a1-98da-3f9bd15a2519", @@ -33,7 +34,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=10, damage_operator="+", - effect=unimplemented, + effect=damage_per(damage_counters_on("defender"), 40, base=10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Golurk_66.py b/spirit/game/scripts/cards/SWSH6/Golurk_66.py index 5a6f45a..0cb5726 100644 --- a/spirit/game/scripts/cards/SWSH6/Golurk_66.py +++ b/spirit/game/scripts/cards/SWSH6/Golurk_66.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, has_tool, recoil_attack + + +def _has_own_tool(ctx): + return has_tool(ctx.attacker) + card = PokemonCardDef( guid="da80fa47-56a8-5986-bc3a-8c23af8a9347", @@ -26,14 +32,14 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 2}, damage=60, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_has_own_tool, 90), ), Attack( title="Megaton Fall", game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.PSYCHIC: 2, PokemonTypes.COLORLESS: 2}, damage=190, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Grapploct_92.py b/spirit/game/scripts/cards/SWSH6/Grapploct_92.py index cc91dbb..13ffd71 100644 --- a/spirit/game/scripts/cards/SWSH6/Grapploct_92.py +++ b/spirit/game/scripts/cards/SWSH6/Grapploct_92.py @@ -1,5 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if +from spirit.game.card_effects.passives_common import is_in_active_spot, opposing_active +from spirit.game.session.passives import Passive + + +class StrangleholdMasterPassive(Passive): + def modify_retreat_cost(self, cost, pokemon, carrier, board): + if is_in_active_spot(carrier) and opposing_active(pokemon, carrier): + return cost + 2 + return cost + + +def _same_hand_size(ctx): + return ctx.hand_size(ctx.player_id) == ctx.hand_size(ctx.opponent_id) + card = PokemonCardDef( guid="e62bc2c7-139b-5741-b864-43ed029d1cab", @@ -22,7 +37,7 @@ card = PokemonCardDef( Ability( title="Stranglehold Master", game_text="As long as this Pok\u00e9mon is in the Active Spot, your opponent's Active Pok\u00e9mon's Retreat Cost is ColorlessColorless more.", - effect=unimplemented, + passive=StrangleholdMasterPassive(), ), Attack( title="Synchro Buster", @@ -30,7 +45,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 2}, damage=80, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_same_hand_size, 80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Greedent_128.py b/spirit/game/scripts/cards/SWSH6/Greedent_128.py index 1eba4cc..072a5b4 100644 --- a/spirit/game/scripts/cards/SWSH6/Greedent_128.py +++ b/spirit/game/scripts/cards/SWSH6/Greedent_128.py @@ -1,5 +1,24 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.trainers import is_energy_card +from spirit.game.session.passives import Passive, carrier_pokemon + +_IN_PLAY_AREAS = ("activePokemonArea", "bench") + + +class BrazenTailPassive(Passive): + """Energy on our side can't be moved to hand/deck/discard by an opposing + Item/Supporter effect.""" + + def blocks_energy_removal(self, energy, mover_player_id, carrier): + if not is_energy_card(energy) \ + or mover_player_id == carrier.owning_player_id: + return False + holder = carrier_pokemon(energy) + if holder is None or holder.owning_player_id != carrier.owning_player_id: + return False + area = getattr(holder, "parent", None) + return bool(area) and area.get_attribute(AttrID.NAME) in _IN_PLAY_AREAS card = PokemonCardDef( guid="7d190e5c-e7c5-5860-9d7a-7f42fd27702a", @@ -22,7 +41,7 @@ card = PokemonCardDef( Ability( title="Brazen Tail", game_text="Energy attached to your Pok\u00e9mon can't be put into your hand, deck, or discard pile by an effect of your opponent's Item or Supporter cards.", - effect=unimplemented, + passive=BrazenTailPassive(), ), Attack( title="Gnaw", diff --git a/spirit/game/scripts/cards/SWSH6/Grookey_16.py b/spirit/game/scripts/cards/SWSH6/Grookey_16.py index 0d455bf..2358ff6 100644 --- a/spirit/game/scripts/cards/SWSH6/Grookey_16.py +++ b/spirit/game/scripts/cards/SWSH6/Grookey_16.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="2d4f5564-05af-58f7-a717-074616ad175a", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=30, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=2, per_heads=30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Hatterene_73.py b/spirit/game/scripts/cards/SWSH6/Hatterene_73.py index 977f951..5dd97f0 100644 --- a/spirit/game/scripts/cards/SWSH6/Hatterene_73.py +++ b/spirit/game/scripts/cards/SWSH6/Hatterene_73.py @@ -1,6 +1,35 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import damage_per, count_energy +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +def _witch_rondo_condition(board, player_id, pokemon=None): + bench = board.find_player_area(player_id, "bench") + return bool(bench and bench.children) + + +async def witch_rondo(ctx): + """You may switch your Active with a Benched Pokémon; if you do, your + opponent switches their Active with a Benched Pokémon too.""" + bench = ctx.my_bench() + if not bench: + return + if not await ctx.ask_yes_no( + "Switch your Active Pokémon with 1 of your Benched Pokémon?"): + return + target = await ctx.choose_pokemon(bench, "Choose your new Active Pokémon") + if target is None: + return + await ctx.switch_active(ctx.player_id, target) + opp_bench = ctx.opponent_bench() + if opp_bench: + opp_target = await ctx.choose_pokemon( + opp_bench, "Choose your new Active Pokémon", player_id=ctx.opponent_id + ) + if opp_target is not None: + await ctx.switch_active(ctx.opponent_id, opp_target) + + card = PokemonCardDef( guid="19405b49-dd40-5516-beb3-1920a1dbdb9e", key="SWSH6", @@ -23,7 +52,9 @@ card = PokemonCardDef( Ability( title="Witch Rondo", game_text="Once during your turn, you may switch your Active Pok\u00e9mon with 1 of your Benched Pok\u00e9mon. If you do, your opponent switches their Active Pok\u00e9mon with 1 of their Benched Pok\u00e9mon.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=_witch_rondo_condition, + effect=witch_rondo, ), Attack( title="Psychic", @@ -31,7 +62,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=30, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("defender"), 50, base=30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Hattrem_72.py b/spirit/game/scripts/cards/SWSH6/Hattrem_72.py index 777990f..051da37 100644 --- a/spirit/game/scripts/cards/SWSH6/Hattrem_72.py +++ b/spirit/game/scripts/cards/SWSH6/Hattrem_72.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import heal_attack card = PokemonCardDef( guid="7b7fa355-0687-5c42-97f2-67ec7fd6098c", @@ -25,7 +26,7 @@ card = PokemonCardDef( game_text="Heal 30 damage from this Pok\u00e9mon.", cost={PokemonTypes.PSYCHIC: 1}, damage=30, - effect=unimplemented, + effect=heal_attack(30, target="self"), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Heracross_6.py b/spirit/game/scripts/cards/SWSH6/Heracross_6.py index d1164e8..edc1bfd 100644 --- a/spirit/game/scripts/cards/SWSH6/Heracross_6.py +++ b/spirit/game/scripts/cards/SWSH6/Heracross_6.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus card = PokemonCardDef( guid="145be926-19e8-532a-a675-7d047f129980", @@ -29,7 +30,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=40, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(160, coins=2), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Honey_142.py b/spirit/game/scripts/cards/SWSH6/Honey_142.py index a3bc99c..70a4071 100644 --- a/spirit/game/scripts/cards/SWSH6/Honey_142.py +++ b/spirit/game/scripts/cards/SWSH6/Honey_142.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.data_utils import is_pokemon_v + + +async def honey(ctx): + """Draw a card for each of your opponent's Benched Pokemon V.""" + count = sum(1 for p in ctx.opponent_bench() if is_pokemon_v(p.archetype_id)) + if count: + await ctx.draw_cards(count) + card = SupporterCardDef( guid="858db387-0163-5ad8-839f-e0ba4c533bcb", @@ -11,5 +20,5 @@ card = SupporterCardDef( collector_number=142, set_code="SWSH6", rarity=Rarities.Uncommon, - effect=unimplemented + effect=honey, ) diff --git a/spirit/game/scripts/cards/SWSH6/Honey_192.py b/spirit/game/scripts/cards/SWSH6/Honey_192.py index c399dc3..f914db8 100644 --- a/spirit/game/scripts/cards/SWSH6/Honey_192.py +++ b/spirit/game/scripts/cards/SWSH6/Honey_192.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.data_utils import is_pokemon_v + + +async def honey(ctx): + """Draw a card for each of your opponent's Benched Pokemon V.""" + count = sum(1 for p in ctx.opponent_bench() if is_pokemon_v(p.archetype_id)) + if count: + await ctx.draw_cards(count) + card = SupporterCardDef( guid="f05d29cd-fd6f-597b-9830-1da1ef089e1d", @@ -11,5 +20,5 @@ card = SupporterCardDef( collector_number=192, set_code="SWSH6", rarity=Rarities.RareUltra, - effect=unimplemented + effect=honey, ) diff --git a/spirit/game/scripts/cards/SWSH6/IceRiderCalyrexVMAX_202.py b/spirit/game/scripts/cards/SWSH6/IceRiderCalyrexVMAX_202.py index 83046e2..bfa97d7 100644 --- a/spirit/game/scripts/cards/SWSH6/IceRiderCalyrexVMAX_202.py +++ b/spirit/game/scripts/cards/SWSH6/IceRiderCalyrexVMAX_202.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_bench + + +async def max_lance(ctx): + """You may discard up to 2 Energy from this Pokémon: +120 damage each.""" + discarded = [] + if ctx.attached_energies(ctx.attacker) and await ctx.ask_yes_no( + "Discard up to 2 Energy from this Pokémon?"): + discarded = await ctx.discard_energy_from( + ctx.attacker, 2, minimum=0, + prompt="Choose up to 2 Energy to discard") + await ctx.deal_damage(10 + 120 * len(discarded)) + card = PokemonCardDef( guid="ddb00149-826c-5fa4-bea2-258d68acd9e6", @@ -25,7 +38,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=10, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_bench("opponent"), 30, base=10), ), Attack( title="Max Lance", @@ -33,7 +46,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 2}, damage=10, damage_operator="+", - effect=unimplemented, + effect=max_lance, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/IceRiderCalyrexVMAX_203.py b/spirit/game/scripts/cards/SWSH6/IceRiderCalyrexVMAX_203.py index 54eb679..2f86917 100644 --- a/spirit/game/scripts/cards/SWSH6/IceRiderCalyrexVMAX_203.py +++ b/spirit/game/scripts/cards/SWSH6/IceRiderCalyrexVMAX_203.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_bench + + +async def max_lance(ctx): + """You may discard up to 2 Energy from this Pokémon: +120 damage each.""" + discarded = [] + if ctx.attached_energies(ctx.attacker) and await ctx.ask_yes_no( + "Discard up to 2 Energy from this Pokémon?"): + discarded = await ctx.discard_energy_from( + ctx.attacker, 2, minimum=0, + prompt="Choose up to 2 Energy to discard") + await ctx.deal_damage(10 + 120 * len(discarded)) + card = PokemonCardDef( guid="2953083b-0b46-5a4b-8ff5-365b7dd47c04", @@ -25,7 +38,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=10, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_bench("opponent"), 30, base=10), ), Attack( title="Max Lance", @@ -33,7 +46,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 2}, damage=10, damage_operator="+", - effect=unimplemented, + effect=max_lance, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/IceRiderCalyrexVMAX_46.py b/spirit/game/scripts/cards/SWSH6/IceRiderCalyrexVMAX_46.py index 0d149d9..57b6d42 100644 --- a/spirit/game/scripts/cards/SWSH6/IceRiderCalyrexVMAX_46.py +++ b/spirit/game/scripts/cards/SWSH6/IceRiderCalyrexVMAX_46.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_bench + + +async def max_lance(ctx): + """You may discard up to 2 Energy from this Pokémon: +120 damage each.""" + discarded = [] + if ctx.attached_energies(ctx.attacker) and await ctx.ask_yes_no( + "Discard up to 2 Energy from this Pokémon?"): + discarded = await ctx.discard_energy_from( + ctx.attacker, 2, minimum=0, + prompt="Choose up to 2 Energy to discard") + await ctx.deal_damage(10 + 120 * len(discarded)) + card = PokemonCardDef( guid="87d80a13-22cb-557a-8177-b6a91bdd2242", @@ -25,7 +38,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=10, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_bench("opponent"), 30, base=10), ), Attack( title="Max Lance", @@ -33,7 +46,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 2}, damage=10, damage_operator="+", - effect=unimplemented, + effect=max_lance, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/IceRiderCalyrexV_163.py b/spirit/game/scripts/cards/SWSH6/IceRiderCalyrexV_163.py index 4bdf87f..3d8e64d 100644 --- a/spirit/game/scripts/cards/SWSH6/IceRiderCalyrexV_163.py +++ b/spirit/game/scripts/cards/SWSH6/IceRiderCalyrexV_163.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import self_energy_discard_attack card = PokemonCardDef( guid="a09fdd63-d697-5635-b88b-9fd40b36eb30", @@ -28,7 +29,7 @@ card = PokemonCardDef( game_text="Discard 2 Energy from this Pok\u00e9mon.", cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, damage=200, - effect=unimplemented, + effect=self_energy_discard_attack(count=2), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/IceRiderCalyrexV_164.py b/spirit/game/scripts/cards/SWSH6/IceRiderCalyrexV_164.py index 969afbe..af90ec8 100644 --- a/spirit/game/scripts/cards/SWSH6/IceRiderCalyrexV_164.py +++ b/spirit/game/scripts/cards/SWSH6/IceRiderCalyrexV_164.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import self_energy_discard_attack card = PokemonCardDef( guid="4aa4a917-b0d2-57bc-a397-69837cca8e5f", @@ -28,7 +29,7 @@ card = PokemonCardDef( game_text="Discard 2 Energy from this Pok\u00e9mon.", cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, damage=200, - effect=unimplemented, + effect=self_energy_discard_attack(count=2), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/IceRiderCalyrexV_45.py b/spirit/game/scripts/cards/SWSH6/IceRiderCalyrexV_45.py index 2d75738..1a68440 100644 --- a/spirit/game/scripts/cards/SWSH6/IceRiderCalyrexV_45.py +++ b/spirit/game/scripts/cards/SWSH6/IceRiderCalyrexV_45.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import self_energy_discard_attack card = PokemonCardDef( guid="a3e293a3-d82d-5cb8-a193-94e749a8b9f3", @@ -28,7 +29,7 @@ card = PokemonCardDef( game_text="Discard 2 Energy from this Pok\u00e9mon.", cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, damage=200, - effect=unimplemented, + effect=self_energy_discard_attack(count=2), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Inteleon_43.py b/spirit/game/scripts/cards/SWSH6/Inteleon_43.py index 95b64ae..2dab2e2 100644 --- a/spirit/game/scripts/cards/SWSH6/Inteleon_43.py +++ b/spirit/game/scripts/cards/SWSH6/Inteleon_43.py @@ -1,6 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def quick_shooting(ctx): + """Once per turn: you may put 2 damage counters on 1 of your opponent's Pokemon.""" + if not await ctx.ask_yes_no("Put 2 damage counters on 1 of your opponent's Pokémon?"): + return + targets = ctx.opponent_pokemon_in_play() + if not targets: + return + target = await ctx.choose_pokemon(targets, "Choose 1 of your opponent's Pokémon") + if target is not None: + await ctx.deal_damage(20, target=target, apply_modifiers=False, as_counters=True) + + card = PokemonCardDef( guid="0b78ab70-a9d8-55b0-b36f-f25ef5471dbf", key="SWSH6", @@ -21,8 +34,9 @@ card = PokemonCardDef( abilities=[ Ability( title="Quick Shooting", - game_text="Once during your turn, you may put 2 damage counters on 1 of your opponent's Pok\u00e9mon.", - effect=unimplemented, + game_text="Once during your turn, you may put 2 damage counters on 1 of your opponent's Pokémon.", + activation=Activations.ONCE_PER_TURN, + effect=quick_shooting, ), Attack( title="Waterfall", @@ -30,4 +44,4 @@ card = PokemonCardDef( damage=70, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH6/JustifiedGloves_143.py b/spirit/game/scripts/cards/SWSH6/JustifiedGloves_143.py index 3de0480..3740cbc 100644 --- a/spirit/game/scripts/cards/SWSH6/JustifiedGloves_143.py +++ b/spirit/game/scripts/cards/SWSH6/JustifiedGloves_143.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.card_effects.passives_common import typed_damage_boost_tool +from spirit.game.data_utils import PokemonToolCardDef +from spirit.game.attributes import PokemonTypes, Rarities card = PokemonToolCardDef( guid="37b01399-320a-5640-a850-22dd6a87ae6f", @@ -11,5 +12,5 @@ card = PokemonToolCardDef( collector_number=143, set_code="SWSH6", rarity=Rarities.Uncommon, - effect=unimplemented + passive=typed_damage_boost_tool(PokemonTypes.DARKNESS, 30) ) diff --git a/spirit/game/scripts/cards/SWSH6/Kakuna_2.py b/spirit/game/scripts/cards/SWSH6/Kakuna_2.py index badbb43..caba8ed 100644 --- a/spirit/game/scripts/cards/SWSH6/Kakuna_2.py +++ b/spirit/game/scripts/cards/SWSH6/Kakuna_2.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import protect_next_turn card = PokemonCardDef( guid="fde00e58-c69a-51a2-8cf3-7ad2585308fe", @@ -23,7 +24,7 @@ card = PokemonCardDef( title="Stiffen", game_text="During your opponent's next turn, this Pok\u00e9mon takes 40 less damage from attacks (after applying Weakness and Resistance).", cost={PokemonTypes.GRASS: 1}, - effect=unimplemented, + effect=protect_next_turn(reduce=40), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/KarensConviction_144.py b/spirit/game/scripts/cards/SWSH6/KarensConviction_144.py index 3a04b4d..6f9256a 100644 --- a/spirit/game/scripts/cards/SWSH6/KarensConviction_144.py +++ b/spirit/game/scripts/cards/SWSH6/KarensConviction_144.py @@ -1,5 +1,24 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef, subtypes_for from spirit.game.attributes import Rarities +from spirit.game.session.passives import TurnDamageModifier + + +async def karens_conviction(ctx): + """This turn, your Single Strike Pokemon's attacks do 20 more damage to + the opponent's Active Pokemon for each Prize the opponent has taken.""" + taken = ctx.prizes_taken(ctx.opponent_id) + if taken <= 0: + return + amount = 20 * taken + ctx.add_turn_damage_modifier( + TurnDamageModifier(amount, ctx.player_id, requires_subtype="Single Strike") + ) + for pokemon in ctx.my_pokemon_in_play(): + if "Single Strike" in subtypes_for(pokemon.archetype_id): + await ctx.add_stat_visualization( + pokemon, "Positive", "DamageDealtIncreased", + card_text=f"+{amount} damage") + card = SupporterCardDef( guid="2bd1b1d0-660b-53b4-8168-308b34b8a82e", @@ -11,5 +30,5 @@ card = SupporterCardDef( collector_number=144, set_code="SWSH6", rarity=Rarities.Uncommon, - effect=unimplemented + effect=karens_conviction ) diff --git a/spirit/game/scripts/cards/SWSH6/KarensConviction_193.py b/spirit/game/scripts/cards/SWSH6/KarensConviction_193.py index 3354af8..93e2caa 100644 --- a/spirit/game/scripts/cards/SWSH6/KarensConviction_193.py +++ b/spirit/game/scripts/cards/SWSH6/KarensConviction_193.py @@ -1,5 +1,24 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef, subtypes_for from spirit.game.attributes import Rarities +from spirit.game.session.passives import TurnDamageModifier + + +async def karens_conviction(ctx): + """This turn, your Single Strike Pokemon's attacks do 20 more damage to + the opponent's Active Pokemon for each Prize the opponent has taken.""" + taken = ctx.prizes_taken(ctx.opponent_id) + if taken <= 0: + return + amount = 20 * taken + ctx.add_turn_damage_modifier( + TurnDamageModifier(amount, ctx.player_id, requires_subtype="Single Strike") + ) + for pokemon in ctx.my_pokemon_in_play(): + if "Single Strike" in subtypes_for(pokemon.archetype_id): + await ctx.add_stat_visualization( + pokemon, "Positive", "DamageDealtIncreased", + card_text=f"+{amount} damage") + card = SupporterCardDef( guid="c0b99030-0bec-51c0-b8de-d205bc7ae614", @@ -11,5 +30,5 @@ card = SupporterCardDef( collector_number=193, set_code="SWSH6", rarity=Rarities.RareUltra, - effect=unimplemented + effect=karens_conviction ) diff --git a/spirit/game/scripts/cards/SWSH6/KarensConviction_216.py b/spirit/game/scripts/cards/SWSH6/KarensConviction_216.py index ba9e138..5b91c48 100644 --- a/spirit/game/scripts/cards/SWSH6/KarensConviction_216.py +++ b/spirit/game/scripts/cards/SWSH6/KarensConviction_216.py @@ -1,5 +1,24 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef, subtypes_for from spirit.game.attributes import Rarities +from spirit.game.session.passives import TurnDamageModifier + + +async def karens_conviction(ctx): + """This turn, your Single Strike Pokemon's attacks do 20 more damage to + the opponent's Active Pokemon for each Prize the opponent has taken.""" + taken = ctx.prizes_taken(ctx.opponent_id) + if taken <= 0: + return + amount = 20 * taken + ctx.add_turn_damage_modifier( + TurnDamageModifier(amount, ctx.player_id, requires_subtype="Single Strike") + ) + for pokemon in ctx.my_pokemon_in_play(): + if "Single Strike" in subtypes_for(pokemon.archetype_id): + await ctx.add_stat_visualization( + pokemon, "Positive", "DamageDealtIncreased", + card_text=f"+{amount} damage") + card = SupporterCardDef( guid="2edb574d-6074-587f-a68a-94fb1923285f", @@ -11,5 +30,5 @@ card = SupporterCardDef( collector_number=216, set_code="SWSH6", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=karens_conviction ) diff --git a/spirit/game/scripts/cards/SWSH6/Kecleon_122.py b/spirit/game/scripts/cards/SWSH6/Kecleon_122.py index 3350149..999d46d 100644 --- a/spirit/game/scripts/cards/SWSH6/Kecleon_122.py +++ b/spirit/game/scripts/cards/SWSH6/Kecleon_122.py @@ -1,5 +1,23 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.session.passives import Passive + + +class ChromashiftPassive(Passive): + """The holder's live types become its attached basic Energies' types.""" + + def modify_pokemon_types(self, types, pokemon, carrier): + if pokemon is not carrier: + return types + energy_types = [] + for child in carrier.children: + if not is_basic_energy_card(child): + continue + for t in child.get_attribute(AttrID.POKEMON_TYPES) or []: + if t not in energy_types: + energy_types.append(t) + return energy_types or types card = PokemonCardDef( guid="743c4a25-d2e0-553f-882b-a16401198f9b", @@ -21,7 +39,7 @@ card = PokemonCardDef( Ability( title="Chromashift", game_text="This Pok\u00e9mon is the same type as any basic Energy attached to it. (If it has 2 or more different types of basic Energy attached, this Pok\u00e9mon is each of those types.)", - effect=unimplemented, + passive=ChromashiftPassive(), ), Attack( title="Spinning Attack", diff --git a/spirit/game/scripts/cards/SWSH6/Kirlia_60.py b/spirit/game/scripts/cards/SWSH6/Kirlia_60.py index ef18126..8a8bcfd 100644 --- a/spirit/game/scripts/cards/SWSH6/Kirlia_60.py +++ b/spirit/game/scripts/cards/SWSH6/Kirlia_60.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.support_common import search_to_bench card = PokemonCardDef( guid="5a7d0956-85c9-5fa2-9a98-aa110f18eba8", @@ -23,7 +24,11 @@ card = PokemonCardDef( title="Mirage Step", game_text="Search your deck for up to 3 Kirlia and put them onto your Bench. Then, shuffle your deck.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=search_to_bench( + predicate=lambda c: c.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) == "Kirlia", + count=3, + prompt="Choose up to 3 Kirlia to put onto your Bench.", + ), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Klara_194.py b/spirit/game/scripts/cards/SWSH6/Klara_194.py index 479f1eb..4ec94cd 100644 --- a/spirit/game/scripts/cards/SWSH6/Klara_194.py +++ b/spirit/game/scripts/cards/SWSH6/Klara_194.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import klara card = SupporterCardDef( guid="f37c5911-13a8-562f-90be-0b3633a6cbff", @@ -11,5 +12,5 @@ card = SupporterCardDef( collector_number=194, set_code="SWSH6", rarity=Rarities.RareUltra, - effect=unimplemented + effect=klara ) diff --git a/spirit/game/scripts/cards/SWSH6/Klara_217.py b/spirit/game/scripts/cards/SWSH6/Klara_217.py index 0cff569..7ca9d61 100644 --- a/spirit/game/scripts/cards/SWSH6/Klara_217.py +++ b/spirit/game/scripts/cards/SWSH6/Klara_217.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import klara card = SupporterCardDef( guid="fa417a1f-87bd-5997-aad1-a0a4adb79e8d", @@ -11,5 +12,5 @@ card = SupporterCardDef( collector_number=217, set_code="SWSH6", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=klara ) diff --git a/spirit/game/scripts/cards/SWSH6/Koffing_94.py b/spirit/game/scripts/cards/SWSH6/Koffing_94.py index 9ccfae6..4f01c92 100644 --- a/spirit/game/scripts/cards/SWSH6/Koffing_94.py +++ b/spirit/game/scripts/cards/SWSH6/Koffing_94.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="5bd23171-dfaa-5f30-96a2-9e7388b67f7f", @@ -20,10 +21,10 @@ card = PokemonCardDef( abilities=[ Attack( title="Smog", - game_text="Your opponent's Active Pok\u00e9mon is now Poisoned.", + game_text="Your opponent's Active Pokémon is now Poisoned.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 1}, damage=20, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH6/Kubfu_93.py b/spirit/game/scripts/cards/SWSH6/Kubfu_93.py index 80581cf..8c20a7f 100644 --- a/spirit/game/scripts/cards/SWSH6/Kubfu_93.py +++ b/spirit/game/scripts/cards/SWSH6/Kubfu_93.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.card_effects.support_common import search_attach_energy card = PokemonCardDef( guid="6e752614-cfb1-5199-bc31-20cc4833fb06", @@ -22,7 +24,7 @@ card = PokemonCardDef( title="Training", game_text="Search your deck for a basic Energy card and attach it to this Pok\u00e9mon. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_attach_energy(predicate=is_basic_energy_card, count=1, to_self=True), ), Attack( title="Elbow Strike", diff --git a/spirit/game/scripts/cards/SWSH6/Lapras_29.py b/spirit/game/scripts/cards/SWSH6/Lapras_29.py index e98bf0b..926e24d 100644 --- a/spirit/game/scripts/cards/SWSH6/Lapras_29.py +++ b/spirit/game/scripts/cards/SWSH6/Lapras_29.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, def_for +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.support_common import search_to_hand + + +def _is_melony(card): + d = def_for(card.archetype_id) + return bool(d) and d.display_name == "Melony" + card = PokemonCardDef( guid="48b6fafd-22c5-55b4-b36d-732bd1e49ec7", @@ -22,14 +30,17 @@ card = PokemonCardDef( title="Wintry Call", game_text="Search your deck for up to 2 Melony cards, reveal them, and put them into your hand. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_to_hand( + _is_melony, count=2, minimum=0, reveal=True, + prompt="Choose up to 2 Melony cards to put into your hand.", + ), ), Attack( title="Icy Wind", - game_text="Your opponent's Active Pok\u00e9mon is now Asleep.", + game_text="Your opponent's Active Pokémon is now Asleep.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 1}, damage=50, - effect=unimplemented, + effect=condition_attack(SpecialConditions.ASLEEP), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH6/Larvesta_23.py b/spirit/game/scripts/cards/SWSH6/Larvesta_23.py index 61cc3bb..4fc9a19 100644 --- a/spirit/game/scripts/cards/SWSH6/Larvesta_23.py +++ b/spirit/game/scripts/cards/SWSH6/Larvesta_23.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.trainers import is_energy_card +from spirit.game.card_effects.support_common import search_attach_energy + + +def _is_fire_energy(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_energy_card(card) and PokemonTypes.FIRE.value in types + card = PokemonCardDef( guid="40ab710c-5198-5847-a045-a8499a3a0597", @@ -23,7 +31,7 @@ card = PokemonCardDef( game_text="Search your deck for a Fire Energy card and attach it to this Pok\u00e9mon. Then, shuffle your deck.", cost={PokemonTypes.FIRE: 1}, damage=10, - effect=unimplemented, + effect=search_attach_energy(predicate=_is_fire_energy, count=1, to_self=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Ledian_5.py b/spirit/game/scripts/cards/SWSH6/Ledian_5.py index d06f999..47ffe32 100644 --- a/spirit/game/scripts/cards/SWSH6/Ledian_5.py +++ b/spirit/game/scripts/cards/SWSH6/Ledian_5.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import draw_attack +from spirit.game.card_effects.attacks_common import self_energy_discard_attack card = PokemonCardDef( guid="94c94758-e81f-5df7-988a-63008ad2db4f", @@ -24,14 +26,14 @@ card = PokemonCardDef( game_text="Draw 2 cards.", cost={PokemonTypes.COLORLESS: 1}, damage=20, - effect=unimplemented, + effect=draw_attack(2), ), Attack( title="Air Slash", game_text="Discard an Energy from this Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 3}, damage=100, - effect=unimplemented, + effect=self_energy_discard_attack(count=1), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Ledyba_4.py b/spirit/game/scripts/cards/SWSH6/Ledyba_4.py index 600bef1..77af3a6 100644 --- a/spirit/game/scripts/cards/SWSH6/Ledyba_4.py +++ b/spirit/game/scripts/cards/SWSH6/Ledyba_4.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import draw_attack card = PokemonCardDef( guid="c1063f2a-32b2-5639-aa8d-5f3c5fc4d41c", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Collect", game_text="Draw a card.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=draw_attack(1), ), Attack( title="Punch", diff --git a/spirit/game/scripts/cards/SWSH6/LiepardV_104.py b/spirit/game/scripts/cards/SWSH6/LiepardV_104.py index f43357f..d3d3e46 100644 --- a/spirit/game/scripts/cards/SWSH6/LiepardV_104.py +++ b/spirit/game/scripts/cards/SWSH6/LiepardV_104.py @@ -1,5 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import remove_self_from_play + + +async def hidden_claw(ctx): + """On play from hand to bench: you may discard a Pokémon Tool from a Pokémon (either side).""" + tools = [(t, p) for t, p in ctx.tools_in_play() + if p.owning_player_id == ctx.player_id or not ctx.effects_blocked(p)] + if not tools: + return + if not await ctx.ask_yes_no("Discard a Pokémon Tool from a Pokémon?"): + return + tool_cards = [t for t, p in tools] + picks = await ctx.choose_cards(tool_cards, 1, prompt="Choose a Pokémon Tool to discard") + await ctx.discard_cards(picks) + card = PokemonCardDef( guid="b1d03ddd-7a00-5126-af3a-e23cec56bb8b", @@ -21,14 +36,15 @@ card = PokemonCardDef( Ability( title="Hidden Claw", game_text="When you play this Pok\u00e9mon from your hand onto your Bench during your turn, you may discard a Pok\u00e9mon Tool from a Pok\u00e9mon (yours or your opponent's).", - effect=unimplemented, + trigger=Triggers.ON_PLAY, + effect=hidden_claw, ), Attack( title="Shadow Ripper", game_text="You may put this Pok\u00e9mon and all attached cards into your hand.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 2}, damage=110, - effect=unimplemented, + effect=remove_self_from_play(destination="hand", with_attachments="same", optional=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/LiepardV_180.py b/spirit/game/scripts/cards/SWSH6/LiepardV_180.py index 89b8190..294101e 100644 --- a/spirit/game/scripts/cards/SWSH6/LiepardV_180.py +++ b/spirit/game/scripts/cards/SWSH6/LiepardV_180.py @@ -1,5 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import remove_self_from_play + + +async def hidden_claw(ctx): + """On play from hand to bench: you may discard a Pokémon Tool from a Pokémon (either side).""" + tools = [(t, p) for t, p in ctx.tools_in_play() + if p.owning_player_id == ctx.player_id or not ctx.effects_blocked(p)] + if not tools: + return + if not await ctx.ask_yes_no("Discard a Pokémon Tool from a Pokémon?"): + return + tool_cards = [t for t, p in tools] + picks = await ctx.choose_cards(tool_cards, 1, prompt="Choose a Pokémon Tool to discard") + await ctx.discard_cards(picks) + card = PokemonCardDef( guid="320309c8-237c-5a29-8572-cc01d205a5ae", @@ -21,14 +36,15 @@ card = PokemonCardDef( Ability( title="Hidden Claw", game_text="When you play this Pok\u00e9mon from your hand onto your Bench during your turn, you may discard a Pok\u00e9mon Tool from a Pok\u00e9mon (yours or your opponent's).", - effect=unimplemented, + trigger=Triggers.ON_PLAY, + effect=hidden_claw, ), Attack( title="Shadow Ripper", game_text="You may put this Pok\u00e9mon and all attached cards into your hand.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 2}, damage=110, - effect=unimplemented, + effect=remove_self_from_play(destination="hand", with_attachments="same", optional=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Lycanroc_87.py b/spirit/game/scripts/cards/SWSH6/Lycanroc_87.py index 96ed37f..99a0344 100644 --- a/spirit/game/scripts/cards/SWSH6/Lycanroc_87.py +++ b/spirit/game/scripts/cards/SWSH6/Lycanroc_87.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, subtypes_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_discard + + +def _is_single_strike(card) -> bool: + return "Single Strike" in subtypes_for(card.archetype_id) + card = PokemonCardDef( guid="ae03ce97-984b-53be-9073-c92cb23cb52b", @@ -25,7 +31,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 2}, damage=80, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_discard("mine", _is_single_strike), 10, base=80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Malamar_70.py b/spirit/game/scripts/cards/SWSH6/Malamar_70.py index 96ec52f..2eb207a 100644 --- a/spirit/game/scripts/cards/SWSH6/Malamar_70.py +++ b/spirit/game/scripts/cards/SWSH6/Malamar_70.py @@ -1,6 +1,25 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, subtypes_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def rapid_strike_tentacles(ctx): + """Reveal any number of Rapid Strike cards from your hand. This attack + does 40 damage for each card revealed in this way. Then, shuffle those + cards into your deck.""" + candidates = [c for c in ctx.hand() if "Rapid Strike" in subtypes_for(c.archetype_id)] + picks = [] + if candidates: + picks = await ctx.choose_cards( + candidates, len(candidates), minimum=0, + prompt="Reveal any number of Rapid Strike cards from your hand.", + ) + if picks: + await ctx.reveal_cards(picks) + await ctx.deal_damage(40 * len(picks)) + if picks: + await ctx.shuffle_into_deck(picks) + + card = PokemonCardDef( guid="6f0653d1-4ca3-514d-9e74-0e156861997d", key="SWSH6", @@ -26,7 +45,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 1}, damage=40, damage_operator="x", - effect=unimplemented, + effect=rapid_strike_tentacles, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Mareep_47.py b/spirit/game/scripts/cards/SWSH6/Mareep_47.py index 82c3939..8bd440e 100644 --- a/spirit/game/scripts/cards/SWSH6/Mareep_47.py +++ b/spirit/game/scripts/cards/SWSH6/Mareep_47.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import debuff_defender_attacks card = PokemonCardDef( guid="6b2b4b30-dd68-5c38-8d0f-5a0e990b2551", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Growl", game_text="During your opponent's next turn, the Defending Pok\u00e9mon's attacks do 20 less damage (before applying Weakness and Resistance).", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=debuff_defender_attacks(20), ), Attack( title="Static Shock", diff --git a/spirit/game/scripts/cards/SWSH6/Melony_146.py b/spirit/game/scripts/cards/SWSH6/Melony_146.py index 5d3203c..0358b4d 100644 --- a/spirit/game/scripts/cards/SWSH6/Melony_146.py +++ b/spirit/game/scripts/cards/SWSH6/Melony_146.py @@ -1,5 +1,34 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import SupporterCardDef, is_pokemon_v +from spirit.game.attributes import Rarities, PokemonTypes +from spirit.game.card_effects.pokemon import energy_provides_type +from spirit.game.card_effects.support_common import ( + attach_from_discard, requires_discard, requires_in_play, +) + + +def _is_water_energy(card): + return energy_provides_type(card, PokemonTypes.WATER.value) + + +def _is_v_pokemon(pokemon): + return is_pokemon_v(pokemon.archetype_id) + + +def _melony_condition(board, player_id): + return ( + requires_discard(_is_water_energy)(board, player_id) + and requires_in_play(_is_v_pokemon, side="mine")(board, player_id) + ) + + +async def _melony_draw(ctx, picks): + await ctx.draw_cards(3) + + +melony = attach_from_discard( + predicate=_is_water_energy, target=_is_v_pokemon, then=_melony_draw, + prompt="Choose a Water Energy card to attach.", +) card = SupporterCardDef( guid="86bff6ea-d6c6-5f67-b0f8-539b3c5223e6", @@ -11,5 +40,6 @@ card = SupporterCardDef( collector_number=146, set_code="SWSH6", rarity=Rarities.Uncommon, - effect=unimplemented + effect=melony, + condition=_melony_condition, ) diff --git a/spirit/game/scripts/cards/SWSH6/Melony_195.py b/spirit/game/scripts/cards/SWSH6/Melony_195.py index 3c9d365..b2bc7f7 100644 --- a/spirit/game/scripts/cards/SWSH6/Melony_195.py +++ b/spirit/game/scripts/cards/SWSH6/Melony_195.py @@ -1,5 +1,34 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import SupporterCardDef, is_pokemon_v +from spirit.game.attributes import Rarities, PokemonTypes +from spirit.game.card_effects.pokemon import energy_provides_type +from spirit.game.card_effects.support_common import ( + attach_from_discard, requires_discard, requires_in_play, +) + + +def _is_water_energy(card): + return energy_provides_type(card, PokemonTypes.WATER.value) + + +def _is_v_pokemon(pokemon): + return is_pokemon_v(pokemon.archetype_id) + + +def _melony_condition(board, player_id): + return ( + requires_discard(_is_water_energy)(board, player_id) + and requires_in_play(_is_v_pokemon, side="mine")(board, player_id) + ) + + +async def _melony_draw(ctx, picks): + await ctx.draw_cards(3) + + +melony = attach_from_discard( + predicate=_is_water_energy, target=_is_v_pokemon, then=_melony_draw, + prompt="Choose a Water Energy card to attach.", +) card = SupporterCardDef( guid="852780aa-fa04-55ef-b61d-33121e83390d", @@ -11,5 +40,6 @@ card = SupporterCardDef( collector_number=195, set_code="SWSH6", rarity=Rarities.RareUltra, - effect=unimplemented + effect=melony, + condition=_melony_condition, ) diff --git a/spirit/game/scripts/cards/SWSH6/Melony_218.py b/spirit/game/scripts/cards/SWSH6/Melony_218.py index 795eeb9..d8b5a50 100644 --- a/spirit/game/scripts/cards/SWSH6/Melony_218.py +++ b/spirit/game/scripts/cards/SWSH6/Melony_218.py @@ -1,5 +1,34 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import SupporterCardDef, is_pokemon_v +from spirit.game.attributes import Rarities, PokemonTypes +from spirit.game.card_effects.pokemon import energy_provides_type +from spirit.game.card_effects.support_common import ( + attach_from_discard, requires_discard, requires_in_play, +) + + +def _is_water_energy(card): + return energy_provides_type(card, PokemonTypes.WATER.value) + + +def _is_v_pokemon(pokemon): + return is_pokemon_v(pokemon.archetype_id) + + +def _melony_condition(board, player_id): + return ( + requires_discard(_is_water_energy)(board, player_id) + and requires_in_play(_is_v_pokemon, side="mine")(board, player_id) + ) + + +async def _melony_draw(ctx, picks): + await ctx.draw_cards(3) + + +melony = attach_from_discard( + predicate=_is_water_energy, target=_is_v_pokemon, then=_melony_draw, + prompt="Choose a Water Energy card to attach.", +) card = SupporterCardDef( guid="b7b8484f-fdcf-5889-8798-7a8dc353f64e", @@ -11,5 +40,6 @@ card = SupporterCardDef( collector_number=218, set_code="SWSH6", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=melony, + condition=_melony_condition, ) diff --git a/spirit/game/scripts/cards/SWSH6/MetagrossVMAX_113.py b/spirit/game/scripts/cards/SWSH6/MetagrossVMAX_113.py index e5ce930..751a0d9 100644 --- a/spirit/game/scripts/cards/SWSH6/MetagrossVMAX_113.py +++ b/spirit/game/scripts/cards/SWSH6/MetagrossVMAX_113.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.card_effects.passives_common import boost_own_next_turn card = PokemonCardDef( guid="7cf01536-fbc5-54ed-af1f-6872961eb578", @@ -24,14 +26,17 @@ card = PokemonCardDef( title="Zap Traction", game_text="Search your deck for up to 2 cards and put them into your hand. Then, shuffle your deck.", cost={PokemonTypes.METAL: 1}, - effect=unimplemented, + effect=search_to_hand( + count=2, minimum=0, reveal=False, + prompt="Choose up to 2 cards to put into your hand.", + ), ), Attack( title="Max Rush", game_text="During your next turn, this Pok\u00e9mon's Max Rush attack does 150 more damage.", cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 1}, damage=100, - effect=unimplemented, + effect=boost_own_next_turn(150, attack_title="Max Rush"), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/MetagrossVMAX_208.py b/spirit/game/scripts/cards/SWSH6/MetagrossVMAX_208.py index 531370d..313f9f2 100644 --- a/spirit/game/scripts/cards/SWSH6/MetagrossVMAX_208.py +++ b/spirit/game/scripts/cards/SWSH6/MetagrossVMAX_208.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.card_effects.passives_common import boost_own_next_turn card = PokemonCardDef( guid="fe27c198-6553-5da7-8beb-7b82d2391035", @@ -24,14 +26,17 @@ card = PokemonCardDef( title="Zap Traction", game_text="Search your deck for up to 2 cards and put them into your hand. Then, shuffle your deck.", cost={PokemonTypes.METAL: 1}, - effect=unimplemented, + effect=search_to_hand( + count=2, minimum=0, reveal=False, + prompt="Choose up to 2 cards to put into your hand.", + ), ), Attack( title="Max Rush", game_text="During your next turn, this Pok\u00e9mon's Max Rush attack does 150 more damage.", cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 1}, damage=100, - effect=unimplemented, + effect=boost_own_next_turn(150, attack_title="Max Rush"), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/MetagrossV_112.py b/spirit/game/scripts/cards/SWSH6/MetagrossV_112.py index fbef7a9..35ffa45 100644 --- a/spirit/game/scripts/cards/SWSH6/MetagrossV_112.py +++ b/spirit/game/scripts/cards/SWSH6/MetagrossV_112.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage, bonus_if, count_energy card = PokemonCardDef( guid="afcc4e86-e3fe-55fb-9aed-e531912c1b91", @@ -25,7 +26,7 @@ card = PokemonCardDef( cost={PokemonTypes.METAL: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=flip_damage(coins=2, bonus_per_heads=20), ), Attack( title="Synchro Hammer", @@ -33,7 +34,10 @@ card = PokemonCardDef( cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 1}, damage=60, damage_operator="+", - effect=unimplemented, + effect=bonus_if( + lambda ctx: count_energy("self")(ctx) == count_energy("defender")(ctx), + 90, + ), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/MetagrossV_181.py b/spirit/game/scripts/cards/SWSH6/MetagrossV_181.py index 5562043..d1183f4 100644 --- a/spirit/game/scripts/cards/SWSH6/MetagrossV_181.py +++ b/spirit/game/scripts/cards/SWSH6/MetagrossV_181.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage, bonus_if, count_energy card = PokemonCardDef( guid="6f965d44-d7c5-5ddb-88f0-864be98052cf", @@ -25,7 +26,7 @@ card = PokemonCardDef( cost={PokemonTypes.METAL: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=flip_damage(coins=2, bonus_per_heads=20), ), Attack( title="Synchro Hammer", @@ -33,7 +34,10 @@ card = PokemonCardDef( cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 1}, damage=60, damage_operator="+", - effect=unimplemented, + effect=bonus_if( + lambda ctx: count_energy("self")(ctx) == count_energy("defender")(ctx), + 90, + ), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/OldCemetery_147.py b/spirit/game/scripts/cards/SWSH6/OldCemetery_147.py index e56f03c..d62d800 100644 --- a/spirit/game/scripts/cards/SWSH6/OldCemetery_147.py +++ b/spirit/game/scripts/cards/SWSH6/OldCemetery_147.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import StadiumCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import StadiumCardDef, Ability, Triggers +from spirit.game.attributes import Rarities, AttrID, PokemonTypes + + +async def old_cemetery_watch(ctx): + """2 damage counters on any non-Psychic Pokemon an Energy was just + manually attached to (either player).""" + receiver = ctx.energy_receiver + if receiver is None: + return + types = receiver.get_attribute(AttrID.POKEMON_TYPES) or [] + if PokemonTypes.PSYCHIC.value in types: + return + await ctx.deal_damage(20, target=receiver, apply_modifiers=False, + as_counters=True) + card = StadiumCardDef( guid="cfa7ff8a-9c7d-5f4e-9ebb-d35870f76f39", @@ -11,5 +25,12 @@ card = StadiumCardDef( collector_number=147, set_code="SWSH6", rarity=Rarities.Uncommon, - effect=unimplemented + abilities=[ + Ability( + title="Old Cemetery", + game_text="Whenever any player attaches an Energy card from their hand to 1 of their non-Psychic Pokémon, put 2 damage counters on that Pokémon.", + trigger=Triggers.ON_ENERGY_ATTACHED, + effect=old_cemetery_watch, + ), + ], ) diff --git a/spirit/game/scripts/cards/SWSH6/Passimian_88.py b/spirit/game/scripts/cards/SWSH6/Passimian_88.py index bdc0bb0..d5be74b 100644 --- a/spirit/game/scripts/cards/SWSH6/Passimian_88.py +++ b/spirit/game/scripts/cards/SWSH6/Passimian_88.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, subtypes_for, is_pokemon_v from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import is_in_active_spot, team_damage_boost_passive +from spirit.game.card_effects.pokemon import is_pokemon_gx +from spirit.game.card_effects.attacks_common import snipe_attack + + +def _is_rapid_strike(pokemon): + return "Rapid Strike" in subtypes_for(pokemon.archetype_id) + + +def _throwing_coach_target(target): + if is_in_active_spot(target): + return False + return is_pokemon_v(target.archetype_id) or is_pokemon_gx(target.archetype_id) + card = PokemonCardDef( guid="c95b8198-79d9-5bfe-a39e-d15512be5632", @@ -21,13 +35,17 @@ card = PokemonCardDef( Ability( title="Throwing Coach", game_text="Your Rapid Strike Pok\u00e9mon's attacks do 30 more damage to your opponent's Benched Pok\u00e9mon V and Benched Pok\u00e9mon-GX (before applying Weakness and Resistance). You can't apply more than 1 Throwing Coach Ability at a time.", - effect=unimplemented, + passive=team_damage_boost_passive( + 30, attacker_pred=_is_rapid_strike, target_pred=_throwing_coach_target, + once_key="ThrowingCoach", to_active_only=False, + ), ), Attack( title="Fling", game_text="This attack does 20 damage to 1 of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.FIGHTING: 1}, - effect=unimplemented, + damage=20, + effect=snipe_attack(20, pool="any", count=1), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Peonia_149.py b/spirit/game/scripts/cards/SWSH6/Peonia_149.py index 80e8867..215ac3e 100644 --- a/spirit/game/scripts/cards/SWSH6/Peonia_149.py +++ b/spirit/game/scripts/cards/SWSH6/Peonia_149.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import peonia, peonia_playable card = SupporterCardDef( guid="fb09775c-8d51-5c86-a1a9-3548f5c2ed16", @@ -11,5 +12,6 @@ card = SupporterCardDef( collector_number=149, set_code="SWSH6", rarity=Rarities.Uncommon, - effect=unimplemented + condition=peonia_playable, + effect=peonia, ) diff --git a/spirit/game/scripts/cards/SWSH6/Peonia_196.py b/spirit/game/scripts/cards/SWSH6/Peonia_196.py index 5632b77..18dc210 100644 --- a/spirit/game/scripts/cards/SWSH6/Peonia_196.py +++ b/spirit/game/scripts/cards/SWSH6/Peonia_196.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import peonia, peonia_playable card = SupporterCardDef( guid="174156d7-f142-57b2-980a-90f2f9b91aac", @@ -11,5 +12,6 @@ card = SupporterCardDef( collector_number=196, set_code="SWSH6", rarity=Rarities.RareUltra, - effect=unimplemented + condition=peonia_playable, + effect=peonia, ) diff --git a/spirit/game/scripts/cards/SWSH6/Peonia_219.py b/spirit/game/scripts/cards/SWSH6/Peonia_219.py index d5709bb..4aada02 100644 --- a/spirit/game/scripts/cards/SWSH6/Peonia_219.py +++ b/spirit/game/scripts/cards/SWSH6/Peonia_219.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import peonia, peonia_playable card = SupporterCardDef( guid="cbc997d3-3608-55f8-9cc0-e0517d7470d4", @@ -11,5 +12,6 @@ card = SupporterCardDef( collector_number=219, set_code="SWSH6", rarity=Rarities.RareRainbow, - effect=unimplemented + condition=peonia_playable, + effect=peonia, ) diff --git a/spirit/game/scripts/cards/SWSH6/Peony_150.py b/spirit/game/scripts/cards/SWSH6/Peony_150.py index 4601174..50ba444 100644 --- a/spirit/game/scripts/cards/SWSH6/Peony_150.py +++ b/spirit/game/scripts/cards/SWSH6/Peony_150.py @@ -1,5 +1,20 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.session.effects import is_trainer_card + + +async def peony(ctx): + """Discard your hand and search up to 2 Trainer cards, reveal, put into hand, then shuffle.""" + hand = ctx.hand() + if hand: + await ctx.discard_cards(hand) + picks = await ctx.search_deck( + is_trainer_card, count=2, minimum=0, + prompt="Choose up to 2 Trainer cards to put into your hand.", + ) + await ctx.put_in_hand(picks, reveal=True) + await ctx.shuffle_deck() + card = SupporterCardDef( guid="468ae19b-b9d0-5335-ada8-7b27389a2e08", @@ -11,5 +26,5 @@ card = SupporterCardDef( collector_number=150, set_code="SWSH6", rarity=Rarities.Uncommon, - effect=unimplemented + effect=peony ) diff --git a/spirit/game/scripts/cards/SWSH6/Peony_197.py b/spirit/game/scripts/cards/SWSH6/Peony_197.py index 3daebdf..c29235a 100644 --- a/spirit/game/scripts/cards/SWSH6/Peony_197.py +++ b/spirit/game/scripts/cards/SWSH6/Peony_197.py @@ -1,5 +1,20 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.session.effects import is_trainer_card + + +async def peony(ctx): + """Discard your hand and search up to 2 Trainer cards, reveal, put into hand, then shuffle.""" + hand = ctx.hand() + if hand: + await ctx.discard_cards(hand) + picks = await ctx.search_deck( + is_trainer_card, count=2, minimum=0, + prompt="Choose up to 2 Trainer cards to put into your hand.", + ) + await ctx.put_in_hand(picks, reveal=True) + await ctx.shuffle_deck() + card = SupporterCardDef( guid="dc697ed6-4006-53b5-a57a-b54c2fb16e9b", @@ -11,5 +26,5 @@ card = SupporterCardDef( collector_number=197, set_code="SWSH6", rarity=Rarities.RareUltra, - effect=unimplemented + effect=peony ) diff --git a/spirit/game/scripts/cards/SWSH6/Peony_220.py b/spirit/game/scripts/cards/SWSH6/Peony_220.py index 1761c40..9ff6bef 100644 --- a/spirit/game/scripts/cards/SWSH6/Peony_220.py +++ b/spirit/game/scripts/cards/SWSH6/Peony_220.py @@ -1,5 +1,20 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.session.effects import is_trainer_card + + +async def peony(ctx): + """Discard your hand and search up to 2 Trainer cards, reveal, put into hand, then shuffle.""" + hand = ctx.hand() + if hand: + await ctx.discard_cards(hand) + picks = await ctx.search_deck( + is_trainer_card, count=2, minimum=0, + prompt="Choose up to 2 Trainer cards to put into your hand.", + ) + await ctx.put_in_hand(picks, reveal=True) + await ctx.shuffle_deck() + card = SupporterCardDef( guid="2958c1b0-6fb6-5026-80e0-6d0593361419", @@ -11,5 +26,5 @@ card = SupporterCardDef( collector_number=220, set_code="SWSH6", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=peony ) diff --git a/spirit/game/scripts/cards/SWSH6/Porygon2_117.py b/spirit/game/scripts/cards/SWSH6/Porygon2_117.py index 92cadf7..b9bfbc5 100644 --- a/spirit/game/scripts/cards/SWSH6/Porygon2_117.py +++ b/spirit/game/scripts/cards/SWSH6/Porygon2_117.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="0edaa000-b9f2-5151-84af-ce858e6b8010", @@ -25,7 +26,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=30, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=3, per_heads=30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/PorygonZ_118.py b/spirit/game/scripts/cards/SWSH6/PorygonZ_118.py index 6a7e9ce..d187539 100644 --- a/spirit/game/scripts/cards/SWSH6/PorygonZ_118.py +++ b/spirit/game/scripts/cards/SWSH6/PorygonZ_118.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import self_energy_discard_attack + + +async def bug_transmission(ctx): + """Whenever you attach an Energy card from your hand to this Pokémon + during your turn, you may make your opponent's Active Pokémon Confused.""" + if ctx.attaching_player_id != ctx.player_id or ctx.energy_receiver is not ctx.source: + return + if await ctx.ask_yes_no("Make your opponent's Active Pokémon Confused?"): + await ctx.apply_special_condition(ctx.opponent_active(), SpecialConditions.CONFUSED) + card = PokemonCardDef( guid="6b4fde7b-7d08-5b05-ba8e-8ba7a5b8fd80", @@ -22,14 +33,15 @@ card = PokemonCardDef( Ability( title="Bug Transmission", game_text="Whenever you attach an Energy card from your hand to this Pok\u00e9mon during your turn, you may make your opponent's Active Pok\u00e9mon Confused.", - effect=unimplemented, + trigger=Triggers.ON_ENERGY_ATTACHED, + effect=bug_transmission, ), Attack( title="Superbeam", game_text="Discard 2 Energy from this Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 3}, damage=170, - effect=unimplemented, + effect=self_energy_discard_attack(count=2), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Qwilfish_101.py b/spirit/game/scripts/cards/SWSH6/Qwilfish_101.py index f286210..37a7d57 100644 --- a/spirit/game/scripts/cards/SWSH6/Qwilfish_101.py +++ b/spirit/game/scripts/cards/SWSH6/Qwilfish_101.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack + + +async def bursting_needles(ctx): + """Active Spot only: if Knocked Out by damage from an opponent's + attack, put 6 damage counters on the Attacking Pokemon.""" + if not ctx.ko_from_attack: + return + active_area = ctx.board.find_player_area(ctx.player_id, "activePokemonArea") + if active_area is not None and active_area.children: + return # was Benched, not Active, when Knocked Out + attacker = ctx.ko_attacker + if attacker is None: + return + await ctx.deal_damage(60, target=attacker, apply_modifiers=False, as_counters=True) + card = PokemonCardDef( guid="ccde68b7-8315-5808-9efb-b4ba23242fa8", @@ -21,14 +37,15 @@ card = PokemonCardDef( Ability( title="Bursting Needles", game_text="If this Pok\u00e9mon is in the Active Spot and is Knocked Out by damage from an attack from your opponent's Pok\u00e9mon, put 6 damage counters on the Attacking Pok\u00e9mon.", - effect=unimplemented, + trigger=Triggers.ON_KNOCKED_OUT, + effect=bursting_needles, ), Attack( title="Poison Jab", game_text="Your opponent's Active Pok\u00e9mon is now Poisoned.", cost={PokemonTypes.COLORLESS: 2}, damage=30, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Raboot_27.py b/spirit/game/scripts/cards/SWSH6/Raboot_27.py index 25239b8..1551f80 100644 --- a/spirit/game/scripts/cards/SWSH6/Raboot_27.py +++ b/spirit/game/scripts/cards/SWSH6/Raboot_27.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import lock_all_attacks + + +async def volley_kick(ctx): + """60 damage; during your next turn, this Pokémon can't attack.""" + await ctx.deal_damage() + lock_all_attacks(ctx, ctx.attacker) + card = PokemonCardDef( guid="97db56be-6d2e-5cbb-9c36-fff318ffb6d8", @@ -24,7 +32,7 @@ card = PokemonCardDef( game_text="During your next turn, this Pok\u00e9mon can't attack.", cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 1}, damage=60, - effect=unimplemented, + effect=volley_kick, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Ralts_59.py b/spirit/game/scripts/cards/SWSH6/Ralts_59.py index 68a0354..d7add82 100644 --- a/spirit/game/scripts/cards/SWSH6/Ralts_59.py +++ b/spirit/game/scripts/cards/SWSH6/Ralts_59.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="d84a88a4-fc49-58df-a343-b1e617461cb9", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Confuse Ray", game_text="Your opponent's Active Pok\u00e9mon is now Confused.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/RapidStrikeScrolloftheSkies_151.py b/spirit/game/scripts/cards/SWSH6/RapidStrikeScrolloftheSkies_151.py index 0955e16..12a9851 100644 --- a/spirit/game/scripts/cards/SWSH6/RapidStrikeScrolloftheSkies_151.py +++ b/spirit/game/scripts/cards/SWSH6/RapidStrikeScrolloftheSkies_151.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import PokemonToolCardDef, Attack, subtypes_for +from spirit.game.attributes import Rarities, PokemonTypes +from spirit.game.card_effects.attacks_common import damage_per, count_energy + + +def _is_rapid_strike(board, player_id, pokemon): + return "Rapid Strike" in subtypes_for(pokemon.archetype_id) + card = PokemonToolCardDef( guid="9ffc4f8e-975d-5a0c-829b-c2ffb5761ee1", @@ -11,5 +17,15 @@ card = PokemonToolCardDef( collector_number=151, set_code="SWSH6", rarity=Rarities.Uncommon, - effect=unimplemented + granted_abilities=[ + Attack( + title="Gravdrop", + game_text="This attack does 50 more damage for each Energy attached to your opponent's Active Pok\u00e9mon.", + cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 1}, + damage=10, + damage_operator="+", + effect=damage_per(count_energy("defender"), 50, base=10), + condition=_is_rapid_strike, + ), + ], ) diff --git a/spirit/game/scripts/cards/SWSH6/RapidStrikeUrshifu_44.py b/spirit/game/scripts/cards/SWSH6/RapidStrikeUrshifu_44.py index c7869a4..fddb5f5 100644 --- a/spirit/game/scripts/cards/SWSH6/RapidStrikeUrshifu_44.py +++ b/spirit/game/scripts/cards/SWSH6/RapidStrikeUrshifu_44.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, subtypes_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_in_play + + +def _is_rapid_strike(pokemon): + return "Rapid Strike" in subtypes_for(pokemon.archetype_id) + card = PokemonCardDef( guid="1dcd2998-5a26-54f6-9755-276cd53b7e23", @@ -30,7 +36,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 1}, damage=30, damage_operator="x", - effect=unimplemented, + effect=damage_per(count_in_play(side="mine", pred=_is_rapid_strike), 30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Rillaboom_18.py b/spirit/game/scripts/cards/SWSH6/Rillaboom_18.py index adcef3e..9a358ab 100644 --- a/spirit/game/scripts/cards/SWSH6/Rillaboom_18.py +++ b/spirit/game/scripts/cards/SWSH6/Rillaboom_18.py @@ -1,6 +1,23 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.support_common import heal_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def raging_repeated_strike(ctx): + """Discard any amount of Energy from your Pokemon; +30 damage per card discarded.""" + energies = [e for p in ctx.my_pokemon_in_play() + for e in ctx.attached_energies(p)] + picks = [] + if energies: + picks = await ctx.choose_cards( + energies, len(energies), minimum=0, + prompt="Choose any amount of Energy to discard from your Pokémon.", + ) + if picks: + await ctx.discard_cards(picks) + await ctx.deal_damage(120 + 30 * len(picks)) + + card = PokemonCardDef( guid="65961c5c-6705-536e-bdd2-c6f13f626101", key="SWSH6", @@ -24,7 +41,7 @@ card = PokemonCardDef( game_text="Heal 30 damage from this Pok\u00e9mon.", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=60, - effect=unimplemented, + effect=heal_attack(30), ), Attack( title="Raging Repeated Strike", @@ -32,7 +49,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 2, PokemonTypes.COLORLESS: 1}, damage=120, damage_operator="+", - effect=unimplemented, + effect=raging_repeated_strike, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Rockruff_86.py b/spirit/game/scripts/cards/SWSH6/Rockruff_86.py index 98c0689..7d5eeb0 100644 --- a/spirit/game/scripts/cards/SWSH6/Rockruff_86.py +++ b/spirit/game/scripts/cards/SWSH6/Rockruff_86.py @@ -1,6 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def crunch(ctx): + """Flip a coin. If heads, discard an Energy from the opponent's Active.""" + await ctx.deal_damage() + heads = (await ctx.flip_coins(1, ctx.ability.title))[0] + if heads: + target = ctx.opponent_active() + if target is not None and not ctx.effects_blocked(target): + await ctx.discard_energy_from( + target, 1, prompt="Choose Energy to discard from the Defending Pokémon") + + card = PokemonCardDef( guid="055d764a-016f-5770-94a6-de719efb0cca", key="SWSH6", @@ -23,7 +35,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, discard an Energy from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.FIGHTING: 2}, damage=30, - effect=unimplemented, + effect=crunch, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/RuggedHelmet_152.py b/spirit/game/scripts/cards/SWSH6/RuggedHelmet_152.py index 62cfcc6..4aa28d7 100644 --- a/spirit/game/scripts/cards/SWSH6/RuggedHelmet_152.py +++ b/spirit/game/scripts/cards/SWSH6/RuggedHelmet_152.py @@ -1,5 +1,24 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented +from spirit.game.data_utils import PokemonToolCardDef, Ability, Triggers from spirit.game.attributes import Rarities +from spirit.game.card_effects.passives_common import is_in_active_spot + + +async def _rugged_helmet_trigger(ctx): + pokemon = ctx.source + if not is_in_active_spot(pokemon): + return + attacker = ctx.damaged_by + if attacker is None or attacker.owning_player_id == pokemon.owning_player_id: + return + energies = ctx.attached_energies(attacker) + if not energies: + return + picked = await ctx.choose_cards( + energies, 1, minimum=1, + prompt="Choose an Energy to put into your opponent's hand", + ) + await ctx.put_in_hand(picked, reveal=False) + card = PokemonToolCardDef( guid="1e983375-1b09-595f-9433-fbc20bebf058", @@ -11,5 +30,12 @@ card = PokemonToolCardDef( collector_number=152, set_code="SWSH6", rarity=Rarities.Uncommon, - effect=unimplemented + granted_abilities=[ + Ability( + title="Rugged Helmet", + game_text="If the Pok\u00e9mon this card is attached to is in the Active Spot and is damaged by an attack from your opponent's Pok\u00e9mon (even if it is Knocked Out), put an Energy attached to the Attacking Pok\u00e9mon into your opponent's hand.", + trigger=Triggers.ON_DAMAGED_BY_ATTACK, + effect=_rugged_helmet_trigger, + ), + ], ) diff --git a/spirit/game/scripts/cards/SWSH6/RuggedHelmet_228.py b/spirit/game/scripts/cards/SWSH6/RuggedHelmet_228.py index c31c3bb..3f06948 100644 --- a/spirit/game/scripts/cards/SWSH6/RuggedHelmet_228.py +++ b/spirit/game/scripts/cards/SWSH6/RuggedHelmet_228.py @@ -1,5 +1,24 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented +from spirit.game.data_utils import PokemonToolCardDef, Ability, Triggers from spirit.game.attributes import Rarities +from spirit.game.card_effects.passives_common import is_in_active_spot + + +async def _rugged_helmet_trigger(ctx): + pokemon = ctx.source + if not is_in_active_spot(pokemon): + return + attacker = ctx.damaged_by + if attacker is None or attacker.owning_player_id == pokemon.owning_player_id: + return + energies = ctx.attached_energies(attacker) + if not energies: + return + picked = await ctx.choose_cards( + energies, 1, minimum=1, + prompt="Choose an Energy to put into your opponent's hand", + ) + await ctx.put_in_hand(picked, reveal=False) + card = PokemonToolCardDef( guid="e77309d9-4361-5c07-9372-24659f044100", @@ -11,5 +30,12 @@ card = PokemonToolCardDef( collector_number=228, set_code="SWSH6", rarity=Rarities.RareSecret, - effect=unimplemented + granted_abilities=[ + Ability( + title="Rugged Helmet", + game_text="If the Pok\u00e9mon this card is attached to is in the Active Spot and is damaged by an attack from your opponent's Pok\u00e9mon (even if it is Knocked Out), put an Energy attached to the Attacking Pok\u00e9mon into your opponent's hand.", + trigger=Triggers.ON_DAMAGED_BY_ATTACK, + effect=_rugged_helmet_trigger, + ), + ], ) diff --git a/spirit/game/scripts/cards/SWSH6/SandacondaVMAX_206.py b/spirit/game/scripts/cards/SWSH6/SandacondaVMAX_206.py index 98cbb6f..8fd4deb 100644 --- a/spirit/game/scripts/cards/SWSH6/SandacondaVMAX_206.py +++ b/spirit/game/scripts/cards/SWSH6/SandacondaVMAX_206.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import spread_damage + + +async def _g_max_cyclone(ctx): + await ctx.deal_damage() + pokemon = ctx.my_pokemon_in_play() + await ctx.move_energy_freely(pokemon, pokemon) + card = PokemonCardDef( guid="0cf280a7-5cfa-53e0-8d56-942d7bcdef40", @@ -24,14 +32,14 @@ card = PokemonCardDef( game_text="This attack also does 20 damage to each of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.FIGHTING: 1}, damage=60, - effect=unimplemented, + effect=spread_damage(20, side="opponent", also_base=True), ), Attack( title="G-Max Cyclone", game_text="Move any amount of Energy from your Pok\u00e9mon to your other Pok\u00e9mon in any way you like.", cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=180, - effect=unimplemented, + effect=_g_max_cyclone, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/SandacondaVMAX_90.py b/spirit/game/scripts/cards/SWSH6/SandacondaVMAX_90.py index 55e3980..26c163e 100644 --- a/spirit/game/scripts/cards/SWSH6/SandacondaVMAX_90.py +++ b/spirit/game/scripts/cards/SWSH6/SandacondaVMAX_90.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import spread_damage + + +async def _g_max_cyclone(ctx): + await ctx.deal_damage() + pokemon = ctx.my_pokemon_in_play() + await ctx.move_energy_freely(pokemon, pokemon) + card = PokemonCardDef( guid="92a5f81b-c0f5-5237-a27e-1dfcee022087", @@ -24,14 +32,14 @@ card = PokemonCardDef( game_text="This attack also does 20 damage to each of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.FIGHTING: 1}, damage=60, - effect=unimplemented, + effect=spread_damage(20, side="opponent", also_base=True), ), Attack( title="G-Max Cyclone", game_text="Move any amount of Energy from your Pok\u00e9mon to your other Pok\u00e9mon in any way you like.", cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=180, - effect=unimplemented, + effect=_g_max_cyclone, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/SandacondaV_175.py b/spirit/game/scripts/cards/SWSH6/SandacondaV_175.py index 044aeea..cc86e09 100644 --- a/spirit/game/scripts/cards/SWSH6/SandacondaV_175.py +++ b/spirit/game/scripts/cards/SWSH6/SandacondaV_175.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.passives_common import takes_less_passive +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -21,7 +22,7 @@ card = PokemonCardDef( Ability( title="Wall of Sand", game_text="This Pok\u00e9mon takes 30 less damage from attacks (after applying Weakness and Resistance).", - effect=unimplemented, + passive=takes_less_passive(30), ), Attack( title="Land Crush", diff --git a/spirit/game/scripts/cards/SWSH6/SandacondaV_89.py b/spirit/game/scripts/cards/SWSH6/SandacondaV_89.py index b993c70..1208478 100644 --- a/spirit/game/scripts/cards/SWSH6/SandacondaV_89.py +++ b/spirit/game/scripts/cards/SWSH6/SandacondaV_89.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.passives_common import takes_less_passive +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -21,7 +22,7 @@ card = PokemonCardDef( Ability( title="Wall of Sand", game_text="This Pok\u00e9mon takes 30 less damage from attacks (after applying Weakness and Resistance).", - effect=unimplemented, + passive=takes_less_passive(30), ), Attack( title="Land Crush", diff --git a/spirit/game/scripts/cards/SWSH6/Sawsbuck_12.py b/spirit/game/scripts/cards/SWSH6/Sawsbuck_12.py index fa45cd6..2a0966b 100644 --- a/spirit/game/scripts/cards/SWSH6/Sawsbuck_12.py +++ b/spirit/game/scripts/cards/SWSH6/Sawsbuck_12.py @@ -1,6 +1,9 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import bonus_if +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +_stadium_in_play = lambda ctx: ctx.stadium_in_play() is not None + card = PokemonCardDef( guid="d22ab419-aaf3-5044-9fbc-947250e38997", key="SWSH6", @@ -30,7 +33,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 2}, damage=80, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_stadium_in_play, 80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Scolipede_107.py b/spirit/game/scripts/cards/SWSH6/Scolipede_107.py index aa08e8f..465f40d 100644 --- a/spirit/game/scripts/cards/SWSH6/Scolipede_107.py +++ b/spirit/game/scripts/cards/SWSH6/Scolipede_107.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack, condition_bonus_attack card = PokemonCardDef( guid="bc6e7720-d28f-5f43-9928-c2958ab3cb1b", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Poisoned.", cost={PokemonTypes.DARKNESS: 1}, damage=50, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED), ), Attack( title="Venoshock", @@ -32,7 +33,7 @@ card = PokemonCardDef( cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 2}, damage=100, damage_operator="+", - effect=unimplemented, + effect=condition_bonus_attack(120, SpecialConditions.POISONED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Seviper_102.py b/spirit/game/scripts/cards/SWSH6/Seviper_102.py index 0350cc0..f6f12bf 100644 --- a/spirit/game/scripts/cards/SWSH6/Seviper_102.py +++ b/spirit/game/scripts/cards/SWSH6/Seviper_102.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, subtypes_for +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, TrainerType +from spirit.game.card_effects.attacks_common import bonus_if + + +def _played_single_strike_supporter(ctx) -> bool: + def matches(record): + archetype_id, _name, trainer_type = record + return trainer_type == TrainerType.SUPPORTER.value \ + and "Single Strike" in subtypes_for(archetype_id) + return ctx.played_trainer_this_turn(matches) > 0 + card = PokemonCardDef( guid="168797c4-54f3-524c-b9f5-c3172bab5a4c", @@ -24,7 +34,7 @@ card = PokemonCardDef( cost={PokemonTypes.DARKNESS: 2, PokemonTypes.COLORLESS: 1}, damage=90, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_played_single_strike_supporter, 90), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/ShadowRiderCalyrexVMAX_204.py b/spirit/game/scripts/cards/SWSH6/ShadowRiderCalyrexVMAX_204.py index f223792..0c1893a 100644 --- a/spirit/game/scripts/cards/SWSH6/ShadowRiderCalyrexVMAX_204.py +++ b/spirit/game/scripts/cards/SWSH6/ShadowRiderCalyrexVMAX_204.py @@ -1,5 +1,50 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy +from spirit.game.card_effects.pokemon import energy_provides_type + + +def _is_psychic_energy(card) -> bool: + return energy_provides_type(card, PokemonTypes.PSYCHIC.value) + + +def _is_psychic_pokemon(pokemon) -> bool: + types = pokemon.get_attribute(AttrID.POKEMON_TYPES) or [] + return PokemonTypes.PSYCHIC.value in types + + +def underworld_door_condition(board, player_id, pokemon) -> bool: + bench = board.find_player_area(player_id, "bench") + if not bench or not any(_is_psychic_pokemon(p) for p in bench.children): + return False + hand = board.find_player_area(player_id, "hand") + return bool(hand) and any(_is_psychic_energy(c) for c in hand.children) + + +async def underworld_door(ctx): + """You may attach a Psychic Energy from hand to a Benched Psychic + Pokemon. If you did, draw 2 cards.""" + bench = [p for p in ctx.my_bench() if _is_psychic_pokemon(p)] + energies = [c for c in ctx.hand() if _is_psychic_energy(c)] + if not bench or not energies: + return + if not await ctx.ask_yes_no( + "Attach a Psychic Energy card from your hand to 1 of your " + "Benched Psychic Pokémon?"): + return + picked = await ctx.choose_cards( + energies, 1, minimum=1, prompt="Choose a Psychic Energy card to attach" + ) + if not picked: + return + target = await ctx.choose_pokemon( + bench, "Choose the Benched Psychic Pokémon to attach it to" + ) + if target is None: + return + await ctx.attach_energy(picked[0], target) + await ctx.draw_cards(2) + card = PokemonCardDef( guid="6ed1b3e8-c46e-5abd-8941-8efe6dab32e7", @@ -23,7 +68,9 @@ card = PokemonCardDef( Ability( title="Underworld Door", game_text="Once during your turn, you may attach a Psychic Energy card from your hand to 1 of your Benched Psychic Pok\u00e9mon. If you attached Energy to a Pok\u00e9mon in this way, draw 2 cards.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=underworld_door_condition, + effect=underworld_door, ), Attack( title="Max Geist", @@ -31,7 +78,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=10, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("mine", energy_type=PokemonTypes.PSYCHIC), 30, base=10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/ShadowRiderCalyrexVMAX_205.py b/spirit/game/scripts/cards/SWSH6/ShadowRiderCalyrexVMAX_205.py index 48d6389..cec96e7 100644 --- a/spirit/game/scripts/cards/SWSH6/ShadowRiderCalyrexVMAX_205.py +++ b/spirit/game/scripts/cards/SWSH6/ShadowRiderCalyrexVMAX_205.py @@ -1,5 +1,50 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy +from spirit.game.card_effects.pokemon import energy_provides_type + + +def _is_psychic_energy(card) -> bool: + return energy_provides_type(card, PokemonTypes.PSYCHIC.value) + + +def _is_psychic_pokemon(pokemon) -> bool: + types = pokemon.get_attribute(AttrID.POKEMON_TYPES) or [] + return PokemonTypes.PSYCHIC.value in types + + +def underworld_door_condition(board, player_id, pokemon) -> bool: + bench = board.find_player_area(player_id, "bench") + if not bench or not any(_is_psychic_pokemon(p) for p in bench.children): + return False + hand = board.find_player_area(player_id, "hand") + return bool(hand) and any(_is_psychic_energy(c) for c in hand.children) + + +async def underworld_door(ctx): + """You may attach a Psychic Energy from hand to a Benched Psychic + Pokemon. If you did, draw 2 cards.""" + bench = [p for p in ctx.my_bench() if _is_psychic_pokemon(p)] + energies = [c for c in ctx.hand() if _is_psychic_energy(c)] + if not bench or not energies: + return + if not await ctx.ask_yes_no( + "Attach a Psychic Energy card from your hand to 1 of your " + "Benched Psychic Pokémon?"): + return + picked = await ctx.choose_cards( + energies, 1, minimum=1, prompt="Choose a Psychic Energy card to attach" + ) + if not picked: + return + target = await ctx.choose_pokemon( + bench, "Choose the Benched Psychic Pokémon to attach it to" + ) + if target is None: + return + await ctx.attach_energy(picked[0], target) + await ctx.draw_cards(2) + card = PokemonCardDef( guid="9bcbc103-84e2-5d78-a60a-215aa0709d1a", @@ -23,7 +68,9 @@ card = PokemonCardDef( Ability( title="Underworld Door", game_text="Once during your turn, you may attach a Psychic Energy card from your hand to 1 of your Benched Psychic Pok\u00e9mon. If you attached Energy to a Pok\u00e9mon in this way, draw 2 cards.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=underworld_door_condition, + effect=underworld_door, ), Attack( title="Max Geist", @@ -31,7 +78,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=10, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("mine", energy_type=PokemonTypes.PSYCHIC), 30, base=10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/ShadowRiderCalyrexVMAX_75.py b/spirit/game/scripts/cards/SWSH6/ShadowRiderCalyrexVMAX_75.py index 77ef620..fe139c4 100644 --- a/spirit/game/scripts/cards/SWSH6/ShadowRiderCalyrexVMAX_75.py +++ b/spirit/game/scripts/cards/SWSH6/ShadowRiderCalyrexVMAX_75.py @@ -1,5 +1,50 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy +from spirit.game.card_effects.pokemon import energy_provides_type + + +def _is_psychic_energy(card) -> bool: + return energy_provides_type(card, PokemonTypes.PSYCHIC.value) + + +def _is_psychic_pokemon(pokemon) -> bool: + types = pokemon.get_attribute(AttrID.POKEMON_TYPES) or [] + return PokemonTypes.PSYCHIC.value in types + + +def underworld_door_condition(board, player_id, pokemon) -> bool: + bench = board.find_player_area(player_id, "bench") + if not bench or not any(_is_psychic_pokemon(p) for p in bench.children): + return False + hand = board.find_player_area(player_id, "hand") + return bool(hand) and any(_is_psychic_energy(c) for c in hand.children) + + +async def underworld_door(ctx): + """You may attach a Psychic Energy from hand to a Benched Psychic + Pokemon. If you did, draw 2 cards.""" + bench = [p for p in ctx.my_bench() if _is_psychic_pokemon(p)] + energies = [c for c in ctx.hand() if _is_psychic_energy(c)] + if not bench or not energies: + return + if not await ctx.ask_yes_no( + "Attach a Psychic Energy card from your hand to 1 of your " + "Benched Psychic Pokémon?"): + return + picked = await ctx.choose_cards( + energies, 1, minimum=1, prompt="Choose a Psychic Energy card to attach" + ) + if not picked: + return + target = await ctx.choose_pokemon( + bench, "Choose the Benched Psychic Pokémon to attach it to" + ) + if target is None: + return + await ctx.attach_energy(picked[0], target) + await ctx.draw_cards(2) + card = PokemonCardDef( guid="3ed89883-b7f1-5247-83bd-f9dfe0f11983", @@ -23,7 +68,9 @@ card = PokemonCardDef( Ability( title="Underworld Door", game_text="Once during your turn, you may attach a Psychic Energy card from your hand to 1 of your Benched Psychic Pok\u00e9mon. If you attached Energy to a Pok\u00e9mon in this way, draw 2 cards.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=underworld_door_condition, + effect=underworld_door, ), Attack( title="Max Geist", @@ -31,7 +78,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=10, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("mine", energy_type=PokemonTypes.PSYCHIC), 30, base=10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/ShadowRiderCalyrexV_171.py b/spirit/game/scripts/cards/SWSH6/ShadowRiderCalyrexV_171.py index aa1970d..25536db 100644 --- a/spirit/game/scripts/cards/SWSH6/ShadowRiderCalyrexV_171.py +++ b/spirit/game/scripts/cards/SWSH6/ShadowRiderCalyrexV_171.py @@ -1,5 +1,33 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID, TrainerType +from spirit.game.session.effects import is_special_energy + + +def _is_stadium_card(card): + return card.get_attribute(AttrID.TRAINER_TYPE) == TrainerType.STADIUM.value + + +async def shadow_mist(ctx): + """10 damage; during your opponent's next turn, they can't play any + Special Energy or Stadium cards from their hand.""" + await ctx.deal_damage() + ctx.lock_plays( + ctx.opponent_id, + lambda card: is_special_energy(card) or _is_stadium_card(card), + ) + + +async def astral_barrage(ctx): + """Choose 2 of your opponent's Pokemon and put 5 damage counters on each.""" + candidates = ctx.opponent_pokemon_in_play() + if not candidates: + return + chosen = await ctx.choose_cards( + candidates, 2, prompt="Choose 2 of your opponent's Pokémon" + ) + for target in chosen: + await ctx.deal_damage(50, target=target, as_counters=True) + card = PokemonCardDef( guid="ce2cc6af-b581-53f5-be63-d164a20aa1a3", @@ -24,13 +52,13 @@ card = PokemonCardDef( game_text="During your opponent's next turn, they can't play any Special Energy or Stadium cards from their hand.", cost={PokemonTypes.PSYCHIC: 1}, damage=10, - effect=unimplemented, + effect=shadow_mist, ), Attack( title="Astral Barrage", game_text="Choose 2 of your opponent's Pok\u00e9mon and put 5 damage counters on each of them.", cost={PokemonTypes.COLORLESS: 3}, - effect=unimplemented, + effect=astral_barrage, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/ShadowRiderCalyrexV_172.py b/spirit/game/scripts/cards/SWSH6/ShadowRiderCalyrexV_172.py index d780cef..63fb9a7 100644 --- a/spirit/game/scripts/cards/SWSH6/ShadowRiderCalyrexV_172.py +++ b/spirit/game/scripts/cards/SWSH6/ShadowRiderCalyrexV_172.py @@ -1,5 +1,33 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID, TrainerType +from spirit.game.session.effects import is_special_energy + + +def _is_stadium_card(card): + return card.get_attribute(AttrID.TRAINER_TYPE) == TrainerType.STADIUM.value + + +async def shadow_mist(ctx): + """10 damage; during your opponent's next turn, they can't play any + Special Energy or Stadium cards from their hand.""" + await ctx.deal_damage() + ctx.lock_plays( + ctx.opponent_id, + lambda card: is_special_energy(card) or _is_stadium_card(card), + ) + + +async def astral_barrage(ctx): + """Choose 2 of your opponent's Pokemon and put 5 damage counters on each.""" + candidates = ctx.opponent_pokemon_in_play() + if not candidates: + return + chosen = await ctx.choose_cards( + candidates, 2, prompt="Choose 2 of your opponent's Pokémon" + ) + for target in chosen: + await ctx.deal_damage(50, target=target, as_counters=True) + card = PokemonCardDef( guid="8a7cf595-4e42-53e2-a0cc-4231602dbaa6", @@ -24,13 +52,13 @@ card = PokemonCardDef( game_text="During your opponent's next turn, they can't play any Special Energy or Stadium cards from their hand.", cost={PokemonTypes.PSYCHIC: 1}, damage=10, - effect=unimplemented, + effect=shadow_mist, ), Attack( title="Astral Barrage", game_text="Choose 2 of your opponent's Pok\u00e9mon and put 5 damage counters on each of them.", cost={PokemonTypes.COLORLESS: 3}, - effect=unimplemented, + effect=astral_barrage, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/ShadowRiderCalyrexV_74.py b/spirit/game/scripts/cards/SWSH6/ShadowRiderCalyrexV_74.py index c72a3ff..ef03c90 100644 --- a/spirit/game/scripts/cards/SWSH6/ShadowRiderCalyrexV_74.py +++ b/spirit/game/scripts/cards/SWSH6/ShadowRiderCalyrexV_74.py @@ -1,5 +1,33 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID, TrainerType +from spirit.game.session.effects import is_special_energy + + +def _is_stadium_card(card): + return card.get_attribute(AttrID.TRAINER_TYPE) == TrainerType.STADIUM.value + + +async def shadow_mist(ctx): + """10 damage; during your opponent's next turn, they can't play any + Special Energy or Stadium cards from their hand.""" + await ctx.deal_damage() + ctx.lock_plays( + ctx.opponent_id, + lambda card: is_special_energy(card) or _is_stadium_card(card), + ) + + +async def astral_barrage(ctx): + """Choose 2 of your opponent's Pokemon and put 5 damage counters on each.""" + candidates = ctx.opponent_pokemon_in_play() + if not candidates: + return + chosen = await ctx.choose_cards( + candidates, 2, prompt="Choose 2 of your opponent's Pokémon" + ) + for target in chosen: + await ctx.deal_damage(50, target=target, as_counters=True) + card = PokemonCardDef( guid="e60c8668-be24-5bf7-b848-e0986c0429a9", @@ -24,13 +52,13 @@ card = PokemonCardDef( game_text="During your opponent's next turn, they can't play any Special Energy or Stadium cards from their hand.", cost={PokemonTypes.PSYCHIC: 1}, damage=10, - effect=unimplemented, + effect=shadow_mist, ), Attack( title="Astral Barrage", game_text="Choose 2 of your opponent's Pok\u00e9mon and put 5 damage counters on each of them.", cost={PokemonTypes.COLORLESS: 3}, - effect=unimplemented, + effect=astral_barrage, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Shaymin_123.py b/spirit/game/scripts/cards/SWSH6/Shaymin_123.py index da50d3a..6415b37 100644 --- a/spirit/game/scripts/cards/SWSH6/Shaymin_123.py +++ b/spirit/game/scripts/cards/SWSH6/Shaymin_123.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import remove_self_from_play + + +async def return_attack(ctx): + """10 damage. You may draw cards until you have 6 cards in hand.""" + await ctx.deal_damage() + if await ctx.ask_yes_no("Draw cards until you have 6 cards in your hand?"): + await ctx.draw_until(6) + card = PokemonCardDef( guid="6b956995-d951-5e07-9ea8-cb29275f4f0c", @@ -24,14 +33,14 @@ card = PokemonCardDef( game_text="You may draw cards until you have 6 cards in your hand.", cost={PokemonTypes.COLORLESS: 1}, damage=10, - effect=unimplemented, + effect=return_attack, ), Attack( title="Sky Return", game_text="Put this Pok\u00e9mon and all attached cards into your hand.", cost={PokemonTypes.COLORLESS: 2}, damage=30, - effect=unimplemented, + effect=remove_self_from_play("hand"), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Siebold_153.py b/spirit/game/scripts/cards/SWSH6/Siebold_153.py index 1c10e13..923ef8a 100644 --- a/spirit/game/scripts/cards/SWSH6/Siebold_153.py +++ b/spirit/game/scripts/cards/SWSH6/Siebold_153.py @@ -1,5 +1,24 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef, subtypes_for from spirit.game.attributes import Rarities +from spirit.game.card_effects.support_common import requires_in_play + + +def _is_rapid_strike(pokemon): + return "Rapid Strike" in subtypes_for(pokemon.archetype_id) + + +async def siebold(ctx): + """Choose up to 2 of your Rapid Strike Pokémon and heal 60 damage from each.""" + candidates = [p for p in ctx.my_pokemon_in_play() if _is_rapid_strike(p)] + if not candidates: + return + picks = await ctx.choose_cards( + candidates, 2, minimum=0, + prompt="Choose up to 2 Rapid Strike Pokémon to heal 60 damage from.", + ) + for pokemon in picks: + await ctx.heal(60, pokemon) + card = SupporterCardDef( guid="1a2e5f90-5cb1-52ab-a644-caf0f7d4890c", @@ -11,5 +30,6 @@ card = SupporterCardDef( collector_number=153, set_code="SWSH6", rarity=Rarities.Uncommon, - effect=unimplemented + condition=requires_in_play(_is_rapid_strike), + effect=siebold ) diff --git a/spirit/game/scripts/cards/SWSH6/Siebold_198.py b/spirit/game/scripts/cards/SWSH6/Siebold_198.py index 07aeb46..7d55a03 100644 --- a/spirit/game/scripts/cards/SWSH6/Siebold_198.py +++ b/spirit/game/scripts/cards/SWSH6/Siebold_198.py @@ -1,5 +1,24 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef, subtypes_for from spirit.game.attributes import Rarities +from spirit.game.card_effects.support_common import requires_in_play + + +def _is_rapid_strike(pokemon): + return "Rapid Strike" in subtypes_for(pokemon.archetype_id) + + +async def siebold(ctx): + """Choose up to 2 of your Rapid Strike Pokémon and heal 60 damage from each.""" + candidates = [p for p in ctx.my_pokemon_in_play() if _is_rapid_strike(p)] + if not candidates: + return + picks = await ctx.choose_cards( + candidates, 2, minimum=0, + prompt="Choose up to 2 Rapid Strike Pokémon to heal 60 damage from.", + ) + for pokemon in picks: + await ctx.heal(60, pokemon) + card = SupporterCardDef( guid="9e6146cc-160e-5248-a558-05547c4b30a8", @@ -11,5 +30,6 @@ card = SupporterCardDef( collector_number=198, set_code="SWSH6", rarity=Rarities.RareUltra, - effect=unimplemented + condition=requires_in_play(_is_rapid_strike), + effect=siebold ) diff --git a/spirit/game/scripts/cards/SWSH6/Siebold_221.py b/spirit/game/scripts/cards/SWSH6/Siebold_221.py index fdc8484..8952696 100644 --- a/spirit/game/scripts/cards/SWSH6/Siebold_221.py +++ b/spirit/game/scripts/cards/SWSH6/Siebold_221.py @@ -1,5 +1,24 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef, subtypes_for from spirit.game.attributes import Rarities +from spirit.game.card_effects.support_common import requires_in_play + + +def _is_rapid_strike(pokemon): + return "Rapid Strike" in subtypes_for(pokemon.archetype_id) + + +async def siebold(ctx): + """Choose up to 2 of your Rapid Strike Pokémon and heal 60 damage from each.""" + candidates = [p for p in ctx.my_pokemon_in_play() if _is_rapid_strike(p)] + if not candidates: + return + picks = await ctx.choose_cards( + candidates, 2, minimum=0, + prompt="Choose up to 2 Rapid Strike Pokémon to heal 60 damage from.", + ) + for pokemon in picks: + await ctx.heal(60, pokemon) + card = SupporterCardDef( guid="2837b729-fd53-5dad-a642-d2990a16c419", @@ -11,5 +30,6 @@ card = SupporterCardDef( collector_number=221, set_code="SWSH6", rarity=Rarities.RareRainbow, - effect=unimplemented + condition=requires_in_play(_is_rapid_strike), + effect=siebold ) diff --git a/spirit/game/scripts/cards/SWSH6/SingleStrikeScrollofPiercing_154.py b/spirit/game/scripts/cards/SWSH6/SingleStrikeScrollofPiercing_154.py index 1aad743..91d8b9b 100644 --- a/spirit/game/scripts/cards/SWSH6/SingleStrikeScrollofPiercing_154.py +++ b/spirit/game/scripts/cards/SWSH6/SingleStrikeScrollofPiercing_154.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import PokemonToolCardDef, Attack, subtypes_for +from spirit.game.attributes import PokemonTypes, Rarities + + +def _is_single_strike(board, player_id, pokemon): + return "Single Strike" in subtypes_for(pokemon.archetype_id) + + +async def bullet_breakthrough(ctx): + """120 damage, not affected by Weakness/Resistance or effects on the opponent's Active.""" + await ctx.deal_damage(120, apply_modifiers=False, ignore_target_effects=True) + card = PokemonToolCardDef( guid="403a69e4-d741-55fa-8cbf-629361902695", @@ -11,5 +21,14 @@ card = PokemonToolCardDef( collector_number=154, set_code="SWSH6", rarity=Rarities.Uncommon, - effect=unimplemented + granted_abilities=[ + Attack( + title="Bullet Breakthrough", + game_text="This attack's damage isn't affected by Weakness or Resistance, or by any effects on your opponent's Active Pok\u00e9mon.", + cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 2}, + damage=120, + effect=bullet_breakthrough, + condition=_is_single_strike, + ), + ], ) diff --git a/spirit/game/scripts/cards/SWSH6/SingleStrikeUrshifu_108.py b/spirit/game/scripts/cards/SWSH6/SingleStrikeUrshifu_108.py index d8d8300..8dde9b1 100644 --- a/spirit/game/scripts/cards/SWSH6/SingleStrikeUrshifu_108.py +++ b/spirit/game/scripts/cards/SWSH6/SingleStrikeUrshifu_108.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, has_damage + + +async def field_crush(ctx): + """50. If your opponent has a Stadium in play, discard it.""" + await ctx.deal_damage() + stadium = ctx.stadium_in_play() + if stadium is not None and stadium.owning_player_id == ctx.opponent_id: + await ctx.discard_stadium() + card = PokemonCardDef( guid="77b3671c-2b08-5330-a74d-c31c47a2c74a", @@ -24,7 +34,7 @@ card = PokemonCardDef( game_text="If your opponent has a Stadium in play, discard it.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 1}, damage=50, - effect=unimplemented, + effect=field_crush, ), Attack( title="Fists of Strife", @@ -32,7 +42,7 @@ card = PokemonCardDef( cost={PokemonTypes.DARKNESS: 2, PokemonTypes.COLORLESS: 1}, damage=100, damage_operator="+", - effect=unimplemented, + effect=bonus_if(has_damage("self"), 100), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Skwovet_127.py b/spirit/game/scripts/cards/SWSH6/Skwovet_127.py index dc626e2..93207a7 100644 --- a/spirit/game/scripts/cards/SWSH6/Skwovet_127.py +++ b/spirit/game/scripts/cards/SWSH6/Skwovet_127.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID, TrainerType +from spirit.game.card_effects.support_common import recover_from_discard, requires_discard + +_TOOL_TYPES = (TrainerType.POKEMON_TOOL.value, TrainerType.POKEMON_TOOL_F.value) + + +def _is_tool_card(card): + return card.get_attribute(AttrID.TRAINER_TYPE) in _TOOL_TYPES + + +scrounge = recover_from_discard( + predicate=_is_tool_card, count=1, minimum=1, reveal=False, to="hand", + prompt="Choose a Pokémon Tool card to put into your hand.", +) card = PokemonCardDef( guid="1107d6b1-b6b2-5e3a-b970-e5c169d2173a", @@ -22,7 +35,8 @@ card = PokemonCardDef( title="Scrounge", game_text="Put a Pok\u00e9mon Tool card from your discard pile into your hand.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=scrounge, + condition=requires_discard(_is_tool_card), ), Attack( title="Gnaw", diff --git a/spirit/game/scripts/cards/SWSH6/Slurpuff_68.py b/spirit/game/scripts/cards/SWSH6/Slurpuff_68.py index 5f7762a..8a8c663 100644 --- a/spirit/game/scripts/cards/SWSH6/Slurpuff_68.py +++ b/spirit/game/scripts/cards/SWSH6/Slurpuff_68.py @@ -1,6 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def follow_the_scent(ctx): + heads = await ctx.flip_coins(3, "Follow the Scent") + count = sum(1 for h in heads if h) + discard = ctx.discard_pile() + if count <= 0 or not discard: + return + picks = await ctx.choose_cards( + discard, count, minimum=1, + prompt=f"Choose up to {count} cards from your discard pile to put into your hand.", + ) + if picks: + await ctx.put_in_hand(picks, reveal=False) + card = PokemonCardDef( guid="c055fdce-d988-5206-aeec-a15fa7e9730f", key="SWSH6", @@ -23,7 +37,7 @@ card = PokemonCardDef( title="Follow the Scent", game_text="Flip 3 coins. Put a number of cards up to the number of heads from your discard pile into your hand.", cost={PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + effect=follow_the_scent, ), Attack( title="Fairy Wind", diff --git a/spirit/game/scripts/cards/SWSH6/Sneasel_30.py b/spirit/game/scripts/cards/SWSH6/Sneasel_30.py index 6b62a1f..17c203a 100644 --- a/spirit/game/scripts/cards/SWSH6/Sneasel_30.py +++ b/spirit/game/scripts/cards/SWSH6/Sneasel_30.py @@ -1,6 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def cut_down(ctx): + heads = await ctx.flip_coins(1, "Cut Down") + if not heads[0]: + return + target = ctx.opponent_active() + if target is None or ctx.effects_blocked(target): + return + await ctx.discard_energy_from( + target, 1, + prompt="Choose an Energy to discard from the Defending Pokémon.", + ) + card = PokemonCardDef( guid="f880509e-223f-5252-b50f-4aa925fadf63", key="SWSH6", @@ -22,7 +35,7 @@ card = PokemonCardDef( title="Cut Down", game_text="Flip a coin. If heads, discard an Energy from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=cut_down, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Snorlax_224.py b/spirit/game/scripts/cards/SWSH6/Snorlax_224.py index 9f6f8d1..61aab2b 100644 --- a/spirit/game/scripts/cards/SWSH6/Snorlax_224.py +++ b/spirit/game/scripts/cards/SWSH6/Snorlax_224.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.pokemon import in_active_spot + + +async def gormandize(ctx): + if await ctx.ask_yes_no("Draw cards until you have 7 cards in your hand?"): + await ctx.draw_until(7) card = PokemonCardDef( guid="c2dcd121-1cd4-563a-8e5e-0f1f31829146", @@ -21,14 +28,17 @@ card = PokemonCardDef( Ability( title="Gormandize", game_text="Once during your turn, if this Pok\u00e9mon is in the Active Spot, you may draw cards until you have 7 cards in your hand. If you use this Ability, your turn ends.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=in_active_spot, + ends_turn=True, + effect=gormandize, ), Attack( title="Body Slam", game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.COLORLESS: 4}, damage=100, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Sobble_41.py b/spirit/game/scripts/cards/SWSH6/Sobble_41.py index f68f045..fdfa557 100644 --- a/spirit/game/scripts/cards/SWSH6/Sobble_41.py +++ b/spirit/game/scripts/cards/SWSH6/Sobble_41.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, subtypes_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage +from spirit.game.card_effects.support_common import search_to_bench +from spirit.game.session.effects import is_basic_pokemon + + +def _is_basic_rapid_strike(card): + return is_basic_pokemon(card) and "Rapid Strike" in subtypes_for(card.archetype_id) card = PokemonCardDef( guid="585cecee-cbc1-55ee-96f6-3f797cb366a6", @@ -22,7 +29,7 @@ card = PokemonCardDef( title="Keep Calling", game_text="Search your deck for up to 3 Basic Rapid Strike Pok\u00e9mon and put them onto your Bench. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_to_bench(predicate=_is_basic_rapid_strike, count=3), ), Attack( title="Double Spin", @@ -30,7 +37,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=20, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=2, per_heads=20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Spiritomb_103.py b/spirit/game/scripts/cards/SWSH6/Spiritomb_103.py index 66159eb..d04242d 100644 --- a/spirit/game/scripts/cards/SWSH6/Spiritomb_103.py +++ b/spirit/game/scripts/cards/SWSH6/Spiritomb_103.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_pokemon_card + + +async def ghostly_cries(ctx): + """1 counter per opponent's discarded Pokemon, any way; if any, shuffle those Pokemon into their deck.""" + opponent = ctx.opponent_id + discard = ctx.discard_pile(opponent) + pokemon_cards = [c for c in discard if is_pokemon_card(c)] + count = len(pokemon_cards) + if count <= 0: + return + await ctx.place_damage_counters(count, candidates=ctx.opponent_pokemon_in_play()) + await ctx.shuffle_into_deck(pokemon_cards, player_id=opponent) + card = PokemonCardDef( guid="95a51d07-4ea7-5ced-8a3f-789e259f7aac", @@ -22,7 +36,7 @@ card = PokemonCardDef( title="Ghostly Cries", game_text="For each Pok\u00e9mon in your opponent's discard pile, put 1 damage counter on your opponent's Pok\u00e9mon in any way you like. If you placed any damage counters in this way, your opponent shuffles all Pok\u00e9mon from their discard pile into their deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=ghostly_cries, ), Attack( title="Will-O-Wisp", diff --git a/spirit/game/scripts/cards/SWSH6/Steenee_14.py b/spirit/game/scripts/cards/SWSH6/Steenee_14.py index 8ed7c3e..adcf119 100644 --- a/spirit/game/scripts/cards/SWSH6/Steenee_14.py +++ b/spirit/game/scripts/cards/SWSH6/Steenee_14.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus card = PokemonCardDef( guid="b8bc2272-231f-5464-81f9-0767657c8aa0", @@ -30,7 +31,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 2}, damage=50, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Swirlix_67.py b/spirit/game/scripts/cards/SWSH6/Swirlix_67.py index d01ca2d..b0fc41b 100644 --- a/spirit/game/scripts/cards/SWSH6/Swirlix_67.py +++ b/spirit/game/scripts/cards/SWSH6/Swirlix_67.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import heal_attack card = PokemonCardDef( guid="a6b58561-b702-5f0c-82fc-33629b34af0f", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Heal 10 damage from this Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 2}, damage=20, - effect=unimplemented, + effect=heal_attack(10, target="self"), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/TapuFini_40.py b/spirit/game/scripts/cards/SWSH6/TapuFini_40.py index ce9f369..f987885 100644 --- a/spirit/game/scripts/cards/SWSH6/TapuFini_40.py +++ b/spirit/game/scripts/cards/SWSH6/TapuFini_40.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import switch_self_attack + + +async def _ocean_loop(ctx): + await ctx.deal_damage() + energies = ctx.attached_energies(ctx.attacker) + picks = await ctx.choose_cards( + energies, 1, + prompt="Choose an Energy card to put into your hand", + ) + await ctx.put_in_hand(picks, reveal=False) + card = PokemonCardDef( guid="17ec4cdd-2663-5f6b-8d28-a2af4f3b9f52", @@ -23,14 +35,14 @@ card = PokemonCardDef( game_text="You may switch this Pok\u00e9mon with 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.WATER: 1}, damage=30, - effect=unimplemented, + effect=switch_self_attack(optional=True), ), Attack( title="Ocean Loop", game_text="Put an Energy attached to this Pok\u00e9mon into your hand.", cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + effect=_ocean_loop, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Tauros_115.py b/spirit/game/scripts/cards/SWSH6/Tauros_115.py index 67fa828..278612f 100644 --- a/spirit/game/scripts/cards/SWSH6/Tauros_115.py +++ b/spirit/game/scripts/cards/SWSH6/Tauros_115.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import ( + damage_per, damage_counters_on, recoil_attack, +) + + +async def _raging_bull_confuse(ctx): + """After the hit: this Pokémon becomes Confused.""" + await ctx.apply_special_condition(ctx.attacker, SpecialConditions.CONFUSED) + card = PokemonCardDef( guid="11ab1ba9-c5f4-5876-86fa-18e0d14fd0d7", @@ -24,14 +33,15 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=20, damage_operator="+", - effect=unimplemented, + effect=damage_per(damage_counters_on("self"), 20, base=20, + also=_raging_bull_confuse), ), Attack( title="Take Down", game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.COLORLESS: 2}, damage=80, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Thundurus_52.py b/spirit/game/scripts/cards/SWSH6/Thundurus_52.py index 7a44624..e4c60e7 100644 --- a/spirit/game/scripts/cards/SWSH6/Thundurus_52.py +++ b/spirit/game/scripts/cards/SWSH6/Thundurus_52.py @@ -1,5 +1,31 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import energy_provides_type +from spirit.game.card_effects.attacks_common import recoil_attack + + +async def assisting_spark(ctx): + """30 damage. You may attach a Lightning Energy card from your hand to + 1 of your Benched Pokemon.""" + await ctx.deal_damage() + bench = ctx.my_bench() + if not bench: + return + energies = [c for c in ctx.hand() if energy_provides_type(c, PokemonTypes.LIGHTNING.value)] + if not energies: + return + if not await ctx.ask_yes_no( + "Attach a Lightning Energy card from your hand to 1 of your Benched Pokémon?" + ): + return + picks = await ctx.choose_cards(energies, 1, prompt="Choose a Lightning Energy card to attach.") + if not picks: + return + target = await ctx.choose_pokemon(bench, "Choose 1 of your Benched Pokémon.") + if target is None: + return + await ctx.attach_energy(picks[0], target) + card = PokemonCardDef( guid="cc401b1c-80d0-5ed1-b895-eb9c92dbbf1e", @@ -23,14 +49,14 @@ card = PokemonCardDef( game_text="You may attach a Lightning Energy card from your hand to 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.LIGHTNING: 1}, damage=30, - effect=unimplemented, + effect=assisting_spark, ), Attack( title="Thunder", game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 1}, damage=130, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Thwackey_17.py b/spirit/game/scripts/cards/SWSH6/Thwackey_17.py index 78befa9..7e0ef95 100644 --- a/spirit/game/scripts/cards/SWSH6/Thwackey_17.py +++ b/spirit/game/scripts/cards/SWSH6/Thwackey_17.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import discard_random_from_hand + + +async def knock_off(ctx): + await ctx.deal_damage() + await discard_random_from_hand(ctx, player_id=ctx.opponent_id, count=1) + card = PokemonCardDef( guid="fdbfe1c1-1b97-509c-a116-7357fd0c464e", @@ -24,7 +31,7 @@ card = PokemonCardDef( game_text="Discard a random card from your opponent's hand.", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=knock_off, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/TornadusVMAX_125.py b/spirit/game/scripts/cards/SWSH6/TornadusVMAX_125.py index ac5370e..665e171 100644 --- a/spirit/game/scripts/cards/SWSH6/TornadusVMAX_125.py +++ b/spirit/game/scripts/cards/SWSH6/TornadusVMAX_125.py @@ -1,6 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def max_wind_spirit(ctx): + """120, +120 more if a Stadium is in play; then discard that Stadium.""" + stadium = ctx.stadium_in_play() + amount = 120 + (120 if stadium is not None else 0) + await ctx.deal_damage(amount) + if stadium is not None: + await ctx.discard_stadium() + + card = PokemonCardDef( guid="eb417894-6b67-5be8-bb18-a1694571ef31", key="SWSH6", @@ -31,7 +41,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 4}, damage=120, damage_operator="+", - effect=unimplemented, + effect=max_wind_spirit, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/TornadusVMAX_209.py b/spirit/game/scripts/cards/SWSH6/TornadusVMAX_209.py index da27ef9..2d9ab94 100644 --- a/spirit/game/scripts/cards/SWSH6/TornadusVMAX_209.py +++ b/spirit/game/scripts/cards/SWSH6/TornadusVMAX_209.py @@ -1,6 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def max_wind_spirit(ctx): + """120, +120 more if a Stadium is in play; then discard that Stadium.""" + stadium = ctx.stadium_in_play() + amount = 120 + (120 if stadium is not None else 0) + await ctx.deal_damage(amount) + if stadium is not None: + await ctx.discard_stadium() + + card = PokemonCardDef( guid="a2fceaef-bffb-51ba-8bc4-852bd4d7cadf", key="SWSH6", @@ -31,7 +41,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 4}, damage=120, damage_operator="+", - effect=unimplemented, + effect=max_wind_spirit, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/TornadusV_124.py b/spirit/game/scripts/cards/SWSH6/TornadusV_124.py index d35db99..e490db4 100644 --- a/spirit/game/scripts/cards/SWSH6/TornadusV_124.py +++ b/spirit/game/scripts/cards/SWSH6/TornadusV_124.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, self_energy_discard_attack card = PokemonCardDef( guid="ea22f75d-ce7a-59fd-8b96-ea0ec61589dc", @@ -25,14 +26,14 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=bonus_if(lambda ctx: ctx.stadium_in_play() is not None, 20), ), Attack( title="Blasting Hammer", game_text="Discard an Energy from this Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 4}, damage=180, - effect=unimplemented, + effect=self_energy_discard_attack(count=1, before_damage=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/TornadusV_184.py b/spirit/game/scripts/cards/SWSH6/TornadusV_184.py index 5d4c937..d96e8a8 100644 --- a/spirit/game/scripts/cards/SWSH6/TornadusV_184.py +++ b/spirit/game/scripts/cards/SWSH6/TornadusV_184.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, self_energy_discard_attack card = PokemonCardDef( guid="e6e6cd69-b2d0-5ad9-99f9-00e9e3656cfc", @@ -25,14 +26,14 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=bonus_if(lambda ctx: ctx.stadium_in_play() is not None, 20), ), Attack( title="Blasting Hammer", game_text="Discard an Energy from this Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 4}, damage=180, - effect=unimplemented, + effect=self_energy_discard_attack(count=1, before_damage=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/TornadusV_185.py b/spirit/game/scripts/cards/SWSH6/TornadusV_185.py index 31e5dba..8b261ed 100644 --- a/spirit/game/scripts/cards/SWSH6/TornadusV_185.py +++ b/spirit/game/scripts/cards/SWSH6/TornadusV_185.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, self_energy_discard_attack card = PokemonCardDef( guid="d89e84e0-53dd-5965-9418-a3bc250feddc", @@ -25,14 +26,14 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=bonus_if(lambda ctx: ctx.stadium_in_play() is not None, 20), ), Attack( title="Blasting Hammer", game_text="Discard an Energy from this Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 4}, damage=180, - effect=unimplemented, + effect=self_energy_discard_attack(count=1, before_damage=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Tsareena_15.py b/spirit/game/scripts/cards/SWSH6/Tsareena_15.py index fa4f48d..ab4a3d2 100644 --- a/spirit/game/scripts/cards/SWSH6/Tsareena_15.py +++ b/spirit/game/scripts/cards/SWSH6/Tsareena_15.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per + + +def _defender_retreat_cost(ctx): + active = ctx.opponent_active() + return int(active.get_attribute(AttrID.RETREAT_COST) or 0) if active else 0 + card = PokemonCardDef( guid="9efe8ff2-f51a-5597-b5c2-82fc75ff7cff", @@ -25,7 +32,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=10, damage_operator="+", - effect=unimplemented, + effect=damage_per(_defender_retreat_cost, 50, base=10), ), Attack( title="Solar Beam", diff --git a/spirit/game/scripts/cards/SWSH6/UrnofVitality_229.py b/spirit/game/scripts/cards/SWSH6/UrnofVitality_229.py index 2799783..256bb93 100644 --- a/spirit/game/scripts/cards/SWSH6/UrnofVitality_229.py +++ b/spirit/game/scripts/cards/SWSH6/UrnofVitality_229.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef, subtypes_for from spirit.game.attributes import Rarities +from spirit.game.card_effects.support_common import recover_from_discard, requires_discard + + +def _is_single_strike_energy(card): + return "Single Strike" in subtypes_for(card.archetype_id) + card = ItemCardDef( guid="83364a3b-69b7-5dca-95ca-8d8827b32d34", @@ -11,5 +17,9 @@ card = ItemCardDef( collector_number=229, set_code="SWSH6", rarity=Rarities.RareSecret, - effect=unimplemented + condition=requires_discard(_is_single_strike_energy), + effect=recover_from_discard( + _is_single_strike_energy, count=2, minimum=1, reveal=False, to="deck_shuffle", + prompt="Choose up to 2 Single Strike Energy cards to shuffle into your deck.", + ), ) diff --git a/spirit/game/scripts/cards/SWSH6/Venipede_105.py b/spirit/game/scripts/cards/SWSH6/Venipede_105.py index eec0dad..d318226 100644 --- a/spirit/game/scripts/cards/SWSH6/Venipede_105.py +++ b/spirit/game/scripts/cards/SWSH6/Venipede_105.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="082beb0f-c5ac-59d3-8ac3-e98f8089ce95", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Poisoned.", cost={PokemonTypes.DARKNESS: 1}, damage=10, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/VolcanionV_162.py b/spirit/game/scripts/cards/SWSH6/VolcanionV_162.py index 0e98dfa..aed8f9b 100644 --- a/spirit/game/scripts/cards/SWSH6/VolcanionV_162.py +++ b/spirit/game/scripts/cards/SWSH6/VolcanionV_162.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, damage_counters_on + + +def _ten_or_more_counters(ctx): + return damage_counters_on("self")(ctx) >= 10 + card = PokemonCardDef( guid="4c7ec035-54c6-5359-b5eb-256b7cb95e40", @@ -29,7 +35,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=100, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_ten_or_more_counters, 150), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/VolcanionV_25.py b/spirit/game/scripts/cards/SWSH6/VolcanionV_25.py index e088693..2ae94ee 100644 --- a/spirit/game/scripts/cards/SWSH6/VolcanionV_25.py +++ b/spirit/game/scripts/cards/SWSH6/VolcanionV_25.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, damage_counters_on + + +def _ten_or_more_counters(ctx): + return damage_counters_on("self")(ctx) >= 10 + card = PokemonCardDef( guid="b9e49930-d36c-5277-b227-301491cfac06", @@ -29,7 +35,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=100, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_ten_or_more_counters, 150), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Volcarona_24.py b/spirit/game/scripts/cards/SWSH6/Volcarona_24.py index c4a661a..0a1cf6e 100644 --- a/spirit/game/scripts/cards/SWSH6/Volcarona_24.py +++ b/spirit/game/scripts/cards/SWSH6/Volcarona_24.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import self_energy_discard_attack card = PokemonCardDef( guid="932320de-41f7-5499-9be6-f94fd5bcdcdb", @@ -29,7 +30,7 @@ card = PokemonCardDef( game_text="Discard 2 Energy from this Pok\u00e9mon.", cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=170, - effect=unimplemented, + effect=self_energy_discard_attack(count=2), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Walrein_39.py b/spirit/game/scripts/cards/SWSH6/Walrein_39.py index 4d72de1..ef6cf95 100644 --- a/spirit/game/scripts/cards/SWSH6/Walrein_39.py +++ b/spirit/game/scripts/cards/SWSH6/Walrein_39.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions + + +async def hail_prison(ctx): + """160 damage, discard 2 Energy from this Pokémon, opponent's Active is now Paralyzed.""" + await ctx.deal_damage() + await ctx.discard_energy_from(ctx.attacker, 2) + await ctx.apply_special_condition(ctx.defender, SpecialConditions.PARALYZED) + card = PokemonCardDef( guid="c210b715-c9dc-5218-bdf3-0e6eebf33c86", @@ -29,7 +37,7 @@ card = PokemonCardDef( game_text="Discard 2 Energy from this Pok\u00e9mon. Your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, damage=160, - effect=unimplemented, + effect=hail_prison, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Weavile_31.py b/spirit/game/scripts/cards/SWSH6/Weavile_31.py index 60a21d2..70df939 100644 --- a/spirit/game/scripts/cards/SWSH6/Weavile_31.py +++ b/spirit/game/scripts/cards/SWSH6/Weavile_31.py @@ -1,5 +1,36 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, subtypes_for +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.session.passives import Passive + + +class TwoHitKOPassive(Passive): + """During my next turn, a Rapid Strike attack that damages the opponent's + Active Knocks it Out outright.""" + + def __init__(self, owner_player_id): + self.owner_player_id = owner_player_id + + async def damage_interceptor(self, ctx, calc, target, carrier): + if not (calc.is_attack and calc.amount > 0 and calc.to_active): + return None + attacker = calc.attacker + if attacker is None or attacker.owning_player_id != self.owner_player_id: + return None + if target.owning_player_id == self.owner_player_id: + return None + if "Rapid Strike" not in subtypes_for(attacker.archetype_id): + return None + current = target.get_attribute(AttrID.HP, 0) + return max(calc.amount, current) + + +async def two_hit_ko(ctx): + """During your next turn, a Rapid Strike attack Knocks Out the Defending Pokemon.""" + ctx.add_passive_through_own_next_turn( + ctx.attacker, TwoHitKOPassive(ctx.player_id) + ) + card = PokemonCardDef( guid="a1e1f2b0-360d-5b4a-a94a-ded80627c938", @@ -23,13 +54,13 @@ card = PokemonCardDef( title="Two-Hit KO", game_text="During your next turn, if the Defending Pok\u00e9mon is damaged by an attack from a Rapid Strike Pok\u00e9mon, it will be Knocked Out.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=two_hit_ko, ), Attack( title="Nasty Plot", game_text="Search your deck for up to 2 cards and put them into your hand. Then, shuffle your deck.", cost={PokemonTypes.WATER: 1}, - effect=unimplemented, + effect=search_to_hand(count=2, minimum=0, reveal=False), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/WeedingGloves_155.py b/spirit/game/scripts/cards/SWSH6/WeedingGloves_155.py index aaa3ded..f1ed086 100644 --- a/spirit/game/scripts/cards/SWSH6/WeedingGloves_155.py +++ b/spirit/game/scripts/cards/SWSH6/WeedingGloves_155.py @@ -1,15 +1,16 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import PokemonToolCardDef +from spirit.game.attributes import Rarities, PokemonTypes +from spirit.game.card_effects.passives_common import typed_damage_boost_tool card = PokemonToolCardDef( guid="d139a85a-f0ea-51e2-ae30-df1e4ff28e69", key="SWSH6", name="com.direwolfdigital.cake.data.archetypes.trainer.WeedingGloves.Name", display_name="Weeding Gloves", - searchable_by=["Weeding Gloves", "Pok\u00c3\u00a9mon Tool"], - subtypes=["Pok\u00e9mon Tool"], + searchable_by=["Weeding Gloves", "Pokémon Tool"], + subtypes=["Pokémon Tool"], collector_number=155, set_code="SWSH6", rarity=Rarities.Uncommon, - effect=unimplemented + passive=typed_damage_boost_tool(PokemonTypes.GRASS, 30), ) diff --git a/spirit/game/scripts/cards/SWSH6/Weezing_95.py b/spirit/game/scripts/cards/SWSH6/Weezing_95.py index 286cb8d..6e78102 100644 --- a/spirit/game/scripts/cards/SWSH6/Weezing_95.py +++ b/spirit/game/scripts/cards/SWSH6/Weezing_95.py @@ -1,5 +1,25 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import damage_per, count_energy +from spirit.game.card_effects.support_common import attach_from_discard +from spirit.game.card_effects.pokemon import energy_provides_type + + +def is_darkness_energy(card): + return energy_provides_type(card, PokemonTypes.DARKNESS.value) + + +_attach_darkness_energy = attach_from_discard( + predicate=is_darkness_energy, count=1, target="self", minimum=1, + prompt="Choose a Darkness Energy card from your discard pile to attach", +) + + +async def mixin_toxin(ctx): + """Opponent's Active is now Confused. Attach a Darkness Energy card from discard to this Pokémon.""" + await ctx.apply_special_condition(ctx.defender, SpecialConditions.CONFUSED) + await _attach_darkness_energy(ctx) + card = PokemonCardDef( guid="665c9743-fdf0-5d83-a2c5-8ff46fd6810e", @@ -23,7 +43,7 @@ card = PokemonCardDef( title="Mixin' Toxin", game_text="Your opponent's Active Pok\u00e9mon is now Confused. Attach a Darkness Energy card from your discard pile to this Pok\u00e9mon.", cost={PokemonTypes.DARKNESS: 1}, - effect=unimplemented, + effect=mixin_toxin, ), Attack( title="Smog Burst", @@ -31,7 +51,7 @@ card = PokemonCardDef( cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("mine", energy_type=PokemonTypes.DARKNESS), 20, base=20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/WelcomingLantern_156.py b/spirit/game/scripts/cards/SWSH6/WelcomingLantern_156.py index 8c60657..4f1434e 100644 --- a/spirit/game/scripts/cards/SWSH6/WelcomingLantern_156.py +++ b/spirit/game/scripts/cards/SWSH6/WelcomingLantern_156.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef, subtypes_for from spirit.game.attributes import Rarities +from spirit.game.card_effects.support_common import recover_from_discard, requires_discard +from spirit.game.session.effects import is_supporter_card + + +def _is_single_strike_supporter(card): + return is_supporter_card(card) and "Single Strike" in subtypes_for(card.archetype_id) + card = ItemCardDef( guid="0145e34c-d02b-5668-b779-814767f610f4", @@ -11,5 +18,9 @@ card = ItemCardDef( collector_number=156, set_code="SWSH6", rarity=Rarities.Uncommon, - effect=unimplemented + condition=requires_discard(_is_single_strike_supporter), + effect=recover_from_discard( + _is_single_strike_supporter, count=1, minimum=1, reveal=False, to="hand", + prompt="Choose a Single Strike Supporter card from your discard pile.", + ), ) diff --git a/spirit/game/scripts/cards/SWSH6/WelcomingLantern_230.py b/spirit/game/scripts/cards/SWSH6/WelcomingLantern_230.py index 0646e61..b11ca5c 100644 --- a/spirit/game/scripts/cards/SWSH6/WelcomingLantern_230.py +++ b/spirit/game/scripts/cards/SWSH6/WelcomingLantern_230.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef, subtypes_for from spirit.game.attributes import Rarities +from spirit.game.card_effects.support_common import recover_from_discard, requires_discard +from spirit.game.session.effects import is_supporter_card + + +def _is_single_strike_supporter(card): + return is_supporter_card(card) and "Single Strike" in subtypes_for(card.archetype_id) + card = ItemCardDef( guid="0e2e453f-ac42-5da4-be1c-26ecea0a0b34", @@ -11,5 +18,9 @@ card = ItemCardDef( collector_number=230, set_code="SWSH6", rarity=Rarities.RareSecret, - effect=unimplemented + condition=requires_discard(_is_single_strike_supporter), + effect=recover_from_discard( + _is_single_strike_supporter, count=1, minimum=1, reveal=False, to="hand", + prompt="Choose a Single Strike Supporter card from your discard pile.", + ), ) diff --git a/spirit/game/scripts/cards/SWSH6/Whirlipede_106.py b/spirit/game/scripts/cards/SWSH6/Whirlipede_106.py index 935ab50..48f8077 100644 --- a/spirit/game/scripts/cards/SWSH6/Whirlipede_106.py +++ b/spirit/game/scripts/cards/SWSH6/Whirlipede_106.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="d5e3feb5-d37b-54f7-80da-4c889288193f", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Poisoned.", cost={PokemonTypes.DARKNESS: 1}, damage=20, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED), ), Attack( title="Rolling Tackle", diff --git a/spirit/game/scripts/cards/SWSH6/Zangoose_120.py b/spirit/game/scripts/cards/SWSH6/Zangoose_120.py index bf1286e..b2a79b5 100644 --- a/spirit/game/scripts/cards/SWSH6/Zangoose_120.py +++ b/spirit/game/scripts/cards/SWSH6/Zangoose_120.py @@ -1,5 +1,28 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, subtypes_for +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, TrainerType + + +def _rapid_strike_supporter_played(ctx): + return ctx.played_trainer_this_turn( + lambda r: r[2] == TrainerType.SUPPORTER.value + and "Rapid Strike" in subtypes_for(r[0]) + ) > 0 + + +async def gale_claws(ctx): + """50, plus 50 to 2 opposing Benched if a Rapid Strike Supporter was played this turn.""" + await ctx.deal_damage() + if not _rapid_strike_supporter_played(ctx): + return + bench = ctx.opponent_bench() + if not bench: + return + targets = await ctx.choose_cards( + bench, 2, prompt="Choose 2 of your opponent's Benched Pokémon" + ) + for target in targets: + await ctx.deal_damage(50, target=target, apply_modifiers=False) + card = PokemonCardDef( guid="78dad1b0-ff63-585a-98c9-3ff0b6ad0719", @@ -23,7 +46,7 @@ card = PokemonCardDef( game_text="If you played a Rapid Strike Supporter card from your hand during this turn, this attack also does 50 damage to 2 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.COLORLESS: 3}, damage=50, - effect=unimplemented, + effect=gale_claws, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Zarude_19.py b/spirit/game/scripts/cards/SWSH6/Zarude_19.py index 25fadba..dacc4ec 100644 --- a/spirit/game/scripts/cards/SWSH6/Zarude_19.py +++ b/spirit/game/scripts/cards/SWSH6/Zarude_19.py @@ -1,5 +1,24 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_pokemon_card + + +def _is_grass_pokemon(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_pokemon_card(card) and PokemonTypes.GRASS.value in types + + +async def pack_call(ctx): + going_second = ctx.player_id != ctx.session.first_player_id + count = 3 if going_second and ctx.session.turn_state.turn_number == 2 else 1 + picks = await ctx.search_deck( + _is_grass_pokemon, count=count, minimum=0, + prompt=f"Choose up to {count} Grass Pokémon.", + ) + await ctx.put_in_hand(picks, reveal=True) + await ctx.shuffle_deck() + card = PokemonCardDef( guid="82a37832-8311-5ff3-8dd9-f1d31cea3047", @@ -22,7 +41,7 @@ card = PokemonCardDef( title="Pack Call", game_text="Search your deck for a Grass Pok\u00e9mon, reveal it, and put it into your hand. If you go second and it's your first turn, search for up to 3 Grass Pok\u00e9mon instead of 1. Then, shuffle your deck.", cost={PokemonTypes.GRASS: 1}, - effect=unimplemented, + effect=pack_call, ), Attack( title="Repeated Whip", @@ -30,7 +49,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=60, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("self", PokemonTypes.GRASS), 20, base=60), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/Zebstrika_51.py b/spirit/game/scripts/cards/SWSH6/Zebstrika_51.py index 5f10db7..0134715 100644 --- a/spirit/game/scripts/cards/SWSH6/Zebstrika_51.py +++ b/spirit/game/scripts/cards/SWSH6/Zebstrika_51.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, subtypes_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage, bonus_if + + +def _other_rapid_strike_attacked_last_turn(ctx): + for pokemon in ctx.my_pokemon_in_play(): + if pokemon is ctx.attacker: + continue + if "Rapid Strike" in subtypes_for(pokemon.archetype_id) and \ + ctx.attack_used_last_turn(entity=pokemon): + return True + return False + card = PokemonCardDef( guid="4a94074c-03f8-5607-9fab-39f665824742", @@ -25,7 +37,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 1}, damage=30, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_other_rapid_strike_attacked_last_turn, 90), ), Attack( title="Spark Rush", @@ -33,7 +45,7 @@ card = PokemonCardDef( cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 2}, damage=90, damage_operator="x", - effect=unimplemented, + effect=flip_damage(until_tails=True, per_heads=90), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/ZeraoraV_165.py b/spirit/game/scripts/cards/SWSH6/ZeraoraV_165.py index 35d3779..20e4a9a 100644 --- a/spirit/game/scripts/cards/SWSH6/ZeraoraV_165.py +++ b/spirit/game/scripts/cards/SWSH6/ZeraoraV_165.py @@ -1,6 +1,32 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, subtypes_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +def _other_rapid_strike_attacked_last_turn(ctx): + for pokemon in ctx.my_pokemon_in_play(): + if pokemon is ctx.attacker: + continue + if "Rapid Strike" in subtypes_for(pokemon.archetype_id) and \ + ctx.attack_used_last_turn(entity=pokemon): + return True + return False + + +async def cross_fist(ctx): + """100, plus 160 to 1 opposing Benched if another Rapid Strike attacked last turn.""" + await ctx.deal_damage() + if not _other_rapid_strike_attacked_last_turn(ctx): + return + bench = ctx.opponent_bench() + if not bench: + return + target = await ctx.choose_pokemon( + bench, "Choose 1 of your opponent's Benched Pokémon" + ) + if target is not None: + await ctx.deal_damage(160, target=target, apply_modifiers=False) + + card = PokemonCardDef( guid="2610fba4-d713-58ab-b265-589a998f2c56", key="SWSH6", @@ -23,7 +49,7 @@ card = PokemonCardDef( game_text="If 1 of your other Rapid Strike Pok\u00e9mon used an attack during your last turn, this attack also does 160 damage to 1 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 2}, damage=100, - effect=unimplemented, + effect=cross_fist, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/ZeraoraV_166.py b/spirit/game/scripts/cards/SWSH6/ZeraoraV_166.py index 064ea47..339f7d4 100644 --- a/spirit/game/scripts/cards/SWSH6/ZeraoraV_166.py +++ b/spirit/game/scripts/cards/SWSH6/ZeraoraV_166.py @@ -1,6 +1,32 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, subtypes_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +def _other_rapid_strike_attacked_last_turn(ctx): + for pokemon in ctx.my_pokemon_in_play(): + if pokemon is ctx.attacker: + continue + if "Rapid Strike" in subtypes_for(pokemon.archetype_id) and \ + ctx.attack_used_last_turn(entity=pokemon): + return True + return False + + +async def cross_fist(ctx): + """100, plus 160 to 1 opposing Benched if another Rapid Strike attacked last turn.""" + await ctx.deal_damage() + if not _other_rapid_strike_attacked_last_turn(ctx): + return + bench = ctx.opponent_bench() + if not bench: + return + target = await ctx.choose_pokemon( + bench, "Choose 1 of your opponent's Benched Pokémon" + ) + if target is not None: + await ctx.deal_damage(160, target=target, apply_modifiers=False) + + card = PokemonCardDef( guid="2a503f20-414e-52ef-8a4f-26eb8ed6a5fd", key="SWSH6", @@ -23,7 +49,7 @@ card = PokemonCardDef( game_text="If 1 of your other Rapid Strike Pok\u00e9mon used an attack during your last turn, this attack also does 160 damage to 1 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 2}, damage=100, - effect=unimplemented, + effect=cross_fist, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH6/ZeraoraV_53.py b/spirit/game/scripts/cards/SWSH6/ZeraoraV_53.py index 7386b14..fe17998 100644 --- a/spirit/game/scripts/cards/SWSH6/ZeraoraV_53.py +++ b/spirit/game/scripts/cards/SWSH6/ZeraoraV_53.py @@ -1,6 +1,32 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, subtypes_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +def _other_rapid_strike_attacked_last_turn(ctx): + for pokemon in ctx.my_pokemon_in_play(): + if pokemon is ctx.attacker: + continue + if "Rapid Strike" in subtypes_for(pokemon.archetype_id) and \ + ctx.attack_used_last_turn(entity=pokemon): + return True + return False + + +async def cross_fist(ctx): + """100, plus 160 to 1 opposing Benched if another Rapid Strike attacked last turn.""" + await ctx.deal_damage() + if not _other_rapid_strike_attacked_last_turn(ctx): + return + bench = ctx.opponent_bench() + if not bench: + return + target = await ctx.choose_pokemon( + bench, "Choose 1 of your opponent's Benched Pokémon" + ) + if target is not None: + await ctx.deal_damage(160, target=target, apply_modifiers=False) + + card = PokemonCardDef( guid="b48e308d-0f44-5423-ade8-47bbe16f48c3", key="SWSH6", @@ -23,7 +49,7 @@ card = PokemonCardDef( game_text="If 1 of your other Rapid Strike Pok\u00e9mon used an attack during your last turn, this attack also does 160 damage to 1 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 2}, damage=100, - effect=unimplemented, + effect=cross_fist, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Altaria_106.py b/spirit/game/scripts/cards/SWSH7/Altaria_106.py index cadf183..82fb009 100644 --- a/spirit/game/scripts/cards/SWSH7/Altaria_106.py +++ b/spirit/game/scripts/cards/SWSH7/Altaria_106.py @@ -1,6 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.session.effects import is_supporter_card from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def tempting_tune(ctx): + """Search a Supporter, reveal it, shuffle, then put it on top of the deck.""" + picks = await ctx.search_deck( + is_supporter_card, count=1, minimum=0, + prompt="Choose a Supporter card to put on top of your deck.", + ) + if picks: + await ctx.reveal_cards(picks) + await ctx.shuffle_deck() + if picks: + await ctx.put_on_top_of_deck(picks[0]) + + card = PokemonCardDef( guid="0db247ea-d398-55fa-b13f-dda102ca3033", key="SWSH7", @@ -21,7 +36,8 @@ card = PokemonCardDef( Ability( title="Tempting Tune", game_text="Once during your turn, you may search your deck for a Supporter card, reveal it, shuffle your deck, then put that card on top of it.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + effect=tempting_tune, ), Attack( title="Glide", @@ -29,4 +45,4 @@ card = PokemonCardDef( damage=60, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH7/Ampharos_56.py b/spirit/game/scripts/cards/SWSH7/Ampharos_56.py index 46af291..54e040f 100644 --- a/spirit/game/scripts/cards/SWSH7/Ampharos_56.py +++ b/spirit/game/scripts/cards/SWSH7/Ampharos_56.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.pokemon import is_lightning_energy + + +async def electron_crush(ctx): + """100 damage; you may discard 3 Lightning Energy from this Pokémon for +120.""" + amount = 100 + if await ctx.ask_yes_no("Discard 3 Lightning Energy from this Pokémon?"): + discarded = await ctx.discard_energy_from( + ctx.attacker, 3, predicate=is_lightning_energy, + prompt="Discard 3 Lightning Energy", + ) + if discarded: + amount += 120 + await ctx.deal_damage(amount) + card = PokemonCardDef( guid="995b9e25-6841-56e5-b827-e6521eb1944c", @@ -24,7 +40,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.LIGHTNING: 1}, damage=40, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), Attack( title="Electron Crush", @@ -32,7 +48,7 @@ card = PokemonCardDef( cost={PokemonTypes.LIGHTNING: 3, PokemonTypes.COLORLESS: 1}, damage=100, damage_operator="+", - effect=unimplemented, + effect=electron_crush, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Appletun_121.py b/spirit/game/scripts/cards/SWSH7/Appletun_121.py index 707cecf..dbfbc69 100644 --- a/spirit/game/scripts/cards/SWSH7/Appletun_121.py +++ b/spirit/game/scripts/cards/SWSH7/Appletun_121.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, defender_is_v +from spirit.game.session.effects import is_special_energy + + +async def thick_mucus(ctx): + """70 for each Special Energy card attached to the opponent's Pokemon.""" + count = sum( + 1 for p in ctx.opponent_pokemon_in_play() + for e in ctx.attached_energies(p) if is_special_energy(e) + ) + await ctx.deal_damage(70 * count) card = PokemonCardDef( guid="ade82de6-2bc0-5531-bc7a-3ebc2dbd264c", @@ -24,7 +35,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 1}, damage=70, damage_operator="x", - effect=unimplemented, + effect=thick_mucus, ), Attack( title="Fighting Tackle", @@ -32,7 +43,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.FIRE: 1}, damage=80, damage_operator="+", - effect=unimplemented, + effect=bonus_if(defender_is_v, 80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/ArctovishV_176.py b/spirit/game/scripts/cards/SWSH7/ArctovishV_176.py index 92ec855..a7a538c 100644 --- a/spirit/game/scripts/cards/SWSH7/ArctovishV_176.py +++ b/spirit/game/scripts/cards/SWSH7/ArctovishV_176.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, is_pokemon_v from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import lock_defender_attacks +from spirit.game.card_effects.pokemon import is_pokemon_gx + + +async def ancient_freeze(ctx): + await ctx.deal_damage() + defender = ctx.defender + if defender is not None and ( + is_pokemon_v(defender.archetype_id) or is_pokemon_gx(defender.archetype_id) + ): + lock_defender_attacks(ctx) + card = PokemonCardDef( guid="ee12ce45-5b07-5b1c-9a65-f8373969fcc9", @@ -23,14 +35,14 @@ card = PokemonCardDef( game_text="If the Defending Pok\u00e9mon is a Pok\u00e9mon V or a Pok\u00e9mon-GX, it can't attack during your opponent's next turn.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=80, - effect=unimplemented, + effect=ancient_freeze, ), Attack( title="Giga Impact", game_text="During your next turn, this Pok\u00e9mon can't attack.", cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 2}, damage=220, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/ArctovishV_48.py b/spirit/game/scripts/cards/SWSH7/ArctovishV_48.py index d364854..e2bc383 100644 --- a/spirit/game/scripts/cards/SWSH7/ArctovishV_48.py +++ b/spirit/game/scripts/cards/SWSH7/ArctovishV_48.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, is_pokemon_v from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import lock_defender_attacks +from spirit.game.card_effects.pokemon import is_pokemon_gx + + +async def ancient_freeze(ctx): + await ctx.deal_damage() + defender = ctx.defender + if defender is not None and ( + is_pokemon_v(defender.archetype_id) or is_pokemon_gx(defender.archetype_id) + ): + lock_defender_attacks(ctx) + card = PokemonCardDef( guid="5a4dc806-2af5-5293-9020-dde3b4e5201a", @@ -23,14 +35,14 @@ card = PokemonCardDef( game_text="If the Defending Pok\u00e9mon is a Pok\u00e9mon V or a Pok\u00e9mon-GX, it can't attack during your opponent's next turn.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=80, - effect=unimplemented, + effect=ancient_freeze, ), Attack( title="Giga Impact", game_text="During your next turn, this Pok\u00e9mon can't attack.", cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 2}, damage=220, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/AromaLady_141.py b/spirit/game/scripts/cards/SWSH7/AromaLady_141.py index 37938c0..b7ba481 100644 --- a/spirit/game/scripts/cards/SWSH7/AromaLady_141.py +++ b/spirit/game/scripts/cards/SWSH7/AromaLady_141.py @@ -1,6 +1,16 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities + +async def aroma_lady(ctx): + drawn = await ctx.draw_cards(2) + if not drawn: + return + active = ctx.my_active() + if active is not None: + await ctx.cure_all_conditions(active) + + card = SupporterCardDef( guid="2f2c650c-aaeb-5f5d-9fa5-5b4c90e67c9c", key="SWSH7", @@ -11,5 +21,5 @@ card = SupporterCardDef( collector_number=141, set_code="SWSH7", rarity=Rarities.Uncommon, - effect=unimplemented + effect=aroma_lady, ) diff --git a/spirit/game/scripts/cards/SWSH7/AromaLady_199.py b/spirit/game/scripts/cards/SWSH7/AromaLady_199.py index 8d395b2..ac0faa0 100644 --- a/spirit/game/scripts/cards/SWSH7/AromaLady_199.py +++ b/spirit/game/scripts/cards/SWSH7/AromaLady_199.py @@ -1,6 +1,16 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities + +async def aroma_lady(ctx): + drawn = await ctx.draw_cards(2) + if not drawn: + return + active = ctx.my_active() + if active is not None: + await ctx.cure_all_conditions(active) + + card = SupporterCardDef( guid="6e956642-4c72-5c47-b3dd-5b9dbf033716", key="SWSH7", @@ -11,5 +21,5 @@ card = SupporterCardDef( collector_number=199, set_code="SWSH7", rarity=Rarities.RareUltra, - effect=unimplemented + effect=aroma_lady, ) diff --git a/spirit/game/scripts/cards/SWSH7/AromaLady_221.py b/spirit/game/scripts/cards/SWSH7/AromaLady_221.py index 72e8f4b..2db1335 100644 --- a/spirit/game/scripts/cards/SWSH7/AromaLady_221.py +++ b/spirit/game/scripts/cards/SWSH7/AromaLady_221.py @@ -1,6 +1,16 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities + +async def aroma_lady(ctx): + drawn = await ctx.draw_cards(2) + if not drawn: + return + active = ctx.my_active() + if active is not None: + await ctx.cure_all_conditions(active) + + card = SupporterCardDef( guid="e42fbdb0-2e1d-5c94-924c-4f866dde42ca", key="SWSH7", @@ -11,5 +21,5 @@ card = SupporterCardDef( collector_number=221, set_code="SWSH7", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=aroma_lady, ) diff --git a/spirit/game/scripts/cards/SWSH7/Avalugg_45.py b/spirit/game/scripts/cards/SWSH7/Avalugg_45.py index dd6ee49..b27027a 100644 --- a/spirit/game/scripts/cards/SWSH7/Avalugg_45.py +++ b/spirit/game/scripts/cards/SWSH7/Avalugg_45.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import protect_next_turn card = PokemonCardDef( guid="10acb2ad-7b29-524e-8aa9-222662342748", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, this Pok\u00e9mon takes 30 less damage from attacks (after applying Weakness and Resistance).", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=80, - effect=unimplemented, + effect=protect_next_turn(reduce=30), ), Attack( title="Hammer In", diff --git a/spirit/game/scripts/cards/SWSH7/Boldore_87.py b/spirit/game/scripts/cards/SWSH7/Boldore_87.py index 86c0a9e..58cd73b 100644 --- a/spirit/game/scripts/cards/SWSH7/Boldore_87.py +++ b/spirit/game/scripts/cards/SWSH7/Boldore_87.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import flip_protection card = PokemonCardDef( guid="2f68f1dc-961e-57e9-b5aa-2fe845f67ac8", @@ -21,9 +22,9 @@ card = PokemonCardDef( abilities=[ Attack( title="Protect", - game_text="Flip a coin. If heads, during your opponent's next turn, prevent all damage from and effects of attacks done to this Pok\u00e9mon.", + game_text="Flip a coin. If heads, during your opponent's next turn, prevent all damage from and effects of attacks done to this Pokémon.", cost={PokemonTypes.FIGHTING: 1}, - effect=unimplemented, + effect=flip_protection(prevent=True, effects_too=True), ), Attack( title="Boulder Crush", @@ -31,4 +32,4 @@ card = PokemonCardDef( damage=60, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH7/BoostShake_142.py b/spirit/game/scripts/cards/SWSH7/BoostShake_142.py index 671a3de..c50c09b 100644 --- a/spirit/game/scripts/cards/SWSH7/BoostShake_142.py +++ b/spirit/game/scripts/cards/SWSH7/BoostShake_142.py @@ -1,5 +1,24 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import ItemCardDef +from spirit.game.attributes import AttrID, Rarities + + +async def boost_shake(ctx): + """Search a card that evolves from 1 of your Pokemon, evolve it immediately, shuffle. Your turn ends.""" + candidates = ctx.my_pokemon_in_play() + if candidates: + target = await ctx.choose_pokemon(candidates, "Choose a Pokémon to evolve") + logic_name = target.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) if target else None + if logic_name: + picks = await ctx.search_deck( + lambda c, name=logic_name: c.get_attribute(AttrID.EVOLUTION_LOGIC_FROM) == name, + count=1, minimum=0, + prompt="Choose a card that evolves from that Pokémon.", + ) + if picks: + await ctx.evolve_pokemon(target, picks[0]) + await ctx.shuffle_deck() + ctx.ends_turn = True + card = ItemCardDef( guid="33b51511-fbd6-572b-8df8-11df705cbea4", @@ -11,5 +30,5 @@ card = ItemCardDef( collector_number=142, set_code="SWSH7", rarity=Rarities.Uncommon, - effect=unimplemented + effect=boost_shake, ) diff --git a/spirit/game/scripts/cards/SWSH7/BoostShake_229.py b/spirit/game/scripts/cards/SWSH7/BoostShake_229.py index 488e4e5..9c6facb 100644 --- a/spirit/game/scripts/cards/SWSH7/BoostShake_229.py +++ b/spirit/game/scripts/cards/SWSH7/BoostShake_229.py @@ -1,5 +1,24 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import ItemCardDef +from spirit.game.attributes import AttrID, Rarities + + +async def boost_shake(ctx): + """Search a card that evolves from 1 of your Pokemon, evolve it immediately, shuffle. Your turn ends.""" + candidates = ctx.my_pokemon_in_play() + if candidates: + target = await ctx.choose_pokemon(candidates, "Choose a Pokémon to evolve") + logic_name = target.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) if target else None + if logic_name: + picks = await ctx.search_deck( + lambda c, name=logic_name: c.get_attribute(AttrID.EVOLUTION_LOGIC_FROM) == name, + count=1, minimum=0, + prompt="Choose a card that evolves from that Pokémon.", + ) + if picks: + await ctx.evolve_pokemon(target, picks[0]) + await ctx.shuffle_deck() + ctx.ends_turn = True + card = ItemCardDef( guid="a364bf94-15e5-548a-8e10-c538dcf29d76", @@ -11,5 +30,5 @@ card = ItemCardDef( collector_number=229, set_code="SWSH7", rarity=Rarities.RareSecret, - effect=unimplemented + effect=boost_shake, ) diff --git a/spirit/game/scripts/cards/SWSH7/Braviary_137.py b/spirit/game/scripts/cards/SWSH7/Braviary_137.py index 047a625..b73c0b0 100644 --- a/spirit/game/scripts/cards/SWSH7/Braviary_137.py +++ b/spirit/game/scripts/cards/SWSH7/Braviary_137.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, active_is, recoil_attack +from spirit.game.session.effects import is_evolution_pokemon card = PokemonCardDef( guid="b5cc2269-b439-57df-ac29-b9b4826794e5", @@ -26,14 +28,14 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 1}, damage=30, damage_operator="+", - effect=unimplemented, + effect=bonus_if(active_is(is_evolution_pokemon), 60), ), Attack( title="Brave Bird", game_text="This Pok\u00e9mon also does 50 damage to itself.", cost={PokemonTypes.COLORLESS: 3}, damage=150, - effect=unimplemented, + effect=recoil_attack(50), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Copycat_143.py b/spirit/game/scripts/cards/SWSH7/Copycat_143.py index 2e80851..b0097f2 100644 --- a/spirit/game/scripts/cards/SWSH7/Copycat_143.py +++ b/spirit/game/scripts/cards/SWSH7/Copycat_143.py @@ -1,6 +1,15 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities + +async def copycat(ctx): + """Shuffle your hand into your deck. Draw a card for each card in your opponent's hand.""" + await ctx.shuffle_into_deck(ctx.hand(), ctx.player_id) + count = ctx.hand_size(ctx.opponent_id) + if count > 0: + await ctx.draw_cards(count) + + card = SupporterCardDef( guid="6a28f459-7cf7-5dad-92d7-3594ad806962", key="SWSH7", @@ -11,5 +20,5 @@ card = SupporterCardDef( collector_number=143, set_code="SWSH7", rarity=Rarities.Uncommon, - effect=unimplemented + effect=copycat ) diff --git a/spirit/game/scripts/cards/SWSH7/Copycat_200.py b/spirit/game/scripts/cards/SWSH7/Copycat_200.py index 04c243b..70b446e 100644 --- a/spirit/game/scripts/cards/SWSH7/Copycat_200.py +++ b/spirit/game/scripts/cards/SWSH7/Copycat_200.py @@ -1,6 +1,15 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities + +async def copycat(ctx): + """Shuffle your hand into your deck. Draw a card for each card in your opponent's hand.""" + await ctx.shuffle_into_deck(ctx.hand(), ctx.player_id) + count = ctx.hand_size(ctx.opponent_id) + if count > 0: + await ctx.draw_cards(count) + + card = SupporterCardDef( guid="27103790-28d2-50ac-a9f5-cd2ce490badd", key="SWSH7", @@ -11,5 +20,5 @@ card = SupporterCardDef( collector_number=200, set_code="SWSH7", rarity=Rarities.RareUltra, - effect=unimplemented + effect=copycat ) diff --git a/spirit/game/scripts/cards/SWSH7/Copycat_222.py b/spirit/game/scripts/cards/SWSH7/Copycat_222.py index 6123cc1..9b81468 100644 --- a/spirit/game/scripts/cards/SWSH7/Copycat_222.py +++ b/spirit/game/scripts/cards/SWSH7/Copycat_222.py @@ -1,6 +1,15 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities + +async def copycat(ctx): + """Shuffle your hand into your deck. Draw a card for each card in your opponent's hand.""" + await ctx.shuffle_into_deck(ctx.hand(), ctx.player_id) + count = ctx.hand_size(ctx.opponent_id) + if count > 0: + await ctx.draw_cards(count) + + card = SupporterCardDef( guid="06b5a68c-6612-5201-b505-010966316dc4", key="SWSH7", @@ -11,5 +20,5 @@ card = SupporterCardDef( collector_number=222, set_code="SWSH7", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=copycat ) diff --git a/spirit/game/scripts/cards/SWSH7/Cresselia_228.py b/spirit/game/scripts/cards/SWSH7/Cresselia_228.py index df682a4..5b21005 100644 --- a/spirit/game/scripts/cards/SWSH7/Cresselia_228.py +++ b/spirit/game/scripts/cards/SWSH7/Cresselia_228.py @@ -1,6 +1,30 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import bonus_if, count_energy +from spirit.game.card_effects.pokemon import energy_provides_type +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def crescent_glow(ctx): + """Search a Psychic Energy card and attach it to 1 of your Pokémon (up + to 3 if you go second and it's your first turn). Then, shuffle.""" + going_second = ctx.player_id != ctx.session.first_player_id + count = 3 if going_second and ctx.session.turn_state.turn_number == 2 else 1 + picks = await ctx.search_deck( + lambda c: energy_provides_type(c, PokemonTypes.PSYCHIC.value), + count=count, minimum=0, + prompt=f"Choose up to {count} Psychic Energy card(s) to attach.", + ) + if picks: + candidates = ctx.my_pokemon_in_play() + if candidates: + target = await ctx.choose_pokemon( + candidates, "Choose a Pokémon to attach the Energy to") + if target is not None: + for card in picks: + await ctx.attach_energy(card, target) + await ctx.shuffle_deck() + + card = PokemonCardDef( guid="968e81cc-ca14-5d20-9bd6-2a27a11f66c0", key="SWSH7", @@ -23,7 +47,7 @@ card = PokemonCardDef( title="Crescent Glow", game_text="Search your deck for a Psychic Energy card and attach it to 1 of your Pok\u00e9mon. If you go second and it's your first turn, instead search for up to 3 Psychic Energy cards and attach them to 1 of your Pok\u00e9mon. Then, shuffle your deck.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=crescent_glow, ), Attack( title="Photon Laser", @@ -31,7 +55,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 2}, damage=30, damage_operator="+", - effect=unimplemented, + effect=bonus_if(lambda ctx: count_energy("mine")(ctx) >= 5, 90), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Crustle_12.py b/spirit/game/scripts/cards/SWSH7/Crustle_12.py index f3840f3..ca2fd4c 100644 --- a/spirit/game/scripts/cards/SWSH7/Crustle_12.py +++ b/spirit/game/scripts/cards/SWSH7/Crustle_12.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus card = PokemonCardDef( guid="80ef6b78-9f36-52b0-9b0a-08bcbed071d3", @@ -25,7 +26,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=30, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(60), ), Attack( title="Hammer In", diff --git a/spirit/game/scripts/cards/SWSH7/Cryogonal_43.py b/spirit/game/scripts/cards/SWSH7/Cryogonal_43.py index 98b8c43..e7de3e1 100644 --- a/spirit/game/scripts/cards/SWSH7/Cryogonal_43.py +++ b/spirit/game/scripts/cards/SWSH7/Cryogonal_43.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.card_effects.support_common import look_top_attach_energy card = PokemonCardDef( guid="ceb469c7-f2c0-5390-bd69-b1478c36e342", @@ -22,7 +24,7 @@ card = PokemonCardDef( title="Element Chain", game_text="Look at the top 6 cards of your deck and attach any number of basic Energy cards you find there to your Pok\u00e9mon in any way you like. Shuffle the other cards back into your deck.", cost={PokemonTypes.WATER: 1}, - effect=unimplemented, + effect=look_top_attach_energy(6, predicate=is_basic_energy_card), ), Attack( title="Icy Snow", diff --git a/spirit/game/scripts/cards/SWSH7/CrystalCave_144.py b/spirit/game/scripts/cards/SWSH7/CrystalCave_144.py index 2c93f15..4de4f71 100644 --- a/spirit/game/scripts/cards/SWSH7/CrystalCave_144.py +++ b/spirit/game/scripts/cards/SWSH7/CrystalCave_144.py @@ -1,5 +1,34 @@ -from spirit.game.data_utils import StadiumCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import StadiumCardDef, Ability, Activations +from spirit.game.attributes import Rarities, PokemonTypes, AttrID +from spirit.game.session.passives import effective_max_hp + + +def _is_metal_or_dragon(pokemon): + types = pokemon.get_attribute(AttrID.POKEMON_TYPES) or [] + return PokemonTypes.METAL.value in types or PokemonTypes.DRAGON.value in types + + +def crystal_cave_condition(board, player_id, stadium): + return any( + _is_metal_or_dragon(p) and p.get_attribute(AttrID.HP, 0) < effective_max_hp(board, p) + for p in board.pokemon_in_play(player_id) + ) + + +async def crystal_cave_heal(ctx): + """Heal 30 damage from each of your Metal Pokémon and Dragon Pokémon.""" + for pokemon in ctx.my_pokemon_in_play(): + if _is_metal_or_dragon(pokemon): + await ctx.heal(30, target=pokemon) + + +CRYSTAL_CAVE_ABILITY = Ability( + title="Crystal Cave", + game_text="Once during each player's turn, that player may heal 30 damage from each of their Metal Pokémon and Dragon Pokémon.", + activation=Activations.ONCE_PER_TURN, + effect=crystal_cave_heal, + condition=crystal_cave_condition, +) card = StadiumCardDef( guid="1a31ac3c-69da-5dff-8aad-5a1f0e7b4811", @@ -11,5 +40,5 @@ card = StadiumCardDef( collector_number=144, set_code="SWSH7", rarity=Rarities.Uncommon, - effect=unimplemented + ability=CRYSTAL_CAVE_ABILITY ) diff --git a/spirit/game/scripts/cards/SWSH7/CrystalCave_230.py b/spirit/game/scripts/cards/SWSH7/CrystalCave_230.py index 682670f..caece4d 100644 --- a/spirit/game/scripts/cards/SWSH7/CrystalCave_230.py +++ b/spirit/game/scripts/cards/SWSH7/CrystalCave_230.py @@ -1,5 +1,34 @@ -from spirit.game.data_utils import StadiumCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import StadiumCardDef, Ability, Activations +from spirit.game.attributes import Rarities, PokemonTypes, AttrID +from spirit.game.session.passives import effective_max_hp + + +def _is_metal_or_dragon(pokemon): + types = pokemon.get_attribute(AttrID.POKEMON_TYPES) or [] + return PokemonTypes.METAL.value in types or PokemonTypes.DRAGON.value in types + + +def crystal_cave_condition(board, player_id, stadium): + return any( + _is_metal_or_dragon(p) and p.get_attribute(AttrID.HP, 0) < effective_max_hp(board, p) + for p in board.pokemon_in_play(player_id) + ) + + +async def crystal_cave_heal(ctx): + """Heal 30 damage from each of your Metal Pokémon and Dragon Pokémon.""" + for pokemon in ctx.my_pokemon_in_play(): + if _is_metal_or_dragon(pokemon): + await ctx.heal(30, target=pokemon) + + +CRYSTAL_CAVE_ABILITY = Ability( + title="Crystal Cave", + game_text="Once during each player's turn, that player may heal 30 damage from each of their Metal Pokémon and Dragon Pokémon.", + activation=Activations.ONCE_PER_TURN, + effect=crystal_cave_heal, + condition=crystal_cave_condition, +) card = StadiumCardDef( guid="f3237093-9efe-5dab-a710-3fff02597ba5", @@ -11,5 +40,5 @@ card = StadiumCardDef( collector_number=230, set_code="SWSH7", rarity=Rarities.RareSecret, - effect=unimplemented + ability=CRYSTAL_CAVE_ABILITY ) diff --git a/spirit/game/scripts/cards/SWSH7/Deino_113.py b/spirit/game/scripts/cards/SWSH7/Deino_113.py index 01cecd7..c3d755b 100644 --- a/spirit/game/scripts/cards/SWSH7/Deino_113.py +++ b/spirit/game/scripts/cards/SWSH7/Deino_113.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_bench card = PokemonCardDef( guid="50f847b3-0622-5266-838f-88c2314de5ad", @@ -21,7 +22,7 @@ card = PokemonCardDef( title="Call for Family", game_text="Search your deck for a Basic Pok\u00e9mon and put it onto your Bench. Then, shuffle your deck.", cost={PokemonTypes.DARKNESS: 1}, - effect=unimplemented, + effect=search_to_bench(), ), Attack( title="Bite", diff --git a/spirit/game/scripts/cards/SWSH7/Dialga_112.py b/spirit/game/scripts/cards/SWSH7/Dialga_112.py index 260483e..fe97218 100644 --- a/spirit/game/scripts/cards/SWSH7/Dialga_112.py +++ b/spirit/game/scripts/cards/SWSH7/Dialga_112.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, is_pokemon_v from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import lock_defender_attacks + + +async def chrono_wind(ctx): + """80 damage; if the Defending Pokemon is a Pokemon V it can't attack next turn.""" + await ctx.deal_damage() + defender = ctx.defender + if defender is not None and is_pokemon_v(defender.archetype_id): + lock_defender_attacks(ctx) + card = PokemonCardDef( guid="08afc319-a853-519a-a76d-c623406d483a", @@ -22,7 +32,7 @@ card = PokemonCardDef( game_text="If the Defending Pok\u00e9mon is a Pok\u00e9mon V, it can't attack during your opponent's next turn.", cost={PokemonTypes.COLORLESS: 3}, damage=80, - effect=unimplemented, + effect=chrono_wind, ), Attack( title="Heavy Impact", diff --git a/spirit/game/scripts/cards/SWSH7/DiggingGloves_145.py b/spirit/game/scripts/cards/SWSH7/DiggingGloves_145.py index b176d7f..effcb38 100644 --- a/spirit/game/scripts/cards/SWSH7/DiggingGloves_145.py +++ b/spirit/game/scripts/cards/SWSH7/DiggingGloves_145.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import PokemonToolCardDef +from spirit.game.attributes import PokemonTypes, Rarities +from spirit.game.card_effects.passives_common import typed_damage_boost_tool card = PokemonToolCardDef( guid="dc156368-3e76-5529-baa8-1bb8ff6a06a1", @@ -11,5 +12,5 @@ card = PokemonToolCardDef( collector_number=145, set_code="SWSH7", rarity=Rarities.Uncommon, - effect=unimplemented + passive=typed_damage_boost_tool(PokemonTypes.FIGHTING, 30), ) diff --git a/spirit/game/scripts/cards/SWSH7/DracozoltVMAX_210.py b/spirit/game/scripts/cards/SWSH7/DracozoltVMAX_210.py index 061f726..d03c143 100644 --- a/spirit/game/scripts/cards/SWSH7/DracozoltVMAX_210.py +++ b/spirit/game/scripts/cards/SWSH7/DracozoltVMAX_210.py @@ -1,6 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def spark_trap_retaliate(ctx): + """During your opponent's next turn, if this Pokémon is damaged by an + attack (even if it is Knocked Out), put 12 damage counters on the + Attacking Pokémon.""" + if not ctx.attack_used_last_turn(title="Spark Trap", entity=ctx.source): + return + attacker = ctx.damaged_by + if attacker is None: + return + await ctx.deal_damage(120, target=attacker, apply_modifiers=False, as_counters=True) + + card = PokemonCardDef( guid="2b149303-66d1-51fd-aff2-4a6ea808aae8", key="SWSH7", @@ -24,7 +37,12 @@ card = PokemonCardDef( game_text="During your opponent's next turn, if this Pok\u00e9mon is damaged by an attack (even if it is Knocked Out), put 12 damage counters on the Attacking Pok\u00e9mon.", cost={PokemonTypes.LIGHTNING: 1}, damage=60, - effect=unimplemented, + ), + Ability( + title="Spark Trap", + game_text="During your opponent's next turn, if this Pok\u00e9mon is damaged by an attack (even if it is Knocked Out), put 12 damage counters on the Attacking Pok\u00e9mon.", + trigger=Triggers.ON_DAMAGED_BY_ATTACK, + effect=spark_trap_retaliate, ), Attack( title="Max Impact", diff --git a/spirit/game/scripts/cards/SWSH7/DracozoltVMAX_59.py b/spirit/game/scripts/cards/SWSH7/DracozoltVMAX_59.py index 6ab03a4..83577d4 100644 --- a/spirit/game/scripts/cards/SWSH7/DracozoltVMAX_59.py +++ b/spirit/game/scripts/cards/SWSH7/DracozoltVMAX_59.py @@ -1,6 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def spark_trap_retaliate(ctx): + """During your opponent's next turn, if this Pokémon is damaged by an + attack (even if it is Knocked Out), put 12 damage counters on the + Attacking Pokémon.""" + if not ctx.attack_used_last_turn(title="Spark Trap", entity=ctx.source): + return + attacker = ctx.damaged_by + if attacker is None: + return + await ctx.deal_damage(120, target=attacker, apply_modifiers=False, as_counters=True) + + card = PokemonCardDef( guid="5b7b04b5-a3c5-5ccd-ba18-94478c957ca6", key="SWSH7", @@ -24,7 +37,12 @@ card = PokemonCardDef( game_text="During your opponent's next turn, if this Pok\u00e9mon is damaged by an attack (even if it is Knocked Out), put 12 damage counters on the Attacking Pok\u00e9mon.", cost={PokemonTypes.LIGHTNING: 1}, damage=60, - effect=unimplemented, + ), + Ability( + title="Spark Trap", + game_text="During your opponent's next turn, if this Pok\u00e9mon is damaged by an attack (even if it is Knocked Out), put 12 damage counters on the Attacking Pok\u00e9mon.", + trigger=Triggers.ON_DAMAGED_BY_ATTACK, + effect=spark_trap_retaliate, ), Attack( title="Max Impact", diff --git a/spirit/game/scripts/cards/SWSH7/DracozoltV_178.py b/spirit/game/scripts/cards/SWSH7/DracozoltV_178.py index 06cd60c..3ebdc2d 100644 --- a/spirit/game/scripts/cards/SWSH7/DracozoltV_178.py +++ b/spirit/game/scripts/cards/SWSH7/DracozoltV_178.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import mill_attack + + +async def primeval_beak(ctx): + """30 damage; during the opponent's next turn, Energy can't be attached + from their hand to the Defending Pokémon.""" + await ctx.deal_damage() + target = ctx.defender + if target is not None and not ctx.effects_blocked(target): + ctx.restrict_attachments(target) + card = PokemonCardDef( guid="054530ab-3af2-5f88-b55a-cf2d919d2780", @@ -20,17 +31,17 @@ card = PokemonCardDef( abilities=[ Attack( title="Primeval Beak", - game_text="During your opponent's next turn, Energy cards can't be attached from your opponent's hand to the Defending Pok\u00e9mon.", + game_text="During your opponent's next turn, Energy cards can't be attached from your opponent's hand to the Defending Pokémon.", cost={PokemonTypes.LIGHTNING: 1}, damage=30, - effect=unimplemented, + effect=primeval_beak, ), Attack( title="Mountain Swing", game_text="Discard the top 3 cards of your deck.", cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 1}, damage=180, - effect=unimplemented, + effect=mill_attack(3, opponent=False), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH7/DracozoltV_58.py b/spirit/game/scripts/cards/SWSH7/DracozoltV_58.py index 3eb9057..435f522 100644 --- a/spirit/game/scripts/cards/SWSH7/DracozoltV_58.py +++ b/spirit/game/scripts/cards/SWSH7/DracozoltV_58.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import mill_attack + + +async def primeval_beak(ctx): + """30 damage; during the opponent's next turn, Energy can't be attached + from their hand to the Defending Pokémon.""" + await ctx.deal_damage() + target = ctx.defender + if target is not None and not ctx.effects_blocked(target): + ctx.restrict_attachments(target) + card = PokemonCardDef( guid="4c752968-166d-5825-9c08-acb3f38c199d", @@ -20,17 +31,17 @@ card = PokemonCardDef( abilities=[ Attack( title="Primeval Beak", - game_text="During your opponent's next turn, Energy cards can't be attached from your opponent's hand to the Defending Pok\u00e9mon.", + game_text="During your opponent's next turn, Energy cards can't be attached from your opponent's hand to the Defending Pokémon.", cost={PokemonTypes.LIGHTNING: 1}, damage=30, - effect=unimplemented, + effect=primeval_beak, ), Attack( title="Mountain Swing", game_text="Discard the top 3 cards of your deck.", cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 1}, damage=180, - effect=unimplemented, + effect=mill_attack(3, opponent=False), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH7/DragoniteV_191.py b/spirit/game/scripts/cards/SWSH7/DragoniteV_191.py index 1d18264..dbf67a2 100644 --- a/spirit/game/scripts/cards/SWSH7/DragoniteV_191.py +++ b/spirit/game/scripts/cards/SWSH7/DragoniteV_191.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import shred, dragon_gale card = PokemonCardDef( guid="d86ebf4c-10b8-55ef-bbd8-43f951be8f46", @@ -22,14 +23,14 @@ card = PokemonCardDef( game_text="This attack's damage isn't affected by any effects on your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 2}, damage=50, - effect=unimplemented, + effect=shred, ), Attack( title="Dragon Gale", game_text="This attack also does 20 damage to each of your Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.WATER: 2, PokemonTypes.LIGHTNING: 1}, damage=250, - effect=unimplemented, + effect=dragon_gale, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Drampa_119.py b/spirit/game/scripts/cards/SWSH7/Drampa_119.py index a0a7f5e..1d75ddc 100644 --- a/spirit/game/scripts/cards/SWSH7/Drampa_119.py +++ b/spirit/game/scripts/cards/SWSH7/Drampa_119.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.attacks_common import bonus_if + + +def _bench_has_damage(ctx) -> bool: + return any(p.get_attribute(AttrID.HP, ctx.max_hp(p)) < ctx.max_hp(p) + for p in ctx.my_bench()) card = PokemonCardDef( guid="77f90310-be17-567d-89c3-bda9e52036b2", @@ -28,7 +34,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 1, PokemonTypes.FIGHTING: 1}, damage=70, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_bench_has_damage, 90), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/DreamBall_146.py b/spirit/game/scripts/cards/SWSH7/DreamBall_146.py index 7d8830e..cc0ff36 100644 --- a/spirit/game/scripts/cards/SWSH7/DreamBall_146.py +++ b/spirit/game/scripts/cards/SWSH7/DreamBall_146.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef, Ability, Triggers from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import ( + dream_ball, dream_ball_playable, dream_ball_prize_window, +) card = ItemCardDef( guid="1810e745-9155-58d2-aaea-90e36b3eeb11", @@ -11,5 +14,14 @@ card = ItemCardDef( collector_number=146, set_code="SWSH7", rarity=Rarities.Uncommon, - effect=unimplemented + effect=dream_ball, + condition=dream_ball_playable, + abilities=[ + Ability( + title="Dream Ball", + game_text="You can play this card only if you took it as a face-down Prize card, before you put it into your hand.", + trigger=Triggers.ON_TAKEN_AS_PRIZE, + effect=dream_ball_prize_window, + ), + ], ) diff --git a/spirit/game/scripts/cards/SWSH7/DuraludonVMAX_123.py b/spirit/game/scripts/cards/SWSH7/DuraludonVMAX_123.py index 0ac41a9..97189c3 100644 --- a/spirit/game/scripts/cards/SWSH7/DuraludonVMAX_123.py +++ b/spirit/game/scripts/cards/SWSH7/DuraludonVMAX_123.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import prevent_damage_when +from spirit.game.models.board import BoardState +from spirit.game.session.effects import is_special_energy + + +def _skyscraper_pred(calc, carrier): + if calc.target is not carrier: + return False + attacker = calc.attacker + return attacker is not None and any( + is_special_energy(e) for e in BoardState.attached_energies(attacker) + ) + + +async def g_max_pulverization(ctx): + await ctx.deal_damage(ignore_target_effects=True) + card = PokemonCardDef( guid="2f545f58-c496-503d-a97e-afce59cb784a", @@ -21,14 +38,14 @@ card = PokemonCardDef( Ability( title="Skyscraper", game_text="Prevent all damage done to this Pok\u00e9mon by attacks from your opponent's Pok\u00e9mon that have Special Energy attached.", - effect=unimplemented, + passive=prevent_damage_when(_skyscraper_pred), ), Attack( title="G-Max Pulverization", game_text="This attack's damage isn't affected by any effects on your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.METAL: 2}, damage=220, - effect=unimplemented, + effect=g_max_pulverization, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/DuraludonVMAX_219.py b/spirit/game/scripts/cards/SWSH7/DuraludonVMAX_219.py index 72b8a82..5db0f25 100644 --- a/spirit/game/scripts/cards/SWSH7/DuraludonVMAX_219.py +++ b/spirit/game/scripts/cards/SWSH7/DuraludonVMAX_219.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import prevent_damage_when +from spirit.game.models.board import BoardState +from spirit.game.session.effects import is_special_energy + + +def _skyscraper_pred(calc, carrier): + if calc.target is not carrier: + return False + attacker = calc.attacker + return attacker is not None and any( + is_special_energy(e) for e in BoardState.attached_energies(attacker) + ) + + +async def g_max_pulverization(ctx): + await ctx.deal_damage(ignore_target_effects=True) + card = PokemonCardDef( guid="5699247a-db0f-5428-9ac4-b9fde390dd1c", @@ -21,14 +38,14 @@ card = PokemonCardDef( Ability( title="Skyscraper", game_text="Prevent all damage done to this Pok\u00e9mon by attacks from your opponent's Pok\u00e9mon that have Special Energy attached.", - effect=unimplemented, + passive=prevent_damage_when(_skyscraper_pred), ), Attack( title="G-Max Pulverization", game_text="This attack's damage isn't affected by any effects on your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.METAL: 2}, damage=220, - effect=unimplemented, + effect=g_max_pulverization, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/DuraludonVMAX_220.py b/spirit/game/scripts/cards/SWSH7/DuraludonVMAX_220.py index 04d817c..dc0c748 100644 --- a/spirit/game/scripts/cards/SWSH7/DuraludonVMAX_220.py +++ b/spirit/game/scripts/cards/SWSH7/DuraludonVMAX_220.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import prevent_damage_when +from spirit.game.models.board import BoardState +from spirit.game.session.effects import is_special_energy + + +def _skyscraper_pred(calc, carrier): + if calc.target is not carrier: + return False + attacker = calc.attacker + return attacker is not None and any( + is_special_energy(e) for e in BoardState.attached_energies(attacker) + ) + + +async def g_max_pulverization(ctx): + await ctx.deal_damage(ignore_target_effects=True) + card = PokemonCardDef( guid="0d1b5ff2-dfd8-5a8d-beff-e59a149c4d53", @@ -21,14 +38,14 @@ card = PokemonCardDef( Ability( title="Skyscraper", game_text="Prevent all damage done to this Pok\u00e9mon by attacks from your opponent's Pok\u00e9mon that have Special Energy attached.", - effect=unimplemented, + passive=prevent_damage_when(_skyscraper_pred), ), Attack( title="G-Max Pulverization", game_text="This attack's damage isn't affected by any effects on your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.METAL: 2}, damage=220, - effect=unimplemented, + effect=g_max_pulverization, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/DuraludonV_122.py b/spirit/game/scripts/cards/SWSH7/DuraludonV_122.py index 94c48eb..5761fc5 100644 --- a/spirit/game/scripts/cards/SWSH7/DuraludonV_122.py +++ b/spirit/game/scripts/cards/SWSH7/DuraludonV_122.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import debuff_defender_attacks card = PokemonCardDef( guid="c8f7443d-d698-52d7-b3a9-6ef459c0ffd9", @@ -27,7 +28,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, the Defending Pok\u00e9mon's attacks do 30 less damage (before applying Weakness and Resistance).", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.METAL: 2}, damage=140, - effect=unimplemented, + effect=debuff_defender_attacks(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/DuraludonV_197.py b/spirit/game/scripts/cards/SWSH7/DuraludonV_197.py index 05403e0..91b94a9 100644 --- a/spirit/game/scripts/cards/SWSH7/DuraludonV_197.py +++ b/spirit/game/scripts/cards/SWSH7/DuraludonV_197.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import debuff_defender_attacks card = PokemonCardDef( guid="81f46e7c-efd9-5944-9338-facca58375c6", @@ -27,7 +28,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, the Defending Pok\u00e9mon's attacks do 30 less damage (before applying Weakness and Resistance).", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.METAL: 2}, damage=140, - effect=unimplemented, + effect=debuff_defender_attacks(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/DuraludonV_198.py b/spirit/game/scripts/cards/SWSH7/DuraludonV_198.py index 03043bd..d4b0305 100644 --- a/spirit/game/scripts/cards/SWSH7/DuraludonV_198.py +++ b/spirit/game/scripts/cards/SWSH7/DuraludonV_198.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import debuff_defender_attacks card = PokemonCardDef( guid="ba60ffbe-a51d-57b9-895c-59bd2947ed2b", @@ -27,7 +28,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, the Defending Pok\u00e9mon's attacks do 30 less damage (before applying Weakness and Resistance).", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.METAL: 2}, damage=140, - effect=unimplemented, + effect=debuff_defender_attacks(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Dwebble_11.py b/spirit/game/scripts/cards/SWSH7/Dwebble_11.py index 255efd6..d63fb8c 100644 --- a/spirit/game/scripts/cards/SWSH7/Dwebble_11.py +++ b/spirit/game/scripts/cards/SWSH7/Dwebble_11.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus card = PokemonCardDef( guid="1cba2caa-983a-5a6c-b35a-49342c57506f", @@ -29,7 +30,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=10, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Eevee_125.py b/spirit/game/scripts/cards/SWSH7/Eevee_125.py index 7631aa4..70226a2 100644 --- a/spirit/game/scripts/cards/SWSH7/Eevee_125.py +++ b/spirit/game/scripts/cards/SWSH7/Eevee_125.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, is_pokemon_v from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_pokemon_card +from spirit.game.card_effects.support_common import search_to_hand + + +def _is_pokemon_v(c): + return is_pokemon_card(c) and is_pokemon_v(c.archetype_id) + card = PokemonCardDef( guid="ab2f65e5-5448-5817-ae8e-c1a2aeed305c", @@ -22,7 +29,7 @@ card = PokemonCardDef( title="Vee-Search", game_text="Search your deck for up to 3 Pok\u00e9mon V, reveal them, and put them into your hand. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_to_hand(predicate=_is_pokemon_v, count=3, reveal=True), ), Attack( title="Stampede", diff --git a/spirit/game/scripts/cards/SWSH7/Eiscue_47.py b/spirit/game/scripts/cards/SWSH7/Eiscue_47.py index 0067bc7..c4b88e9 100644 --- a/spirit/game/scripts/cards/SWSH7/Eiscue_47.py +++ b/spirit/game/scripts/cards/SWSH7/Eiscue_47.py @@ -1,5 +1,27 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.session.passives import Passive + + +class BlockfacePassive(Passive): + """Prevent all damage from attacks by Basic Pokemon.""" + + def prevents_damage(self, calc, carrier): + return ( + calc.is_attack + and calc.is_opposing + and calc.target is carrier + and calc.attacker is not None + and calc.attacker.get_attribute(AttrID.STAGE) == PokemonStage.BASIC.value + ) + + +async def blockface(ctx): + """70. During your opponent's next turn, prevent all damage done to this + Pokemon by attacks from Basic Pokemon.""" + await ctx.deal_damage() + ctx.add_passive_through_opponents_turn(ctx.attacker, BlockfacePassive()) + card = PokemonCardDef( guid="446961a6-932f-5066-a3a1-bdc019ee4819", @@ -28,7 +50,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, prevent all damage done to this Pok\u00e9mon by attacks from Basic Pok\u00e9mon.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=70, - effect=unimplemented, + effect=blockface, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Eldegoss_16.py b/spirit/game/scripts/cards/SWSH7/Eldegoss_16.py index c8d6b3f..d223108 100644 --- a/spirit/game/scripts/cards/SWSH7/Eldegoss_16.py +++ b/spirit/game/scripts/cards/SWSH7/Eldegoss_16.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.card_effects.passives_common import protect_next_turn card = PokemonCardDef( guid="ef0a67ce-76ac-50fe-bf07-013c0604e798", @@ -22,14 +25,18 @@ card = PokemonCardDef( Ability( title="Cotton Lift", game_text="Once during your turn, you may search your deck for up to 2 basic Energy cards, reveal them, and put them into your hand. Then, shuffle your deck.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + effect=search_to_hand( + is_basic_energy_card, count=2, minimum=0, reveal=True, + prompt="Choose up to 2 basic Energy cards to put into your hand.", + ), ), Attack( title="Cotton Guard", game_text="During your opponent's next turn, this Pok\u00e9mon takes 30 less damage from attacks (after applying Weakness and Resistance).", cost={PokemonTypes.GRASS: 1}, damage=30, - effect=unimplemented, + effect=protect_next_turn(reduce=30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/ElementalBadge_147.py b/spirit/game/scripts/cards/SWSH7/ElementalBadge_147.py index 03acd63..8d09b2f 100644 --- a/spirit/game/scripts/cards/SWSH7/ElementalBadge_147.py +++ b/spirit/game/scripts/cards/SWSH7/ElementalBadge_147.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented +from spirit.game.data_utils import PokemonToolCardDef, is_pokemon_v, def_for from spirit.game.attributes import Rarities +from spirit.game.card_effects.passives_common import attack_discount_passive + +_ELEMENTAL_BADGE_NAMES = ("Vaporeon", "Jolteon", "Flareon") + + +def _elemental_badge_pred(pokemon): + if not is_pokemon_v(pokemon.archetype_id): + return False + definition = def_for(pokemon.archetype_id) + name = getattr(definition, "display_name", "") or "" + return any(n in name for n in _ELEMENTAL_BADGE_NAMES) + card = PokemonToolCardDef( guid="de6e7629-b576-54c9-8c98-cdbda7ec87fa", @@ -11,5 +23,5 @@ card = PokemonToolCardDef( collector_number=147, set_code="SWSH7", rarity=Rarities.Uncommon, - effect=unimplemented + passive=attack_discount_passive(1, pred=_elemental_badge_pred), ) diff --git a/spirit/game/scripts/cards/SWSH7/Emolga_57.py b/spirit/game/scripts/cards/SWSH7/Emolga_57.py index 3188f51..fb692b7 100644 --- a/spirit/game/scripts/cards/SWSH7/Emolga_57.py +++ b/spirit/game/scripts/cards/SWSH7/Emolga_57.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions card = PokemonCardDef( guid="9190d53c-d233-5897-b9fe-11d8c8a82f60", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.LIGHTNING: 1}, damage=30, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Entei_19.py b/spirit/game/scripts/cards/SWSH7/Entei_19.py index 83e0460..4b98148 100644 --- a/spirit/game/scripts/cards/SWSH7/Entei_19.py +++ b/spirit/game/scripts/cards/SWSH7/Entei_19.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, subtypes_for +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.attacks_common import damage_per, recoil_attack + + +def _single_strike_bench_counters(ctx) -> int: + total = 0 + for p in ctx.my_bench(): + if "Single Strike" in subtypes_for(p.archetype_id): + total += max(0, (ctx.max_hp(p) - p.get_attribute(AttrID.HP, 0)) // 10) + return total card = PokemonCardDef( guid="3a9f71bb-13e7-5436-ba45-d6fdbf7b0128", @@ -24,14 +33,14 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 1}, damage=10, damage_operator="x", - effect=unimplemented, + effect=damage_per(_single_strike_bench_counters, 10), ), Attack( title="Heat Tackle", game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/EspeonVMAX_65.py b/spirit/game/scripts/cards/SWSH7/EspeonVMAX_65.py index c01c1d0..53bc898 100644 --- a/spirit/game/scripts/cards/SWSH7/EspeonVMAX_65.py +++ b/spirit/game/scripts/cards/SWSH7/EspeonVMAX_65.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import count_energy, damage_per +from spirit.game.card_effects.passives_common import attack_effect_shield_passive +from spirit.game.models.board import BoardState + + +def _solar_revelation_protects(target, carrier): + return ( + target.owning_player_id == carrier.owning_player_id + and bool(BoardState.attached_energies(target)) + ) + card = PokemonCardDef( guid="10695936-872f-5081-ac1c-c5acd13d7400", @@ -23,7 +34,7 @@ card = PokemonCardDef( Ability( title="Solar Revelation", game_text="Prevent all effects of attacks from your opponent's Pok\u00e9mon done to all of your Pok\u00e9mon that have Energy attached.(Existing effects are not removed. Damage is not an effect.)", - effect=unimplemented, + passive=attack_effect_shield_passive(_solar_revelation_protects), ), Attack( title="Max Mindstorm", @@ -31,7 +42,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 2}, damage=60, damage_operator="x", - effect=unimplemented, + effect=damage_per(count_energy("opponent"), 60), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/EspeonV_179.py b/spirit/game/scripts/cards/SWSH7/EspeonV_179.py index 73a0bcc..f6dd863 100644 --- a/spirit/game/scripts/cards/SWSH7/EspeonV_179.py +++ b/spirit/game/scripts/cards/SWSH7/EspeonV_179.py @@ -1,6 +1,10 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import snipe_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, is_pokemon_v from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +zen_shot = snipe_attack(60, pool=lambda p: is_pokemon_v(p.archetype_id), count=1, + prompt="Choose 1 of your opponent's Pokémon V") + card = PokemonCardDef( guid="8a0de134-d2ab-5cd5-bf34-0c539c9450a3", key="SWSH7", @@ -23,7 +27,7 @@ card = PokemonCardDef( title="Zen Shot", game_text="This attack does 60 damage to 1 of your opponent's Pok\u00e9mon V. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=zen_shot, ), Attack( title="Super Psy Bolt", diff --git a/spirit/game/scripts/cards/SWSH7/EspeonV_180.py b/spirit/game/scripts/cards/SWSH7/EspeonV_180.py index e6addd2..bb4bb94 100644 --- a/spirit/game/scripts/cards/SWSH7/EspeonV_180.py +++ b/spirit/game/scripts/cards/SWSH7/EspeonV_180.py @@ -1,6 +1,10 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import snipe_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, is_pokemon_v from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +zen_shot = snipe_attack(60, pool=lambda p: is_pokemon_v(p.archetype_id), count=1, + prompt="Choose 1 of your opponent's Pokémon V") + card = PokemonCardDef( guid="aeee1e90-e43b-596c-b65a-3612e05de81b", key="SWSH7", @@ -23,7 +27,7 @@ card = PokemonCardDef( title="Zen Shot", game_text="This attack does 60 damage to 1 of your opponent's Pok\u00e9mon V. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=zen_shot, ), Attack( title="Super Psy Bolt", diff --git a/spirit/game/scripts/cards/SWSH7/EspeonV_64.py b/spirit/game/scripts/cards/SWSH7/EspeonV_64.py index 56f70db..97732f7 100644 --- a/spirit/game/scripts/cards/SWSH7/EspeonV_64.py +++ b/spirit/game/scripts/cards/SWSH7/EspeonV_64.py @@ -1,6 +1,10 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import snipe_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, is_pokemon_v from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +zen_shot = snipe_attack(60, pool=lambda p: is_pokemon_v(p.archetype_id), count=1, + prompt="Choose 1 of your opponent's Pokémon V") + card = PokemonCardDef( guid="a3ce9ccf-7ad5-5eac-b274-74d1f0648c95", key="SWSH7", @@ -23,7 +27,7 @@ card = PokemonCardDef( title="Zen Shot", game_text="This attack does 60 damage to 1 of your opponent's Pok\u00e9mon V. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=zen_shot, ), Attack( title="Super Psy Bolt", diff --git a/spirit/game/scripts/cards/SWSH7/Feebas_37.py b/spirit/game/scripts/cards/SWSH7/Feebas_37.py index dfae829..6e9cc71 100644 --- a/spirit/game/scripts/cards/SWSH7/Feebas_37.py +++ b/spirit/game/scripts/cards/SWSH7/Feebas_37.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import flip_damage +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 1}, damage=10, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=3, per_heads=10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Flaaffy_55.py b/spirit/game/scripts/cards/SWSH7/Flaaffy_55.py index 7029999..09b38f5 100644 --- a/spirit/game/scripts/cards/SWSH7/Flaaffy_55.py +++ b/spirit/game/scripts/cards/SWSH7/Flaaffy_55.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import attach_from_discard +from spirit.game.card_effects.pokemon import is_lightning_energy +from spirit.game.card_effects.passives_common import is_in_active_spot + + +def _dynamotor_condition(board, player_id, pokemon): + bench = board.find_player_area(player_id, "bench") + if not bench or not bench.children: + return False + discard = board.find_player_area(player_id, "discard") + cards = discard.children if discard else [] + return any(is_lightning_energy(c) for c in cards) + card = PokemonCardDef( guid="02d6b223-273b-548e-bdd0-e67ba16dadf6", @@ -22,7 +35,13 @@ card = PokemonCardDef( Ability( title="Dynamotor", game_text="Once during your turn (before your attack), you may attach a Lightning Energy card from your discard pile to 1 of your Benched Pok\u00e9mon.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=_dynamotor_condition, + effect=attach_from_discard( + predicate=is_lightning_energy, count=1, minimum=0, + target=lambda p: not is_in_active_spot(p), + prompt="Choose a Lightning Energy card to attach to a Benched Pok\u00e9mon", + ), ), Attack( title="Electro Ball", diff --git a/spirit/game/scripts/cards/SWSH7/Flabb_71.py b/spirit/game/scripts/cards/SWSH7/Flabb_71.py index b14d138..20d0448 100644 --- a/spirit/game/scripts/cards/SWSH7/Flabb_71.py +++ b/spirit/game/scripts/cards/SWSH7/Flabb_71.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="a9347ac2-89e4-55cd-bd60-47c258d25341", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Hypnotic Gaze", game_text="Your opponent's Active Pok\u00e9mon is now Asleep.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=condition_attack(SpecialConditions.ASLEEP), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Flapple_120.py b/spirit/game/scripts/cards/SWSH7/Flapple_120.py index 719d5cd..ac490bc 100644 --- a/spirit/game/scripts/cards/SWSH7/Flapple_120.py +++ b/spirit/game/scripts/cards/SWSH7/Flapple_120.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.attacks_common import damage_per, count_in_play, bonus_if, defender_is_v + + +def _has_ability(pokemon) -> bool: + abilities = pokemon.get_attribute(AttrID.PIE_ABILITIES) or [] + return any(isinstance(e, dict) and e.get("abilityType") in ("PokeAbility", "PokePower") + for e in abilities) + card = PokemonCardDef( guid="b37d682c-193f-5d1d-b36a-06a5eae44cc8", @@ -24,7 +32,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 1}, damage=50, damage_operator="x", - effect=unimplemented, + effect=damage_per(count_in_play("opponent", _has_ability), 50), ), Attack( title="Fighting Tackle", @@ -32,7 +40,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.FIRE: 1}, damage=80, damage_operator="+", - effect=unimplemented, + effect=bonus_if(defender_is_v, 80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/FlareonVMAX_18.py b/spirit/game/scripts/cards/SWSH7/FlareonVMAX_18.py index f1e5c7e..1c70cc6 100644 --- a/spirit/game/scripts/cards/SWSH7/FlareonVMAX_18.py +++ b/spirit/game/scripts/cards/SWSH7/FlareonVMAX_18.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import mill_scaled_damage +from spirit.game.card_effects.pokemon import is_energy_card card = PokemonCardDef( guid="edf3398f-8777-5e83-a7a1-6261b49f092e", @@ -25,7 +27,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 2}, damage=100, damage_operator="x", - effect=unimplemented, + effect=mill_scaled_damage(5, 100, pred=is_energy_card), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/FlareonV_169.py b/spirit/game/scripts/cards/SWSH7/FlareonV_169.py index b3f3b85..8818f34 100644 --- a/spirit/game/scripts/cards/SWSH7/FlareonV_169.py +++ b/spirit/game/scripts/cards/SWSH7/FlareonV_169.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.support_common import search_attach_energy +from spirit.game.card_effects.pokemon import energy_provides_type card = PokemonCardDef( guid="37d3427f-d90e-5263-9802-d5030ce5ae37", @@ -23,14 +26,17 @@ card = PokemonCardDef( game_text="Search your deck for a Fire Energy card and attach it to this Pok\u00e9mon. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, damage=20, - effect=unimplemented, + effect=search_attach_energy( + predicate=lambda c: energy_provides_type(c, PokemonTypes.FIRE.value), + count=1, to_self=True, + ), ), Attack( title="Scorching Column", game_text="Your opponent's Active Pok\u00e9mon is now Burned.", cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + effect=condition_attack(SpecialConditions.BURNED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Fletchinder_139.py b/spirit/game/scripts/cards/SWSH7/Fletchinder_139.py index 6b6dfa8..cbbc1af 100644 --- a/spirit/game/scripts/cards/SWSH7/Fletchinder_139.py +++ b/spirit/game/scripts/cards/SWSH7/Fletchinder_139.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import flip_bonus +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -26,7 +27,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Fletchling_138.py b/spirit/game/scripts/cards/SWSH7/Fletchling_138.py index 8db8fcc..ab55c80 100644 --- a/spirit/game/scripts/cards/SWSH7/Fletchling_138.py +++ b/spirit/game/scripts/cards/SWSH7/Fletchling_138.py @@ -1,6 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import flip_or_nothing +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def tailwind_draw(ctx): + await ctx.draw_cards(1) + session = ctx.session + going_second = ctx.player_id != session.first_player_id + if going_second and session.turn_state.turn_number == 2: + await ctx.draw_cards(3) + card = PokemonCardDef( guid="48f2a108-c0af-51ef-b085-cd51a5e52778", key="SWSH7", @@ -23,14 +32,14 @@ card = PokemonCardDef( title="Tailwind Draw", game_text="Draw a card. If you go second and it's your first turn, draw 3 more cards.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=tailwind_draw, ), Attack( title="Surprise Attack", game_text="Flip a coin. If tails, this attack does nothing.", cost={PokemonTypes.COLORLESS: 1}, damage=20, - effect=unimplemented, + effect=flip_or_nothing(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Floette_72.py b/spirit/game/scripts/cards/SWSH7/Floette_72.py index 1ad38b8..b730cfc 100644 --- a/spirit/game/scripts/cards/SWSH7/Floette_72.py +++ b/spirit/game/scripts/cards/SWSH7/Floette_72.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="0fae6e84-ae1d-5a57-a212-bd307f200ab1", @@ -30,7 +31,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 2}, damage=50, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=2, per_heads=50), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Florges_73.py b/spirit/game/scripts/cards/SWSH7/Florges_73.py index 07c12c7..e4431fe 100644 --- a/spirit/game/scripts/cards/SWSH7/Florges_73.py +++ b/spirit/game/scripts/cards/SWSH7/Florges_73.py @@ -1,5 +1,24 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations, subtypes_for +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack + + +def _rapid_strike_connection_condition(board, player_id, pokemon): + in_play = board.pokemon_in_play(player_id) + if not any("Rapid Strike" in subtypes_for(p.archetype_id) for p in in_play): + return False + return any(board.attached_energies(p) for p in in_play) + + +async def rapid_strike_connection(ctx): + """As often as you like: move an Energy from 1 of your Pokemon to 1 of + your Rapid Strike Pokemon.""" + sources = ctx.my_pokemon_in_play() + dests = [p for p in sources if "Rapid Strike" in subtypes_for(p.archetype_id)] + if not dests: + return + await ctx.move_energy_freely(sources, dests) + card = PokemonCardDef( guid="34694ea2-ac3d-5733-9d5f-f68214e3cb52", @@ -21,15 +40,17 @@ card = PokemonCardDef( abilities=[ Ability( title="Rapid Strike Connection", - game_text="As often as you like during your turn, you may move an Energy from 1 of your Pok\u00e9mon to 1 of your Rapid Strike Pok\u00e9mon.", - effect=unimplemented, + game_text="As often as you like during your turn, you may move an Energy from 1 of your Pokémon to 1 of your Rapid Strike Pokémon.", + activation=Activations.UNLIMITED, + condition=_rapid_strike_connection_condition, + effect=rapid_strike_connection, ), Attack( title="Wonder Shine", - game_text="Your opponent's Active Pok\u00e9mon is now Confused.", + game_text="Your opponent's Active Pokémon is now Confused.", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 2}, damage=110, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH7/Froslass_226.py b/spirit/game/scripts/cards/SWSH7/Froslass_226.py index a386d71..3b840e4 100644 --- a/spirit/game/scripts/cards/SWSH7/Froslass_226.py +++ b/spirit/game/scripts/cards/SWSH7/Froslass_226.py @@ -1,5 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.support_common import attach_from_discard +from spirit.game.card_effects.pokemon import is_energy_card +from spirit.game.card_effects.attacks_common import lock_all_attacks + + +def _is_water_energy(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_energy_card(card) and PokemonTypes.WATER.value in types + + +async def crystal_breath(ctx): + """During your next turn, this Pokemon can't attack.""" + await ctx.deal_damage() + lock_all_attacks(ctx, ctx.attacker) + card = PokemonCardDef( guid="de77f375-23ac-5e02-a9bc-4ada51a1dde9", @@ -22,14 +37,18 @@ card = PokemonCardDef( Ability( title="Frost Over", game_text="When you play this Pok\u00e9mon from your hand to evolve 1 of your Pok\u00e9mon during your turn, you may attach a Water Energy card from your discard pile to 1 of your Pok\u00e9mon.", - effect=unimplemented, + trigger=Triggers.ON_EVOLVE, + effect=attach_from_discard( + predicate=_is_water_energy, count=1, minimum=0, target="choice", + prompt="Choose a Water Energy card to attach", + ), ), Attack( title="Crystal Breath", game_text="During your next turn, this Pok\u00e9mon can't attack.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 1}, damage=90, - effect=unimplemented, + effect=crystal_breath, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/FullFaceGuard_148.py b/spirit/game/scripts/cards/SWSH7/FullFaceGuard_148.py index 02d6ced..265c18f 100644 --- a/spirit/game/scripts/cards/SWSH7/FullFaceGuard_148.py +++ b/spirit/game/scripts/cards/SWSH7/FullFaceGuard_148.py @@ -1,15 +1,37 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented +from spirit.game.data_utils import PokemonToolCardDef, def_for, Attack from spirit.game.attributes import Rarities +from spirit.game.session.passives import Passive, carrier_pokemon, ability_locked + + +def _holder_has_no_abilities(carrier, board): + pokemon = carrier_pokemon(carrier) + if pokemon is None: + return False + definition = def_for(pokemon.archetype_id) + printed = [a for a in getattr(definition, "abilities", []) if not isinstance(a, Attack)] + return not printed or ability_locked(board, pokemon) + + +class FullFaceGuardPassive(Passive): + def modify_damage_taken(self, calc, carrier): + if not (calc.is_attack and calc.is_opposing): + return + if calc.target is not carrier_pokemon(carrier): + return + if not _holder_has_no_abilities(carrier, calc.board): + return + calc.amount = max(0, calc.amount - 20) + card = PokemonToolCardDef( guid="8c94efbd-9476-5d52-aa36-69efb2783fad", key="SWSH7", name="com.direwolfdigital.cake.data.archetypes.trainer.FullFaceGuard.Name", display_name="Full Face Guard", - searchable_by=["Full Face Guard", "Item", "Pok\u00c3\u00a9mon Tool"], - subtypes=["Item", "Pok\u00e9mon Tool"], + searchable_by=["Full Face Guard", "Item", "Pokémon Tool"], + subtypes=["Item", "Pokémon Tool"], collector_number=148, set_code="SWSH7", rarity=Rarities.Uncommon, - effect=unimplemented + passive=FullFaceGuardPassive() ) diff --git a/spirit/game/scripts/cards/SWSH7/FullFaceGuard_231.py b/spirit/game/scripts/cards/SWSH7/FullFaceGuard_231.py index abdc18d..3f8d0a9 100644 --- a/spirit/game/scripts/cards/SWSH7/FullFaceGuard_231.py +++ b/spirit/game/scripts/cards/SWSH7/FullFaceGuard_231.py @@ -1,15 +1,37 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented +from spirit.game.data_utils import PokemonToolCardDef, def_for, Attack from spirit.game.attributes import Rarities +from spirit.game.session.passives import Passive, carrier_pokemon, ability_locked + + +def _holder_has_no_abilities(carrier, board): + pokemon = carrier_pokemon(carrier) + if pokemon is None: + return False + definition = def_for(pokemon.archetype_id) + printed = [a for a in getattr(definition, "abilities", []) if not isinstance(a, Attack)] + return not printed or ability_locked(board, pokemon) + + +class FullFaceGuardPassive(Passive): + def modify_damage_taken(self, calc, carrier): + if not (calc.is_attack and calc.is_opposing): + return + if calc.target is not carrier_pokemon(carrier): + return + if not _holder_has_no_abilities(carrier, calc.board): + return + calc.amount = max(0, calc.amount - 20) + card = PokemonToolCardDef( guid="dc2222c2-5869-5e8d-86c3-fe1075e49c15", key="SWSH7", name="com.direwolfdigital.cake.data.archetypes.trainer.FullFaceGuard.Name", display_name="Full Face Guard", - searchable_by=["Full Face Guard", "Item", "Pok\u00c3\u00a9mon Tool"], - subtypes=["Item", "Pok\u00e9mon Tool"], + searchable_by=["Full Face Guard", "Item", "Pokémon Tool"], + subtypes=["Item", "Pokémon Tool"], collector_number=231, set_code="SWSH7", rarity=Rarities.RareSecret, - effect=unimplemented + passive=FullFaceGuardPassive() ) diff --git a/spirit/game/scripts/cards/SWSH7/GalarianArticuno_63.py b/spirit/game/scripts/cards/SWSH7/GalarianArticuno_63.py index 7356bc4..23267fb 100644 --- a/spirit/game/scripts/cards/SWSH7/GalarianArticuno_63.py +++ b/spirit/game/scripts/cards/SWSH7/GalarianArticuno_63.py @@ -1,5 +1,42 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.pokemon import is_energy_card + + +def _is_psychic_energy(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_energy_card(card) and PokemonTypes.PSYCHIC.value in types + + +async def cruel_charge(ctx): + """On play: you may attach up to 2 Psychic Energy cards from your hand + to this Pokemon.""" + energies = [c for c in ctx.hand() if _is_psychic_energy(c)] + if not energies: + return + picks = await ctx.choose_cards( + energies, 2, minimum=0, + prompt="Choose up to 2 Psychic Energy cards to attach", + ) + for card in picks: + await ctx.attach_energy(card, ctx.source) + + +async def psylaser(ctx): + """Discard all Psychic Energy from this Pokemon. This attack does 120 + damage to 1 of your opponent's Pokemon. (Don't apply Weakness and + Resistance for Benched Pokemon.)""" + await ctx.discard_energy_from( + ctx.attacker, 99, predicate=_is_psychic_energy, + prompt="Discard all Psychic Energy from Galarian Articuno", + ) + target = await ctx.choose_pokemon( + ctx.opponent_pokemon_in_play(), + "Choose 1 of your opponent's Pokémon to take 120 damage", + ) + if target is not None: + await ctx.deal_damage(120, target=target) + card = PokemonCardDef( guid="013aa082-f0ea-5824-a357-4f63b661ecb9", @@ -22,13 +59,14 @@ card = PokemonCardDef( Ability( title="Cruel Charge", game_text="When you play this Pok\u00e9mon from your hand onto your Bench during your turn, you may attach up to 2 Psychic Energy cards from your hand to this Pok\u00e9mon.", - effect=unimplemented, + trigger=Triggers.ON_PLAY, + effect=cruel_charge, ), Attack( title="Psylaser", game_text="Discard all Psychic Energy from this Pok\u00e9mon. This attack does 120 damage to 1 of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.PSYCHIC: 2, PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=psylaser, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/GalarianMoltres_93.py b/spirit/game/scripts/cards/SWSH7/GalarianMoltres_93.py index 43d0007..56d7048 100644 --- a/spirit/game/scripts/cards/SWSH7/GalarianMoltres_93.py +++ b/spirit/game/scripts/cards/SWSH7/GalarianMoltres_93.py @@ -1,5 +1,25 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_prizes_taken +from spirit.game.card_effects.pokemon import is_energy_card, energy_provides_type + + +async def malevolent_charge(ctx): + def is_darkness_energy(c): + return is_energy_card(c) and energy_provides_type(c, PokemonTypes.DARKNESS.value) + matches = [c for c in ctx.hand() if is_darkness_energy(c)] + if not matches: + return + if not await ctx.ask_yes_no( + "Attach up to 2 Darkness Energy cards from your hand to this Pokémon?"): + return + picks = await ctx.choose_cards( + matches, 2, minimum=0, + prompt="Choose up to 2 Darkness Energy cards to attach", + ) + for card in picks: + await ctx.attach_energy(card, ctx.source) + card = PokemonCardDef( guid="55dce45d-bc5d-5ae3-993d-143d6bb8a1c0", @@ -21,7 +41,8 @@ card = PokemonCardDef( Ability( title="Malevolent Charge", game_text="When you play this Pok\u00e9mon from your hand onto your Bench during your turn, you may attach up to 2 Darkness Energy cards from your hand to this Pok\u00e9mon.", - effect=unimplemented, + trigger=Triggers.ON_PLAY, + effect=malevolent_charge, ), Attack( title="Fiery Wrath", @@ -29,7 +50,7 @@ card = PokemonCardDef( cost={PokemonTypes.DARKNESS: 2, PokemonTypes.COLORLESS: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_prizes_taken("opponent"), 50, base=20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/GalarianZapdos_82.py b/spirit/game/scripts/cards/SWSH7/GalarianZapdos_82.py index 66a1dc8..fcc2a1c 100644 --- a/spirit/game/scripts/cards/SWSH7/GalarianZapdos_82.py +++ b/spirit/game/scripts/cards/SWSH7/GalarianZapdos_82.py @@ -1,5 +1,38 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions, AttrID +from spirit.game.card_effects.pokemon import is_energy_card + + +def _is_fighting_energy(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_energy_card(card) and PokemonTypes.FIGHTING.value in types + + +async def strong_legs_charge(ctx): + """On play: you may attach up to 2 Fighting Energy cards from your hand + to this Pokemon.""" + energies = [c for c in ctx.hand() if _is_fighting_energy(c)] + if not energies: + return + picks = await ctx.choose_cards( + energies, 2, minimum=0, + prompt="Choose up to 2 Fighting Energy cards to attach", + ) + for card in picks: + await ctx.attach_energy(card, ctx.source) + + +async def zapper_kick(ctx): + """70. You may discard all Energy from this Pokemon. If you do, your + opponent's Active Pokemon is now Paralyzed.""" + await ctx.deal_damage() + energies = ctx.attached_energies(ctx.attacker) + if energies and await ctx.ask_yes_no("Discard all Energy from this Pokémon?"): + await ctx.discard_energy_from( + ctx.attacker, 99, prompt="Discard all Energy from Galarian Zapdos" + ) + await ctx.apply_special_condition(ctx.defender, SpecialConditions.PARALYZED) + card = PokemonCardDef( guid="44d1a4de-d325-5bf3-ab01-a81510979fff", @@ -20,15 +53,16 @@ card = PokemonCardDef( abilities=[ Ability( title="Strong Legs Charge", - game_text="When you play this Pok\u00e9mon from your hand onto your Bench during your turn, you may attach up to 2 Fighting Energy cards from your hand to this Pok\u00e9mon.", - effect=unimplemented, + game_text="When you play this Pokémon from your hand onto your Bench during your turn, you may attach up to 2 Fighting Energy cards from your hand to this Pokémon.", + trigger=Triggers.ON_PLAY, + effect=strong_legs_charge, ), Attack( title="Zapper Kick", - game_text="You may discard all Energy from this Pok\u00e9mon. If you do, your opponent's Active Pok\u00e9mon is now Paralyzed.", + game_text="You may discard all Energy from this Pokémon. If you do, your opponent's Active Pokémon is now Paralyzed.", cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=70, - effect=unimplemented, + effect=zapper_kick, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH7/GarbodorVMAX_101.py b/spirit/game/scripts/cards/SWSH7/GarbodorVMAX_101.py index be1de90..26813c2 100644 --- a/spirit/game/scripts/cards/SWSH7/GarbodorVMAX_101.py +++ b/spirit/game/scripts/cards/SWSH7/GarbodorVMAX_101.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.session.passives import Passive + + +class RubbishCollectingPassive(Passive): + def tool_capacity(self, pokemon, carrier): + return 2 if pokemon is carrier else 1 + card = PokemonCardDef( guid="bade01ac-aa38-5203-8d66-68dabb38892e", @@ -22,14 +30,14 @@ card = PokemonCardDef( Ability( title="Rubbish Collecting", game_text="This Pok\u00e9mon may have up to 2 Pok\u00e9mon Tools attached to it. If it loses this Ability, discard Pok\u00e9mon Tools from it until only 1 remains.", - effect=unimplemented, + passive=RubbishCollectingPassive(), ), Attack( title="G-Max Malodor", game_text="Your opponent's Active Pok\u00e9mon is now Poisoned. During your opponent's next turn, that Pok\u00e9mon can't retreat.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED, no_retreat=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/GarbodorVMAX_216.py b/spirit/game/scripts/cards/SWSH7/GarbodorVMAX_216.py index 28d5546..37172f5 100644 --- a/spirit/game/scripts/cards/SWSH7/GarbodorVMAX_216.py +++ b/spirit/game/scripts/cards/SWSH7/GarbodorVMAX_216.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.session.passives import Passive + + +class RubbishCollectingPassive(Passive): + def tool_capacity(self, pokemon, carrier): + return 2 if pokemon is carrier else 1 + card = PokemonCardDef( guid="0db6edab-04f5-57ce-adfa-ce90e8778b25", @@ -22,14 +30,14 @@ card = PokemonCardDef( Ability( title="Rubbish Collecting", game_text="This Pok\u00e9mon may have up to 2 Pok\u00e9mon Tools attached to it. If it loses this Ability, discard Pok\u00e9mon Tools from it until only 1 remains.", - effect=unimplemented, + passive=RubbishCollectingPassive(), ), Attack( title="G-Max Malodor", game_text="Your opponent's Active Pok\u00e9mon is now Poisoned. During your opponent's next turn, that Pok\u00e9mon can't retreat.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED, no_retreat=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/GarbodorV_100.py b/spirit/game/scripts/cards/SWSH7/GarbodorV_100.py index ea1b024..c565b2e 100644 --- a/spirit/game/scripts/cards/SWSH7/GarbodorV_100.py +++ b/spirit/game/scripts/cards/SWSH7/GarbodorV_100.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="514bed7f-5ea4-5267-9dc8-63f24c7a242f", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Poisoned. During your opponent's next turn, that Pok\u00e9mon can't retreat.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 1}, damage=40, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED, no_retreat=True), ), Attack( title="Sludge Bomb", diff --git a/spirit/game/scripts/cards/SWSH7/GarbodorV_190.py b/spirit/game/scripts/cards/SWSH7/GarbodorV_190.py index 9718ac6..5965059 100644 --- a/spirit/game/scripts/cards/SWSH7/GarbodorV_190.py +++ b/spirit/game/scripts/cards/SWSH7/GarbodorV_190.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="73d8ea9c-a44b-5a6f-b3dc-ef8824d12a21", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Poisoned. During your opponent's next turn, that Pok\u00e9mon can't retreat.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 1}, damage=40, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED, no_retreat=True), ), Attack( title="Sludge Bomb", diff --git a/spirit/game/scripts/cards/SWSH7/Gigalith_88.py b/spirit/game/scripts/cards/SWSH7/Gigalith_88.py index cfe83a0..b056a01 100644 --- a/spirit/game/scripts/cards/SWSH7/Gigalith_88.py +++ b/spirit/game/scripts/cards/SWSH7/Gigalith_88.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import protect_next_turn +from spirit.game.card_effects.attacks_common import damage_counters_on + + +async def pressure_shot(ctx): + """180. This Pokemon also does 10 damage to itself for each damage + counter on it.""" + await ctx.deal_damage() + counters = damage_counters_on("self")(ctx) + if counters: + await ctx.deal_damage(counters * 10, target=ctx.attacker, apply_modifiers=False) + card = PokemonCardDef( guid="e4f64fa3-e6fe-5828-9ef9-c0e8d6bfff72", @@ -24,14 +36,14 @@ card = PokemonCardDef( game_text="During your opponent's next turn, this Pok\u00e9mon takes 50 less damage from attacks (after applying Weakness and Resistance).", cost={PokemonTypes.FIGHTING: 1}, damage=50, - effect=unimplemented, + effect=protect_next_turn(reduce=50), ), Attack( title="Pressure Shot", game_text="This Pok\u00e9mon also does 10 damage to itself for each damage counter on it.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 2}, damage=180, - effect=unimplemented, + effect=pressure_shot, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/GlaceonVMAX_208.py b/spirit/game/scripts/cards/SWSH7/GlaceonVMAX_208.py index 3518fb7..cdb8b3c 100644 --- a/spirit/game/scripts/cards/SWSH7/GlaceonVMAX_208.py +++ b/spirit/game/scripts/cards/SWSH7/GlaceonVMAX_208.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, def_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import snipe_attack +from spirit.game.card_effects.passives_common import prevent_damage_when +from spirit.game.card_effects.pokemon import is_pokemon_vmax +from spirit.game.session.passives import carrier_pokemon + + +def _is_glaceon_vmax(pokemon) -> bool: + definition = def_for(pokemon.archetype_id) + return definition is not None and definition.display_name == "Glaceon VMAX" + + +CRYSTAL_VEIL_PASSIVE = prevent_damage_when( + lambda calc, carrier: carrier_pokemon(carrier) is calc.target + and is_pokemon_vmax(calc.attacker.archetype_id) + and not _is_glaceon_vmax(calc.attacker) +) card = PokemonCardDef( guid="1f989bbf-8fde-5e51-a5f4-67c18d056999", @@ -22,14 +38,14 @@ card = PokemonCardDef( Ability( title="Crystal Veil", game_text="Prevent all damage done to this Pok\u00e9mon by attacks from your opponent's Pok\u00e9mon VMAX, except any Glaceon VMAX.", - effect=unimplemented, + passive=CRYSTAL_VEIL_PASSIVE, ), Attack( title="Max Icicle", game_text="This attack also does 30 damage to 1 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=150, - effect=unimplemented, + effect=snipe_attack(30, also_base=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/GlaceonVMAX_209.py b/spirit/game/scripts/cards/SWSH7/GlaceonVMAX_209.py index f1b9ae7..812a879 100644 --- a/spirit/game/scripts/cards/SWSH7/GlaceonVMAX_209.py +++ b/spirit/game/scripts/cards/SWSH7/GlaceonVMAX_209.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, def_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import snipe_attack +from spirit.game.card_effects.passives_common import prevent_damage_when +from spirit.game.card_effects.pokemon import is_pokemon_vmax +from spirit.game.session.passives import carrier_pokemon + + +def _is_glaceon_vmax(pokemon) -> bool: + definition = def_for(pokemon.archetype_id) + return definition is not None and definition.display_name == "Glaceon VMAX" + + +CRYSTAL_VEIL_PASSIVE = prevent_damage_when( + lambda calc, carrier: carrier_pokemon(carrier) is calc.target + and is_pokemon_vmax(calc.attacker.archetype_id) + and not _is_glaceon_vmax(calc.attacker) +) card = PokemonCardDef( guid="f0338b44-0381-5301-983d-e59048561f3d", @@ -22,14 +38,14 @@ card = PokemonCardDef( Ability( title="Crystal Veil", game_text="Prevent all damage done to this Pok\u00e9mon by attacks from your opponent's Pok\u00e9mon VMAX, except any Glaceon VMAX.", - effect=unimplemented, + passive=CRYSTAL_VEIL_PASSIVE, ), Attack( title="Max Icicle", game_text="This attack also does 30 damage to 1 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=150, - effect=unimplemented, + effect=snipe_attack(30, also_base=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/GlaceonVMAX_41.py b/spirit/game/scripts/cards/SWSH7/GlaceonVMAX_41.py index 5facbb1..ad33cd9 100644 --- a/spirit/game/scripts/cards/SWSH7/GlaceonVMAX_41.py +++ b/spirit/game/scripts/cards/SWSH7/GlaceonVMAX_41.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, def_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import snipe_attack +from spirit.game.card_effects.passives_common import prevent_damage_when +from spirit.game.card_effects.pokemon import is_pokemon_vmax +from spirit.game.session.passives import carrier_pokemon + + +def _is_glaceon_vmax(pokemon) -> bool: + definition = def_for(pokemon.archetype_id) + return definition is not None and definition.display_name == "Glaceon VMAX" + + +CRYSTAL_VEIL_PASSIVE = prevent_damage_when( + lambda calc, carrier: carrier_pokemon(carrier) is calc.target + and is_pokemon_vmax(calc.attacker.archetype_id) + and not _is_glaceon_vmax(calc.attacker) +) card = PokemonCardDef( guid="3154d5b6-f781-5a79-9628-2613fb383413", @@ -22,14 +38,14 @@ card = PokemonCardDef( Ability( title="Crystal Veil", game_text="Prevent all damage done to this Pok\u00e9mon by attacks from your opponent's Pok\u00e9mon VMAX, except any Glaceon VMAX.", - effect=unimplemented, + passive=CRYSTAL_VEIL_PASSIVE, ), Attack( title="Max Icicle", game_text="This attack also does 30 damage to 1 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=150, - effect=unimplemented, + effect=snipe_attack(30, also_base=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/GlaceonV_174.py b/spirit/game/scripts/cards/SWSH7/GlaceonV_174.py index 2b5e5a6..8e9c906 100644 --- a/spirit/game/scripts/cards/SWSH7/GlaceonV_174.py +++ b/spirit/game/scripts/cards/SWSH7/GlaceonV_174.py @@ -1,5 +1,25 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID + + +async def frozen_awakening(ctx): + """Search your deck for a card that evolves from this Pokemon and evolve it. Then shuffle.""" + logic_name = ctx.source.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) + picks = await ctx.search_deck( + lambda c: logic_name and c.get_attribute(AttrID.EVOLUTION_LOGIC_FROM) == logic_name, + count=1, minimum=0, + prompt="Choose a card that evolves from this Pokémon.", + ) + if picks: + await ctx.evolve_pokemon(ctx.source, picks[0]) + await ctx.shuffle_deck() + + +async def heavy_snow(ctx): + """120. Discard a Stadium in play.""" + await ctx.deal_damage() + await ctx.discard_stadium() + card = PokemonCardDef( guid="1eab7ad0-a61b-5e65-9943-87337804002b", @@ -22,14 +42,14 @@ card = PokemonCardDef( title="Frozen Awakening", game_text="Search your deck for a card that evolves from this Pok\u00e9mon and put it onto this Pok\u00e9mon to evolve it. Then, shuffle your deck.", cost={PokemonTypes.WATER: 1}, - effect=unimplemented, + effect=frozen_awakening, ), Attack( title="Heavy Snow", game_text="Discard a Stadium in play.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=120, - effect=unimplemented, + effect=heavy_snow, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/GlaceonV_175.py b/spirit/game/scripts/cards/SWSH7/GlaceonV_175.py index 208fd30..adfab86 100644 --- a/spirit/game/scripts/cards/SWSH7/GlaceonV_175.py +++ b/spirit/game/scripts/cards/SWSH7/GlaceonV_175.py @@ -1,5 +1,25 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID + + +async def frozen_awakening(ctx): + """Search your deck for a card that evolves from this Pokemon and evolve it. Then shuffle.""" + logic_name = ctx.source.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) + picks = await ctx.search_deck( + lambda c: logic_name and c.get_attribute(AttrID.EVOLUTION_LOGIC_FROM) == logic_name, + count=1, minimum=0, + prompt="Choose a card that evolves from this Pokémon.", + ) + if picks: + await ctx.evolve_pokemon(ctx.source, picks[0]) + await ctx.shuffle_deck() + + +async def heavy_snow(ctx): + """120. Discard a Stadium in play.""" + await ctx.deal_damage() + await ctx.discard_stadium() + card = PokemonCardDef( guid="98dc82c0-94b0-59d2-86a4-2bef8247dfb1", @@ -22,14 +42,14 @@ card = PokemonCardDef( title="Frozen Awakening", game_text="Search your deck for a card that evolves from this Pok\u00e9mon and put it onto this Pok\u00e9mon to evolve it. Then, shuffle your deck.", cost={PokemonTypes.WATER: 1}, - effect=unimplemented, + effect=frozen_awakening, ), Attack( title="Heavy Snow", game_text="Discard a Stadium in play.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=120, - effect=unimplemented, + effect=heavy_snow, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/GlaceonV_40.py b/spirit/game/scripts/cards/SWSH7/GlaceonV_40.py index e7149ab..a544d9d 100644 --- a/spirit/game/scripts/cards/SWSH7/GlaceonV_40.py +++ b/spirit/game/scripts/cards/SWSH7/GlaceonV_40.py @@ -1,5 +1,25 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID + + +async def frozen_awakening(ctx): + """Search your deck for a card that evolves from this Pokemon and evolve it. Then shuffle.""" + logic_name = ctx.source.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) + picks = await ctx.search_deck( + lambda c: logic_name and c.get_attribute(AttrID.EVOLUTION_LOGIC_FROM) == logic_name, + count=1, minimum=0, + prompt="Choose a card that evolves from this Pokémon.", + ) + if picks: + await ctx.evolve_pokemon(ctx.source, picks[0]) + await ctx.shuffle_deck() + + +async def heavy_snow(ctx): + """120. Discard a Stadium in play.""" + await ctx.deal_damage() + await ctx.discard_stadium() + card = PokemonCardDef( guid="da6f9a68-b24e-5f99-bb87-f882605f99f6", @@ -22,14 +42,14 @@ card = PokemonCardDef( title="Frozen Awakening", game_text="Search your deck for a card that evolves from this Pok\u00e9mon and put it onto this Pok\u00e9mon to evolve it. Then, shuffle your deck.", cost={PokemonTypes.WATER: 1}, - effect=unimplemented, + effect=frozen_awakening, ), Attack( title="Heavy Snow", game_text="Discard a Stadium in play.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=120, - effect=unimplemented, + effect=heavy_snow, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Golduck_25.py b/spirit/game/scripts/cards/SWSH7/Golduck_25.py index 3339d1b..4367be5 100644 --- a/spirit/game/scripts/cards/SWSH7/Golduck_25.py +++ b/spirit/game/scripts/cards/SWSH7/Golduck_25.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="18a17ca8-fbb3-5cd3-9c65-d28387383d98", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Confused.", cost={PokemonTypes.COLORLESS: 1}, damage=20, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED), ), Attack( title="Surf", diff --git a/spirit/game/scripts/cards/SWSH7/GolurkV_181.py b/spirit/game/scripts/cards/SWSH7/GolurkV_181.py index fd0e3c9..786e772 100644 --- a/spirit/game/scripts/cards/SWSH7/GolurkV_181.py +++ b/spirit/game/scripts/cards/SWSH7/GolurkV_181.py @@ -1,4 +1,5 @@ from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.pokemon import rewind_beam from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -29,7 +30,7 @@ card = PokemonCardDef( game_text="If your opponent's Active Pok\u00e9mon is an evolved Pok\u00e9mon, devolve it by putting the highest Stage Evolution card on it into your opponent's hand.", cost={PokemonTypes.PSYCHIC: 2, PokemonTypes.COLORLESS: 2}, damage=180, - effect=unimplemented, + effect=rewind_beam, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/GolurkV_182.py b/spirit/game/scripts/cards/SWSH7/GolurkV_182.py index d5f254b..f12de96 100644 --- a/spirit/game/scripts/cards/SWSH7/GolurkV_182.py +++ b/spirit/game/scripts/cards/SWSH7/GolurkV_182.py @@ -1,4 +1,5 @@ from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.pokemon import rewind_beam from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -29,7 +30,7 @@ card = PokemonCardDef( game_text="If your opponent's Active Pok\u00e9mon is an evolved Pok\u00e9mon, devolve it by putting the highest Stage Evolution card on it into your opponent's hand.", cost={PokemonTypes.PSYCHIC: 2, PokemonTypes.COLORLESS: 2}, damage=180, - effect=unimplemented, + effect=rewind_beam, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/GolurkV_70.py b/spirit/game/scripts/cards/SWSH7/GolurkV_70.py index d2e7132..e913d3c 100644 --- a/spirit/game/scripts/cards/SWSH7/GolurkV_70.py +++ b/spirit/game/scripts/cards/SWSH7/GolurkV_70.py @@ -1,4 +1,5 @@ from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.pokemon import rewind_beam from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -29,7 +30,7 @@ card = PokemonCardDef( game_text="If your opponent's Active Pok\u00e9mon is an evolved Pok\u00e9mon, devolve it by putting the highest Stage Evolution card on it into your opponent's hand.", cost={PokemonTypes.PSYCHIC: 2, PokemonTypes.COLORLESS: 2}, damage=180, - effect=unimplemented, + effect=rewind_beam, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Gordie_149.py b/spirit/game/scripts/cards/SWSH7/Gordie_149.py index 738f27c..13ec720 100644 --- a/spirit/game/scripts/cards/SWSH7/Gordie_149.py +++ b/spirit/game/scripts/cards/SWSH7/Gordie_149.py @@ -1,5 +1,23 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.pokemon import is_energy_card + + +async def gordie(ctx): + """Look at the top 7 cards of your deck. You may reveal any number of + Energy cards you find there and put them into your hand. Shuffle the + other cards back into your deck.""" + top = ctx.deck_top(7) + candidates = [c for c in top if is_energy_card(c)] + if candidates: + picks = await ctx.choose_cards( + candidates, len(candidates), minimum=0, + prompt="Choose Energy cards to put into your hand.", + display_cards=top, + ) + await ctx.put_in_hand(picks, reveal=True) + await ctx.shuffle_deck() + card = SupporterCardDef( guid="114fed3f-796f-5c17-8fc3-3c77eac969e6", @@ -11,5 +29,5 @@ card = SupporterCardDef( collector_number=149, set_code="SWSH7", rarity=Rarities.Uncommon, - effect=unimplemented + effect=gordie ) diff --git a/spirit/game/scripts/cards/SWSH7/Gordie_201.py b/spirit/game/scripts/cards/SWSH7/Gordie_201.py index 73c1c5a..e8b2540 100644 --- a/spirit/game/scripts/cards/SWSH7/Gordie_201.py +++ b/spirit/game/scripts/cards/SWSH7/Gordie_201.py @@ -1,5 +1,23 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.pokemon import is_energy_card + + +async def gordie(ctx): + """Look at the top 7 cards of your deck. You may reveal any number of + Energy cards you find there and put them into your hand. Shuffle the + other cards back into your deck.""" + top = ctx.deck_top(7) + candidates = [c for c in top if is_energy_card(c)] + if candidates: + picks = await ctx.choose_cards( + candidates, len(candidates), minimum=0, + prompt="Choose Energy cards to put into your hand.", + display_cards=top, + ) + await ctx.put_in_hand(picks, reveal=True) + await ctx.shuffle_deck() + card = SupporterCardDef( guid="10ab6181-77a0-5a87-ad42-83686c81930c", @@ -11,5 +29,5 @@ card = SupporterCardDef( collector_number=201, set_code="SWSH7", rarity=Rarities.RareUltra, - effect=unimplemented + effect=gordie ) diff --git a/spirit/game/scripts/cards/SWSH7/Gordie_223.py b/spirit/game/scripts/cards/SWSH7/Gordie_223.py index fecd16c..7c27790 100644 --- a/spirit/game/scripts/cards/SWSH7/Gordie_223.py +++ b/spirit/game/scripts/cards/SWSH7/Gordie_223.py @@ -1,5 +1,23 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.pokemon import is_energy_card + + +async def gordie(ctx): + """Look at the top 7 cards of your deck. You may reveal any number of + Energy cards you find there and put them into your hand. Shuffle the + other cards back into your deck.""" + top = ctx.deck_top(7) + candidates = [c for c in top if is_energy_card(c)] + if candidates: + picks = await ctx.choose_cards( + candidates, len(candidates), minimum=0, + prompt="Choose Energy cards to put into your hand.", + display_cards=top, + ) + await ctx.put_in_hand(picks, reveal=True) + await ctx.shuffle_deck() + card = SupporterCardDef( guid="149ccb10-26c4-51cf-be95-66a30ccd9c07", @@ -11,5 +29,5 @@ card = SupporterCardDef( collector_number=223, set_code="SWSH7", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=gordie ) diff --git a/spirit/game/scripts/cards/SWSH7/Gossifleur_15.py b/spirit/game/scripts/cards/SWSH7/Gossifleur_15.py index 8775eee..aff6a51 100644 --- a/spirit/game/scripts/cards/SWSH7/Gossifleur_15.py +++ b/spirit/game/scripts/cards/SWSH7/Gossifleur_15.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import heal_attack card = PokemonCardDef( guid="954ed455-2c78-56d4-88b3-f42fea25cf78", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Heal 10 damage from this Pok\u00e9mon.", cost={PokemonTypes.GRASS: 1}, damage=10, - effect=unimplemented, + effect=heal_attack(10, target="self"), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Gourgeist_77.py b/spirit/game/scripts/cards/SWSH7/Gourgeist_77.py index 1ff4908..4d826b6 100644 --- a/spirit/game/scripts/cards/SWSH7/Gourgeist_77.py +++ b/spirit/game/scripts/cards/SWSH7/Gourgeist_77.py @@ -1,5 +1,28 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_pokemon_card + + +def _is_psychic_pokemon(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_pokemon_card(card) and PokemonTypes.PSYCHIC.value in types + + +async def pandemonium(ctx): + """Reveal the top 6 cards of your deck. This attack does 60 damage for + each Psychic Pokemon found there. Then, shuffle those Pokemon back into + your deck and discard the other cards.""" + top = ctx.deck_top(6) + if top: + await ctx.reveal_cards(top) + psychic = [c for c in top if _is_psychic_pokemon(c)] + await ctx.deal_damage(60 * len(psychic)) + others = [c for c in top if c not in psychic] + if psychic: + await ctx.shuffle_into_deck(psychic) + if others: + await ctx.discard_cards(others) + card = PokemonCardDef( guid="ecf0c178-7ba2-500d-bcaa-24627b4d12ca", @@ -26,7 +49,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=60, damage_operator="x", - effect=unimplemented, + effect=pandemonium, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/GyaradosVMAX_207.py b/spirit/game/scripts/cards/SWSH7/GyaradosVMAX_207.py index 2342ffa..6115d62 100644 --- a/spirit/game/scripts/cards/SWSH7/GyaradosVMAX_207.py +++ b/spirit/game/scripts/cards/SWSH7/GyaradosVMAX_207.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import discard_opponent_energy_attack card = PokemonCardDef( guid="b1cb2538-a51f-5c40-a17a-0897447647a0", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Discard an Energy from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + effect=discard_opponent_energy_attack(), ), Attack( title="Max Tyrant", diff --git a/spirit/game/scripts/cards/SWSH7/GyaradosVMAX_29.py b/spirit/game/scripts/cards/SWSH7/GyaradosVMAX_29.py index e0666b2..cbd21c9 100644 --- a/spirit/game/scripts/cards/SWSH7/GyaradosVMAX_29.py +++ b/spirit/game/scripts/cards/SWSH7/GyaradosVMAX_29.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import discard_opponent_energy_attack card = PokemonCardDef( guid="9108502d-07f4-59df-9562-de04430ecbf0", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Discard an Energy from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + effect=discard_opponent_energy_attack(), ), Attack( title="Max Tyrant", diff --git a/spirit/game/scripts/cards/SWSH7/GyaradosV_171.py b/spirit/game/scripts/cards/SWSH7/GyaradosV_171.py index 1e5c0ea..22a4c22 100644 --- a/spirit/game/scripts/cards/SWSH7/GyaradosV_171.py +++ b/spirit/game/scripts/cards/SWSH7/GyaradosV_171.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import damage_per, damage_counters_on +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, damage=20, damage_operator="x", - effect=unimplemented, + effect=damage_per(damage_counters_on("self"), 20), ), Attack( title="Heavy Storm", diff --git a/spirit/game/scripts/cards/SWSH7/GyaradosV_28.py b/spirit/game/scripts/cards/SWSH7/GyaradosV_28.py index 75c8386..635b84c 100644 --- a/spirit/game/scripts/cards/SWSH7/GyaradosV_28.py +++ b/spirit/game/scripts/cards/SWSH7/GyaradosV_28.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import damage_per, damage_counters_on +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, damage=20, damage_operator="x", - effect=unimplemented, + effect=damage_per(damage_counters_on("self"), 20), ), Attack( title="Heavy Storm", diff --git a/spirit/game/scripts/cards/SWSH7/Herdier_134.py b/spirit/game/scripts/cards/SWSH7/Herdier_134.py index fd65a10..6cc6c88 100644 --- a/spirit/game/scripts/cards/SWSH7/Herdier_134.py +++ b/spirit/game/scripts/cards/SWSH7/Herdier_134.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack card = PokemonCardDef( guid="7e9e3a92-e952-505a-aeea-0ebb9bc7decb", @@ -29,7 +30,7 @@ card = PokemonCardDef( game_text="This Pok\u00e9mon also does 20 damage to itself.", cost={PokemonTypes.COLORLESS: 3}, damage=80, - effect=unimplemented, + effect=recoil_attack(20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Hippowdon_85.py b/spirit/game/scripts/cards/SWSH7/Hippowdon_85.py index d78aa14..20c9a3d 100644 --- a/spirit/game/scripts/cards/SWSH7/Hippowdon_85.py +++ b/spirit/game/scripts/cards/SWSH7/Hippowdon_85.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import self_energy_discard_attack card = PokemonCardDef( guid="09e1df8c-bc42-5068-9937-729d5fad39ae", @@ -29,7 +30,7 @@ card = PokemonCardDef( game_text="Discard 2 Energy from this Pok\u00e9mon.", cost={PokemonTypes.FIGHTING: 3, PokemonTypes.COLORLESS: 1}, damage=220, - effect=unimplemented, + effect=self_energy_discard_attack(count=2), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Hitmonchan_81.py b/spirit/game/scripts/cards/SWSH7/Hitmonchan_81.py index f53c439..6a4fc12 100644 --- a/spirit/game/scripts/cards/SWSH7/Hitmonchan_81.py +++ b/spirit/game/scripts/cards/SWSH7/Hitmonchan_81.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, active_is +from spirit.game.session.effects import is_evolution_pokemon + + +async def bullet_straight_punch(ctx): + await ctx.deal_damage(ignore_resistance=True) + card = PokemonCardDef( guid="0a044504-7a6b-5b24-a6ff-66a9b0c31acd", @@ -24,14 +31,14 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=bonus_if(active_is(is_evolution_pokemon), 50, base=20), ), Attack( title="Bullet Straight Punch", game_text="This attack's damage isn't affected by Resistance.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 1}, damage=40, - effect=unimplemented, + effect=bullet_straight_punch, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Hoppip_2.py b/spirit/game/scripts/cards/SWSH7/Hoppip_2.py index 1b56646..be494aa 100644 --- a/spirit/game/scripts/cards/SWSH7/Hoppip_2.py +++ b/spirit/game/scripts/cards/SWSH7/Hoppip_2.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="9ffae970-6891-5ef8-bb76-10b0ecf626ad", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1}, damage=20, damage_operator="x", - effect=unimplemented, + effect=flip_damage(until_tails=True, per_heads=20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Hydreigon_115.py b/spirit/game/scripts/cards/SWSH7/Hydreigon_115.py index 0e5bab8..840ae20 100644 --- a/spirit/game/scripts/cards/SWSH7/Hydreigon_115.py +++ b/spirit/game/scripts/cards/SWSH7/Hydreigon_115.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per + + +def _opponent_prizes_taken_last_turn(ctx): + return ctx.session.turn_state.prizes_taken_last_turn.get(ctx.opponent_id, 0) + card = PokemonCardDef( guid="7ab3100a-7b75-5273-96b3-bb2e66a4f4dc", @@ -24,7 +30,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.DARKNESS: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=damage_per(_opponent_prizes_taken_last_turn, 100, base=20), ), Attack( title="Pitch-Black Fangs", diff --git a/spirit/game/scripts/cards/SWSH7/Hypno_62.py b/spirit/game/scripts/cards/SWSH7/Hypno_62.py index eb60ec3..52bc7dc 100644 --- a/spirit/game/scripts/cards/SWSH7/Hypno_62.py +++ b/spirit/game/scripts/cards/SWSH7/Hypno_62.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack + + +async def wake_up_slap(ctx): + """30, +90 if the Defending Pokemon is affected by a Special Condition; + then that Pokemon recovers from all Special Conditions.""" + defender = ctx.defender + affected = defender is not None and bool(defender.get_attribute(AttrID.SPECIAL_CONDITIONS)) + await ctx.deal_damage(30 + (90 if affected else 0)) + if affected: + await ctx.cure_all_conditions(defender) + card = PokemonCardDef( guid="60e4595c-f8b3-5142-a916-9f655d8d92a1", @@ -22,17 +34,17 @@ card = PokemonCardDef( abilities=[ Attack( title="Hypnosis", - game_text="Your opponent's Active Pok\u00e9mon is now Asleep.", + game_text="Your opponent's Active Pokémon is now Asleep.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=condition_attack(SpecialConditions.ASLEEP), ), Attack( title="Wake-Up Slap", - game_text="If your opponent's Active Pok\u00e9mon is affected by a Special Condition, this attack does 90 more damage. Then, that Pok\u00e9mon recovers from all Special Conditions.", + game_text="If your opponent's Active Pokémon is affected by a Special Condition, this attack does 90 more damage. Then, that Pokémon recovers from all Special Conditions.", cost={PokemonTypes.PSYCHIC: 1}, damage=30, damage_operator="+", - effect=unimplemented, + effect=wake_up_slap, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH7/Inteleon_227.py b/spirit/game/scripts/cards/SWSH7/Inteleon_227.py index 4486a03..9147097 100644 --- a/spirit/game/scripts/cards/SWSH7/Inteleon_227.py +++ b/spirit/game/scripts/cards/SWSH7/Inteleon_227.py @@ -1,6 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def quick_shooting(ctx): + """Once per turn: you may put 2 damage counters on 1 of your opponent's Pokemon.""" + if not await ctx.ask_yes_no("Put 2 damage counters on 1 of your opponent's Pokémon?"): + return + targets = ctx.opponent_pokemon_in_play() + if not targets: + return + target = await ctx.choose_pokemon(targets, "Choose 1 of your opponent's Pokémon") + if target is not None: + await ctx.deal_damage(20, target=target, apply_modifiers=False, as_counters=True) + + card = PokemonCardDef( guid="aabb4dbb-ca2d-5d66-9f48-c3dd3f7ef71a", key="SWSH7", @@ -21,8 +34,9 @@ card = PokemonCardDef( abilities=[ Ability( title="Quick Shooting", - game_text="Once during your turn, you may put 2 damage counters on 1 of your opponent's Pok\u00e9mon.", - effect=unimplemented, + game_text="Once during your turn, you may put 2 damage counters on 1 of your opponent's Pokémon.", + activation=Activations.ONCE_PER_TURN, + effect=quick_shooting, ), Attack( title="Waterfall", @@ -30,4 +44,4 @@ card = PokemonCardDef( damage=70, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH7/JolteonVMAX_51.py b/spirit/game/scripts/cards/SWSH7/JolteonVMAX_51.py index 3cdf115..1240b4e 100644 --- a/spirit/game/scripts/cards/SWSH7/JolteonVMAX_51.py +++ b/spirit/game/scripts/cards/SWSH7/JolteonVMAX_51.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities + + +async def max_thunder_rumble(ctx): + """100. Also 100 to 1 opponent's Benched Pokemon with any damage counters (no W/R).""" + await ctx.deal_damage() + bench = [p for p in ctx.opponent_bench() + if p.get_attribute(AttrID.HP, ctx.max_hp(p)) < ctx.max_hp(p)] + if not bench: + return + target = await ctx.choose_pokemon(bench, "Choose a damaged Benched Pokémon") + if target is not None: + await ctx.deal_damage(100, target=target, apply_modifiers=False) card = PokemonCardDef( guid="f7808eee-95b1-56b3-9cbb-d890fe3aa74c", @@ -24,7 +36,7 @@ card = PokemonCardDef( game_text="This attack also does 100 damage to 1 of your opponent's Benched Pok\u00e9mon that has any damage counters on it. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 1}, damage=100, - effect=unimplemented, + effect=max_thunder_rumble, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/JolteonV_177.py b/spirit/game/scripts/cards/SWSH7/JolteonV_177.py index 82e8e7a..c4d297c 100644 --- a/spirit/game/scripts/cards/SWSH7/JolteonV_177.py +++ b/spirit/game/scripts/cards/SWSH7/JolteonV_177.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage, snipe_attack card = PokemonCardDef( guid="d83e599d-ff36-59e0-8fa9-bfc406c83c85", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Thunder Spear", game_text="This attack does 20 damage to 1 of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=snipe_attack(20, pool="any", count=1), ), Attack( title="Pin Missile", @@ -30,7 +31,7 @@ card = PokemonCardDef( cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 2}, damage=60, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=4, per_heads=60), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Jumpluff_4.py b/spirit/game/scripts/cards/SWSH7/Jumpluff_4.py index 64817c4..79dded3 100644 --- a/spirit/game/scripts/cards/SWSH7/Jumpluff_4.py +++ b/spirit/game/scripts/cards/SWSH7/Jumpluff_4.py @@ -1,4 +1,5 @@ from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.pokemon import FluffyBarragePassive from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -22,7 +23,7 @@ card = PokemonCardDef( Ability( title="Fluffy Barrage", game_text="This Pok\u00e9mon may attack twice each turn. If the first attack Knocks Out your opponent's Active Pok\u00e9mon, you may attack again after your opponent chooses a new Active Pok\u00e9mon.", - effect=unimplemented, + passive=FluffyBarragePassive(), ), Attack( title="Spinning Attack", diff --git a/spirit/game/scripts/cards/SWSH7/Kyurem_116.py b/spirit/game/scripts/cards/SWSH7/Kyurem_116.py index 5c89cdd..f6a2e56 100644 --- a/spirit/game/scripts/cards/SWSH7/Kyurem_116.py +++ b/spirit/game/scripts/cards/SWSH7/Kyurem_116.py @@ -1,5 +1,27 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import energy_provides_type + + +async def extreme_freeze(ctx): + """Discard any amount of Water Energy from your Pokémon; 60 damage for + each card discarded this way.""" + candidates = [ + e for p in ctx.my_pokemon_in_play() + for e in ctx.attached_energies(p) + if energy_provides_type(e, PokemonTypes.WATER.value) + ] + if not candidates: + return + picks = await ctx.choose_cards( + candidates, len(candidates), minimum=0, + prompt="Choose any amount of Water Energy to discard", + ) + if not picks: + return + await ctx.discard_cards(picks) + await ctx.deal_damage(60 * len(picks)) + card = PokemonCardDef( guid="3f309371-5702-5f03-9019-fe2863ce729d", @@ -23,7 +45,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 2, PokemonTypes.METAL: 1}, damage=60, damage_operator="x", - effect=unimplemented, + effect=extreme_freeze, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Lanturn_53.py b/spirit/game/scripts/cards/SWSH7/Lanturn_53.py index 2f7a427..18f5283 100644 --- a/spirit/game/scripts/cards/SWSH7/Lanturn_53.py +++ b/spirit/game/scripts/cards/SWSH7/Lanturn_53.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import smokescreen_attack card = PokemonCardDef( guid="4675df0f-5f04-5fb8-940b-e3693f3a4770", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, if the Defending Pok\u00e9mon tries to attack, your opponent flips a coin. If tails, that attack doesn't happen.", cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 1}, damage=40, - effect=unimplemented, + effect=smokescreen_attack(), ), Attack( title="Electro Ball", diff --git a/spirit/game/scripts/cards/SWSH7/LeafeonVMAX_204.py b/spirit/game/scripts/cards/SWSH7/LeafeonVMAX_204.py index bc4450c..9c24151 100644 --- a/spirit/game/scripts/cards/SWSH7/LeafeonVMAX_204.py +++ b/spirit/game/scripts/cards/SWSH7/LeafeonVMAX_204.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per +from spirit.game.card_effects.support_common import heal_attack + + +def _defender_retreat_cost(ctx): + active = ctx.opponent_active() + return int(active.get_attribute(AttrID.RETREAT_COST) or 0) if active else 0 card = PokemonCardDef( guid="a6c0a11e-a591-55c5-b2e3-283d849e9298", @@ -25,14 +32,14 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=60, damage_operator="x", - effect=unimplemented, + effect=damage_per(_defender_retreat_cost, 60), ), Attack( title="Max Leaf", game_text="Heal 30 damage from this Pok\u00e9mon.", cost={PokemonTypes.GRASS: 2, PokemonTypes.COLORLESS: 1}, damage=170, - effect=unimplemented, + effect=heal_attack(30, target="self"), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/LeafeonVMAX_205.py b/spirit/game/scripts/cards/SWSH7/LeafeonVMAX_205.py index c7a6768..cf1f6b2 100644 --- a/spirit/game/scripts/cards/SWSH7/LeafeonVMAX_205.py +++ b/spirit/game/scripts/cards/SWSH7/LeafeonVMAX_205.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per +from spirit.game.card_effects.support_common import heal_attack + + +def _defender_retreat_cost(ctx): + active = ctx.opponent_active() + return int(active.get_attribute(AttrID.RETREAT_COST) or 0) if active else 0 card = PokemonCardDef( guid="602d8f29-4016-51df-8598-d3094cf3d95b", @@ -25,14 +32,14 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=60, damage_operator="x", - effect=unimplemented, + effect=damage_per(_defender_retreat_cost, 60), ), Attack( title="Max Leaf", game_text="Heal 30 damage from this Pok\u00e9mon.", cost={PokemonTypes.GRASS: 2, PokemonTypes.COLORLESS: 1}, damage=170, - effect=unimplemented, + effect=heal_attack(30, target="self"), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/LeafeonVMAX_8.py b/spirit/game/scripts/cards/SWSH7/LeafeonVMAX_8.py index cd0ce14..220065b 100644 --- a/spirit/game/scripts/cards/SWSH7/LeafeonVMAX_8.py +++ b/spirit/game/scripts/cards/SWSH7/LeafeonVMAX_8.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per +from spirit.game.card_effects.support_common import heal_attack + + +def _defender_retreat_cost(ctx): + active = ctx.opponent_active() + return int(active.get_attribute(AttrID.RETREAT_COST) or 0) if active else 0 card = PokemonCardDef( guid="ef02857d-8e3e-57d9-9d05-0faecc5ad47e", @@ -25,14 +32,14 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=60, damage_operator="x", - effect=unimplemented, + effect=damage_per(_defender_retreat_cost, 60), ), Attack( title="Max Leaf", game_text="Heal 30 damage from this Pok\u00e9mon.", cost={PokemonTypes.GRASS: 2, PokemonTypes.COLORLESS: 1}, damage=170, - effect=unimplemented, + effect=heal_attack(30, target="self"), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/LeafeonV_166.py b/spirit/game/scripts/cards/SWSH7/LeafeonV_166.py index f5d190f..64bf4e2 100644 --- a/spirit/game/scripts/cards/SWSH7/LeafeonV_166.py +++ b/spirit/game/scripts/cards/SWSH7/LeafeonV_166.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus +from spirit.game.card_effects.support_common import search_attach_energy +from spirit.game.card_effects.trainers import is_grass_energy_card card = PokemonCardDef( guid="1fc0107e-9d80-5934-b5b5-2eaa9b53e163", @@ -21,7 +24,11 @@ card = PokemonCardDef( Ability( title="Greening Cells", game_text="Once during your turn, you may search your deck for a Grass Energy card and attach it to 1 of your Pok\u00e9mon. Then, shuffle your deck. If you use this Ability, your turn ends.", - effect=unimplemented, + ends_turn=True, + effect=search_attach_energy( + predicate=is_grass_energy_card, count=1, distribute=False, + prompt="Choose a Grass Energy card to attach.", + ), ), Attack( title="Leaf Blade", @@ -29,7 +36,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 2}, damage=90, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(60), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/LeafeonV_167.py b/spirit/game/scripts/cards/SWSH7/LeafeonV_167.py index 158bb38..6555ab8 100644 --- a/spirit/game/scripts/cards/SWSH7/LeafeonV_167.py +++ b/spirit/game/scripts/cards/SWSH7/LeafeonV_167.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus +from spirit.game.card_effects.support_common import search_attach_energy +from spirit.game.card_effects.trainers import is_grass_energy_card card = PokemonCardDef( guid="61328c09-6694-5cb9-995c-5fe41a32f173", @@ -21,7 +24,11 @@ card = PokemonCardDef( Ability( title="Greening Cells", game_text="Once during your turn, you may search your deck for a Grass Energy card and attach it to 1 of your Pok\u00e9mon. Then, shuffle your deck. If you use this Ability, your turn ends.", - effect=unimplemented, + ends_turn=True, + effect=search_attach_energy( + predicate=is_grass_energy_card, count=1, distribute=False, + prompt="Choose a Grass Energy card to attach.", + ), ), Attack( title="Leaf Blade", @@ -29,7 +36,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 2}, damage=90, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(60), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/LeafeonV_7.py b/spirit/game/scripts/cards/SWSH7/LeafeonV_7.py index e077f77..13bd0b7 100644 --- a/spirit/game/scripts/cards/SWSH7/LeafeonV_7.py +++ b/spirit/game/scripts/cards/SWSH7/LeafeonV_7.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus +from spirit.game.card_effects.support_common import search_attach_energy +from spirit.game.card_effects.trainers import is_grass_energy_card card = PokemonCardDef( guid="4128106f-9a3f-5a6a-9c6c-0d2033bcc454", @@ -21,7 +24,11 @@ card = PokemonCardDef( Ability( title="Greening Cells", game_text="Once during your turn, you may search your deck for a Grass Energy card and attach it to 1 of your Pok\u00e9mon. Then, shuffle your deck. If you use this Ability, your turn ends.", - effect=unimplemented, + ends_turn=True, + effect=search_attach_energy( + predicate=is_grass_energy_card, count=1, distribute=False, + prompt="Choose a Grass Energy card to attach.", + ), ), Attack( title="Leaf Blade", @@ -29,7 +36,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 2}, damage=90, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(60), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Lilligant_10.py b/spirit/game/scripts/cards/SWSH7/Lilligant_10.py index a970f1a..e0664fd 100644 --- a/spirit/game/scripts/cards/SWSH7/Lilligant_10.py +++ b/spirit/game/scripts/cards/SWSH7/Lilligant_10.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="f50902e5-58a9-55be-9a01-66861e98dd89", @@ -24,7 +25,10 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Asleep. If tails, your opponent's Active Pok\u00e9mon is now Confused.", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=70, - effect=unimplemented, + effect=condition_attack( + SpecialConditions.ASLEEP, flip=True, + tails_conditions=(SpecialConditions.CONFUSED,), + ), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Lillipup_133.py b/spirit/game/scripts/cards/SWSH7/Lillipup_133.py index 5033dec..dc62270 100644 --- a/spirit/game/scripts/cards/SWSH7/Lillipup_133.py +++ b/spirit/game/scripts/cards/SWSH7/Lillipup_133.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_supporter_card +from spirit.game.card_effects.support_common import search_to_hand card = PokemonCardDef( guid="78dc570b-3f2c-5266-bb59-672dd54b04ff", @@ -22,7 +24,8 @@ card = PokemonCardDef( title="Lead", game_text="Search your deck for a Supporter card, reveal it, and put it into your hand. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_to_hand(is_supporter_card, count=1, reveal=True, + prompt="Choose a Supporter card."), ), Attack( title="Tackle", diff --git a/spirit/game/scripts/cards/SWSH7/Lotad_32.py b/spirit/game/scripts/cards/SWSH7/Lotad_32.py index 591d473..860ef02 100644 --- a/spirit/game/scripts/cards/SWSH7/Lotad_32.py +++ b/spirit/game/scripts/cards/SWSH7/Lotad_32.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_bench card = PokemonCardDef( guid="c6941739-e110-549b-ac57-4362a3375b9d", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Call for Family", game_text="Search your deck for up to 2 Basic Pok\u00e9mon and put them onto your Bench. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_to_bench(count=2), ), Attack( title="Rain Splash", diff --git a/spirit/game/scripts/cards/SWSH7/LuckyIcePop_150.py b/spirit/game/scripts/cards/SWSH7/LuckyIcePop_150.py index 5c31ae1..5c3f4e1 100644 --- a/spirit/game/scripts/cards/SWSH7/LuckyIcePop_150.py +++ b/spirit/game/scripts/cards/SWSH7/LuckyIcePop_150.py @@ -1,6 +1,18 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef from spirit.game.attributes import Rarities + +async def lucky_ice_pop_effect(ctx): + """Heal 20 from your Active; if any damage was healed, flip - heads returns + this card to hand instead of the discard pile.""" + healed = await ctx.heal(20) + if healed <= 0: + return + heads = (await ctx.flip_coins(1, "Lucky Ice Pop"))[0] + if heads: + await ctx.put_in_hand([ctx.source], reveal=False) + + card = ItemCardDef( guid="f4b60669-ccfa-5017-8a9c-0f9a84b2ba98", key="SWSH7", @@ -11,5 +23,5 @@ card = ItemCardDef( collector_number=150, set_code="SWSH7", rarity=Rarities.Uncommon, - effect=unimplemented + effect=lucky_ice_pop_effect ) diff --git a/spirit/game/scripts/cards/SWSH7/Ludicolo_34.py b/spirit/game/scripts/cards/SWSH7/Ludicolo_34.py index 6c8a3bd..fed549a 100644 --- a/spirit/game/scripts/cards/SWSH7/Ludicolo_34.py +++ b/spirit/game/scripts/cards/SWSH7/Ludicolo_34.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.passives import TurnDamageModifier + + +async def enthusiastic_dance(ctx): + """On evolve: you may buff your Basic Pokemon's attacks +100 this turn.""" + if await ctx.ask_yes_no( + "Use Enthusiastic Dance? Your Basic Pokémon's attacks do 100 more " + "damage to your opponent's Active Pokémon this turn." + ): + ctx.add_turn_damage_modifier( + TurnDamageModifier(100, ctx.player_id, requires_subtype="Basic") + ) + card = PokemonCardDef( guid="cf8f094b-19f2-5ba9-b7ee-32449319a97b", @@ -22,7 +35,8 @@ card = PokemonCardDef( Ability( title="Enthusiastic Dance", game_text="When you play this Pok\u00e9mon from your hand to evolve 1 of your Pok\u00e9mon during your turn, you may use this Ability. During this turn, your Basic Pok\u00e9mon's attacks do 100 more damage to your opponent's Active Pok\u00e9mon (before applying Weakness and Resistance).", - effect=unimplemented, + trigger=Triggers.ON_EVOLVE, + effect=enthusiastic_dance, ), Attack( title="Wave Splash", diff --git a/spirit/game/scripts/cards/SWSH7/Luvdisc_39.py b/spirit/game/scripts/cards/SWSH7/Luvdisc_39.py index 553eb84..8935914 100644 --- a/spirit/game/scripts/cards/SWSH7/Luvdisc_39.py +++ b/spirit/game/scripts/cards/SWSH7/Luvdisc_39.py @@ -1,6 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def synchrodraw(ctx): + """Shuffle your hand into your deck. Then, draw a card for each card in + your opponent's hand.""" + draw_count = ctx.hand_size(ctx.opponent_id) + await ctx.shuffle_into_deck(ctx.hand(), ctx.player_id) + await ctx.draw_cards(draw_count) + + card = PokemonCardDef( guid="c247656d-7fa8-57db-9178-7dff92d38c86", key="SWSH7", @@ -22,7 +31,7 @@ card = PokemonCardDef( title="Synchrodraw", game_text="Shuffle your hand into your deck. Then, draw a card for each card in your opponent's hand.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=synchrodraw, ), Attack( title="Water Gun", diff --git a/spirit/game/scripts/cards/SWSH7/LycanrocVMAX_213.py b/spirit/game/scripts/cards/SWSH7/LycanrocVMAX_213.py index 7c8a959..1fdda08 100644 --- a/spirit/game/scripts/cards/SWSH7/LycanrocVMAX_213.py +++ b/spirit/game/scripts/cards/SWSH7/LycanrocVMAX_213.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.attacks_common import snipe_attack + + +async def hunting_claw(ctx): + """Knock Out 1 of your opponent's Pokemon in play that has 60 HP or less remaining.""" + candidates = [ + p for p in ctx.opponent_pokemon_in_play() + if p.get_attribute(AttrID.HP, 0) <= 60 + ] + if not candidates: + return + target = await ctx.choose_pokemon( + candidates, "Choose 1 of your opponent's Pokémon with 60 HP or less remaining" + ) + if target is not None: + await ctx.knock_out(target) + card = PokemonCardDef( guid="63091c42-7636-53b0-a3e6-c4eecfe6a0da", @@ -23,14 +40,14 @@ card = PokemonCardDef( title="Hunting Claw", game_text="Knock Out 1 of your opponent's Pok\u00e9mon in play that has 60 HP or less remaining.", cost={PokemonTypes.FIGHTING: 1}, - effect=unimplemented, + effect=hunting_claw, ), Attack( title="Max Edge", game_text="This attack also does 30 damage to 1 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=190, - effect=unimplemented, + effect=snipe_attack(30, also_base=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/LycanrocVMAX_92.py b/spirit/game/scripts/cards/SWSH7/LycanrocVMAX_92.py index 4c58660..727be3d 100644 --- a/spirit/game/scripts/cards/SWSH7/LycanrocVMAX_92.py +++ b/spirit/game/scripts/cards/SWSH7/LycanrocVMAX_92.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.attacks_common import snipe_attack + + +async def hunting_claw(ctx): + """Knock Out 1 of your opponent's Pokemon in play that has 60 HP or less remaining.""" + candidates = [ + p for p in ctx.opponent_pokemon_in_play() + if p.get_attribute(AttrID.HP, 0) <= 60 + ] + if not candidates: + return + target = await ctx.choose_pokemon( + candidates, "Choose 1 of your opponent's Pokémon with 60 HP or less remaining" + ) + if target is not None: + await ctx.knock_out(target) + card = PokemonCardDef( guid="7b4075a5-927c-5aa7-8913-455d6a33cd4e", @@ -23,14 +40,14 @@ card = PokemonCardDef( title="Hunting Claw", game_text="Knock Out 1 of your opponent's Pok\u00e9mon in play that has 60 HP or less remaining.", cost={PokemonTypes.FIGHTING: 1}, - effect=unimplemented, + effect=hunting_claw, ), Attack( title="Max Edge", game_text="This attack also does 30 damage to 1 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=190, - effect=unimplemented, + effect=snipe_attack(30, also_base=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/LycanrocV_187.py b/spirit/game/scripts/cards/SWSH7/LycanrocV_187.py index d293edc..6fedd3e 100644 --- a/spirit/game/scripts/cards/SWSH7/LycanrocV_187.py +++ b/spirit/game/scripts/cards/SWSH7/LycanrocV_187.py @@ -1,4 +1,4 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -28,7 +28,7 @@ card = PokemonCardDef( game_text="During your next turn, this Pok\u00e9mon can't attack.", cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=200, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/LycanrocV_91.py b/spirit/game/scripts/cards/SWSH7/LycanrocV_91.py index 16df52d..1148184 100644 --- a/spirit/game/scripts/cards/SWSH7/LycanrocV_91.py +++ b/spirit/game/scripts/cards/SWSH7/LycanrocV_91.py @@ -1,4 +1,4 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -28,7 +28,7 @@ card = PokemonCardDef( game_text="During your next turn, this Pok\u00e9mon can't attack.", cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=200, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Marshadow_80.py b/spirit/game/scripts/cards/SWSH7/Marshadow_80.py index 8f64f14..c531f29 100644 --- a/spirit/game/scripts/cards/SWSH7/Marshadow_80.py +++ b/spirit/game/scripts/cards/SWSH7/Marshadow_80.py @@ -1,5 +1,29 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, subtypes_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.session.effects import is_pokemon_card +from spirit.game.session.passives import Passive + + +def _is_rapid_strike_card(card): + return is_pokemon_card(card) and "Rapid Strike" in subtypes_for(card.archetype_id) + + +class ShadowFlickerPassive(Passive): + def modify_prizes_for_knockout(self, pokemon, ctx, count, carrier): + if pokemon is carrier: + return count + 1 + return count + + +async def shadow_flicker(ctx): + """10 damage. If the Defending Pokemon is Knocked Out during your next + turn, take 1 more Prize card.""" + await ctx.deal_damage() + defender = ctx.defender + if defender is not None: + ctx.add_passive_through_own_next_turn(defender, ShadowFlickerPassive()) + card = PokemonCardDef( guid="61197dd1-389f-59da-b240-7015c62b6188", @@ -23,14 +47,17 @@ card = PokemonCardDef( title="Rapid Hunt", game_text="Search your deck for up to 2 Rapid Strike cards, reveal them, and put them into your hand. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_to_hand( + _is_rapid_strike_card, count=2, minimum=0, + prompt="Choose up to 2 Rapid Strike cards to put into your hand.", + ), ), Attack( title="Shadow Flicker", game_text="If the Defending Pok\u00e9mon is Knocked Out during your next turn, take 1 more Prize card.", cost={PokemonTypes.COLORLESS: 1}, damage=10, - effect=unimplemented, + effect=shadow_flicker, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/MedichamV_185.py b/spirit/game/scripts/cards/SWSH7/MedichamV_185.py index 74f8278..60ea77e 100644 --- a/spirit/game/scripts/cards/SWSH7/MedichamV_185.py +++ b/spirit/game/scripts/cards/SWSH7/MedichamV_185.py @@ -1,6 +1,12 @@ from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.pokemon import yoga_loop, yoga_loop_condition from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def smash_uppercut(ctx): + """Printed damage; not affected by Resistance.""" + await ctx.deal_damage(ignore_resistance=True) + card = PokemonCardDef( guid="9d740c60-c197-5b5c-9926-89c03bdc7709", key="SWSH7", @@ -22,14 +28,15 @@ card = PokemonCardDef( title="Yoga Loop", game_text="Put 2 damage counters on 1 of your opponent's Pok\u00e9mon. If your opponent's Pok\u00e9mon is Knocked Out by this attack, take another turn after this one. (Skip Pok\u00e9mon Checkup.) If 1 of your Pok\u00e9mon used Yoga Loop during your last turn, this attack can't be used.", cost={PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + condition=yoga_loop_condition, + effect=yoga_loop, ), Attack( title="Smash Uppercut", game_text="This attack's damage isn't affected by Resistance.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 2}, damage=100, - effect=unimplemented, + effect=smash_uppercut, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/MedichamV_186.py b/spirit/game/scripts/cards/SWSH7/MedichamV_186.py index 7f01e8d..bf1fd10 100644 --- a/spirit/game/scripts/cards/SWSH7/MedichamV_186.py +++ b/spirit/game/scripts/cards/SWSH7/MedichamV_186.py @@ -1,6 +1,12 @@ from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.pokemon import yoga_loop, yoga_loop_condition from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def smash_uppercut(ctx): + """Printed damage; not affected by Resistance.""" + await ctx.deal_damage(ignore_resistance=True) + card = PokemonCardDef( guid="44be1f86-562c-5c4d-b833-f7e036824a98", key="SWSH7", @@ -22,14 +28,15 @@ card = PokemonCardDef( title="Yoga Loop", game_text="Put 2 damage counters on 1 of your opponent's Pok\u00e9mon. If your opponent's Pok\u00e9mon is Knocked Out by this attack, take another turn after this one. (Skip Pok\u00e9mon Checkup.) If 1 of your Pok\u00e9mon used Yoga Loop during your last turn, this attack can't be used.", cost={PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + condition=yoga_loop_condition, + effect=yoga_loop, ), Attack( title="Smash Uppercut", game_text="This attack's damage isn't affected by Resistance.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 2}, damage=100, - effect=unimplemented, + effect=smash_uppercut, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/MedichamV_83.py b/spirit/game/scripts/cards/SWSH7/MedichamV_83.py index d6c37d3..78abb78 100644 --- a/spirit/game/scripts/cards/SWSH7/MedichamV_83.py +++ b/spirit/game/scripts/cards/SWSH7/MedichamV_83.py @@ -1,6 +1,12 @@ from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.pokemon import yoga_loop, yoga_loop_condition from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def smash_uppercut(ctx): + """Printed damage; not affected by Resistance.""" + await ctx.deal_damage(ignore_resistance=True) + card = PokemonCardDef( guid="55a457fb-4cb9-5537-9c27-d2612cb5b714", key="SWSH7", @@ -22,14 +28,15 @@ card = PokemonCardDef( title="Yoga Loop", game_text="Put 2 damage counters on 1 of your opponent's Pok\u00e9mon. If your opponent's Pok\u00e9mon is Knocked Out by this attack, take another turn after this one. (Skip Pok\u00e9mon Checkup.) If 1 of your Pok\u00e9mon used Yoga Loop during your last turn, this attack can't be used.", cost={PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + condition=yoga_loop_condition, + effect=yoga_loop, ), Attack( title="Smash Uppercut", game_text="This attack's damage isn't affected by Resistance.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 2}, damage=100, - effect=unimplemented, + effect=smash_uppercut, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Milotic_38.py b/spirit/game/scripts/cards/SWSH7/Milotic_38.py index f8021b5..d392e00 100644 --- a/spirit/game/scripts/cards/SWSH7/Milotic_38.py +++ b/spirit/game/scripts/cards/SWSH7/Milotic_38.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage +from spirit.game.card_effects.passives_common import trainer_effect_shield_passive + + +def _you_or_your_hand(entity, carrier): + # "done to you or your hand": player-level effects plus cards in hand; + # effects on your Pokemon (Boss's Orders, damage) are NOT prevented. + if entity is None: + return True + parent = getattr(entity, "parent", None) + return bool(parent) and parent.get_attribute(AttrID.NAME) == "hand" card = PokemonCardDef( guid="11050f75-e444-53ac-af9b-8803477130eb", @@ -22,7 +33,7 @@ card = PokemonCardDef( Ability( title="Dew Guard", game_text="Whenever your opponent plays a Supporter card from their hand, prevent all effects of that card done to you or your hand.", - effect=unimplemented, + passive=trainer_effect_shield_passive(protects=_you_or_your_hand), ), Attack( title="Double Smash", @@ -30,7 +41,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 1}, damage=70, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=2, per_heads=70), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/MoonSunBadge_151.py b/spirit/game/scripts/cards/SWSH7/MoonSunBadge_151.py index a321730..cc9cedd 100644 --- a/spirit/game/scripts/cards/SWSH7/MoonSunBadge_151.py +++ b/spirit/game/scripts/cards/SWSH7/MoonSunBadge_151.py @@ -1,15 +1,35 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import PokemonToolCardDef, def_for, is_pokemon_v +from spirit.game.attributes import Rarities, TrainerType +from spirit.game.session.passives import Passive, carrier_pokemon + + +class MoonSunBadgePassive(Passive): + """An Espeon/Umbreon V holder is shielded from the effects an opposing + Supporter card does to it.""" + + def blocks_trainer_effects(self, affected_player_id, trainer_card, + trainer_type, carrier, affected_entity=None, + board=None): + if trainer_type != TrainerType.SUPPORTER.value or affected_entity is None: + return False + holder = carrier_pokemon(carrier) + if holder is None or holder is not carrier_pokemon(affected_entity): + return False + if not is_pokemon_v(holder.archetype_id): + return False + name = getattr(def_for(holder.archetype_id), "display_name", "") or "" + return "Espeon" in name or "Umbreon" in name + card = PokemonToolCardDef( guid="292b0db0-0e61-56fc-b1ad-ce20eab2407a", key="SWSH7", name="com.direwolfdigital.cake.data.archetypes.trainer.MoonSunBadge.Name", display_name="Moon & Sun Badge", - searchable_by=["Moon & Sun Badge", "Item", "Pok\u00c3\u00a9mon Tool"], - subtypes=["Item", "Pok\u00e9mon Tool"], + searchable_by=["Moon & Sun Badge", "Item", "Pokémon Tool"], + subtypes=["Item", "Pokémon Tool"], collector_number=151, set_code="SWSH7", rarity=Rarities.Uncommon, - effect=unimplemented + passive=MoonSunBadgePassive(), ) diff --git a/spirit/game/scripts/cards/SWSH7/NoivernV_117.py b/spirit/game/scripts/cards/SWSH7/NoivernV_117.py index 81e7782..7a84b91 100644 --- a/spirit/game/scripts/cards/SWSH7/NoivernV_117.py +++ b/spirit/game/scripts/cards/SWSH7/NoivernV_117.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, damage_all_opponents + + +def _same_hand_size(ctx) -> bool: + return ctx.hand_size(ctx.player_id) == ctx.hand_size(ctx.opponent_id) + card = PokemonCardDef( guid="f9499785-b8b2-5571-ac3b-56b019a23377", @@ -21,7 +27,7 @@ card = PokemonCardDef( title="Boomburst", game_text="This attack does 20 damage to each of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=damage_all_opponents(20), ), Attack( title="Synchro Loud", @@ -29,7 +35,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.DARKNESS: 1}, damage=60, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_same_hand_size, 120), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/NoivernV_195.py b/spirit/game/scripts/cards/SWSH7/NoivernV_195.py index f92d79e..542ce3d 100644 --- a/spirit/game/scripts/cards/SWSH7/NoivernV_195.py +++ b/spirit/game/scripts/cards/SWSH7/NoivernV_195.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, damage_all_opponents + + +def _same_hand_size(ctx) -> bool: + return ctx.hand_size(ctx.player_id) == ctx.hand_size(ctx.opponent_id) + card = PokemonCardDef( guid="f1c73080-7971-551a-8419-a07eb4ef2fae", @@ -21,7 +27,7 @@ card = PokemonCardDef( title="Boomburst", game_text="This attack does 20 damage to each of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=damage_all_opponents(20), ), Attack( title="Synchro Loud", @@ -29,7 +35,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.DARKNESS: 1}, damage=60, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_same_hand_size, 120), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/NoivernV_196.py b/spirit/game/scripts/cards/SWSH7/NoivernV_196.py index 57430a9..76b91bb 100644 --- a/spirit/game/scripts/cards/SWSH7/NoivernV_196.py +++ b/spirit/game/scripts/cards/SWSH7/NoivernV_196.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, damage_all_opponents + + +def _same_hand_size(ctx) -> bool: + return ctx.hand_size(ctx.player_id) == ctx.hand_size(ctx.opponent_id) + card = PokemonCardDef( guid="d1d4fcca-65be-51ac-ad8a-154b22a7475c", @@ -21,7 +27,7 @@ card = PokemonCardDef( title="Boomburst", game_text="This attack does 20 damage to each of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=damage_all_opponents(20), ), Attack( title="Synchro Loud", @@ -29,7 +35,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.DARKNESS: 1}, damage=60, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_same_hand_size, 120), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Nuzleaf_96.py b/spirit/game/scripts/cards/SWSH7/Nuzleaf_96.py index 58dd8ab..7760e03 100644 --- a/spirit/game/scripts/cards/SWSH7/Nuzleaf_96.py +++ b/spirit/game/scripts/cards/SWSH7/Nuzleaf_96.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="b98b3a86-1819-5559-9b2d-70b09bdc60f0", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.DARKNESS: 1}, damage=20, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Pikachu_49.py b/spirit/game/scripts/cards/SWSH7/Pikachu_49.py index 252480b..601789d 100644 --- a/spirit/game/scripts/cards/SWSH7/Pikachu_49.py +++ b/spirit/game/scripts/cards/SWSH7/Pikachu_49.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import attach_from_discard +from spirit.game.card_effects.pokemon import is_lightning_energy card = PokemonCardDef( guid="e2d51fbe-fb4d-5241-a92b-44ce2b2b463c", @@ -22,7 +24,7 @@ card = PokemonCardDef( title="Energize", game_text="Attach a Lightning Energy card from your discard pile to this Pok\u00e9mon.", cost={PokemonTypes.LIGHTNING: 1}, - effect=unimplemented, + effect=attach_from_discard(predicate=is_lightning_energy), ), Attack( title="Electro Ball", diff --git a/spirit/game/scripts/cards/SWSH7/Pinsir_1.py b/spirit/game/scripts/cards/SWSH7/Pinsir_1.py index 97164a5..1a5b5f8 100644 --- a/spirit/game/scripts/cards/SWSH7/Pinsir_1.py +++ b/spirit/game/scripts/cards/SWSH7/Pinsir_1.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, subtypes_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import no_resistance_passive + + +def _single_strike_attacker(calc, carrier): + return (calc.attacker is not None + and calc.attacker.owning_player_id == carrier.owning_player_id + and "Single Strike" in subtypes_for(calc.attacker.archetype_id)) card = PokemonCardDef( guid="f2928e4f-4662-5c5a-aca8-905e7ea6e7bd", @@ -21,7 +28,7 @@ card = PokemonCardDef( Ability( title="Vise Coach", game_text="Damage from your Single Strike Pok\u00e9mon's attacks isn't affected by your opponent's Active Pok\u00e9mon's Resistance.", - effect=unimplemented, + passive=no_resistance_passive(when=_single_strike_attacker), ), Attack( title="Seismic Toss", diff --git a/spirit/game/scripts/cards/SWSH7/Pyroar_23.py b/spirit/game/scripts/cards/SWSH7/Pyroar_23.py index a95042e..660a98a 100644 --- a/spirit/game/scripts/cards/SWSH7/Pyroar_23.py +++ b/spirit/game/scripts/cards/SWSH7/Pyroar_23.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import discard_opponent_energy_attack card = PokemonCardDef( guid="21fceab7-7aa9-5dac-9fa4-bdb7f0de7741", @@ -29,7 +30,7 @@ card = PokemonCardDef( game_text="Discard an Energy from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 1}, damage=70, - effect=unimplemented, + effect=discard_opponent_energy_attack(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Raichu_50.py b/spirit/game/scripts/cards/SWSH7/Raichu_50.py index e1eb869..a9fdb18 100644 --- a/spirit/game/scripts/cards/SWSH7/Raichu_50.py +++ b/spirit/game/scripts/cards/SWSH7/Raichu_50.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, is_pokemon_v from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import self_energy_discard_attack +from spirit.game.card_effects.pokemon import is_pokemon_gx + + +async def big_sparking(ctx): + """50 to each Pokemon V and Pokemon-GX, both sides; no W/R on the Bench.""" + opponent_active = ctx.opponent_active() + targets = ctx.my_pokemon_in_play() + ctx.opponent_pokemon_in_play() + for pokemon in targets: + if is_pokemon_v(pokemon.archetype_id) or is_pokemon_gx(pokemon.archetype_id): + await ctx.deal_damage( + 50, target=pokemon, apply_modifiers=(pokemon is opponent_active) + ) + card = PokemonCardDef( guid="ab225dfa-785f-5a87-b866-d1ea7cccf07d", @@ -23,14 +37,14 @@ card = PokemonCardDef( title="Big Sparking", game_text="This attack does 50 damage to each Pok\u00e9mon V and Pok\u00e9mon-GX (both yours and your opponent's). (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.LIGHTNING: 1}, - effect=unimplemented, + effect=big_sparking, ), Attack( title="Thunderbolt", game_text="Discard all Energy from this Pok\u00e9mon.", cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 1}, damage=180, - effect=unimplemented, + effect=self_energy_discard_attack(all_energy=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Raihan_152.py b/spirit/game/scripts/cards/SWSH7/Raihan_152.py index 42ba9a5..c9ef7cd 100644 --- a/spirit/game/scripts/cards/SWSH7/Raihan_152.py +++ b/spirit/game/scripts/cards/SWSH7/Raihan_152.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.support_common import attach_from_discard +from spirit.game.card_effects.trainers import is_basic_energy_card + + +async def _raihan_search(ctx, picks): + found = await ctx.search_deck( + count=1, minimum=0, + prompt="Search your deck for a card and put it into your hand.", + ) + await ctx.put_in_hand(found, reveal=False) + await ctx.shuffle_deck() + + +def _raihan_condition(board, player_id): + return bool(board.turn_state.kos_by_attack_last_turn.get(player_id)) + card = SupporterCardDef( guid="41507820-9223-5680-842b-4be97ea9f85b", @@ -11,5 +27,10 @@ card = SupporterCardDef( collector_number=152, set_code="SWSH7", rarity=Rarities.Uncommon, - effect=unimplemented + condition=_raihan_condition, + effect=attach_from_discard( + predicate=is_basic_energy_card, count=1, target="choice", + prompt="Attach a basic Energy card from your discard pile to 1 of your Pokémon.", + then=_raihan_search, + ), ) diff --git a/spirit/game/scripts/cards/SWSH7/Raihan_202.py b/spirit/game/scripts/cards/SWSH7/Raihan_202.py index 1cc9d5a..c7f492f 100644 --- a/spirit/game/scripts/cards/SWSH7/Raihan_202.py +++ b/spirit/game/scripts/cards/SWSH7/Raihan_202.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.support_common import attach_from_discard +from spirit.game.card_effects.trainers import is_basic_energy_card + + +async def _raihan_search(ctx, picks): + found = await ctx.search_deck( + count=1, minimum=0, + prompt="Search your deck for a card and put it into your hand.", + ) + await ctx.put_in_hand(found, reveal=False) + await ctx.shuffle_deck() + + +def _raihan_condition(board, player_id): + return bool(board.turn_state.kos_by_attack_last_turn.get(player_id)) + card = SupporterCardDef( guid="5017c315-f9a7-595e-8eb2-1e9d6ef984f0", @@ -11,5 +27,10 @@ card = SupporterCardDef( collector_number=202, set_code="SWSH7", rarity=Rarities.RareUltra, - effect=unimplemented + condition=_raihan_condition, + effect=attach_from_discard( + predicate=is_basic_energy_card, count=1, target="choice", + prompt="Attach a basic Energy card from your discard pile to 1 of your Pokémon.", + then=_raihan_search, + ), ) diff --git a/spirit/game/scripts/cards/SWSH7/Raihan_224.py b/spirit/game/scripts/cards/SWSH7/Raihan_224.py index 2f9e31d..7176af0 100644 --- a/spirit/game/scripts/cards/SWSH7/Raihan_224.py +++ b/spirit/game/scripts/cards/SWSH7/Raihan_224.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.support_common import attach_from_discard +from spirit.game.card_effects.trainers import is_basic_energy_card + + +async def _raihan_search(ctx, picks): + found = await ctx.search_deck( + count=1, minimum=0, + prompt="Search your deck for a card and put it into your hand.", + ) + await ctx.put_in_hand(found, reveal=False) + await ctx.shuffle_deck() + + +def _raihan_condition(board, player_id): + return bool(board.turn_state.kos_by_attack_last_turn.get(player_id)) + card = SupporterCardDef( guid="86caf873-a92e-56ea-8090-6ed2f04180ad", @@ -11,5 +27,10 @@ card = SupporterCardDef( collector_number=224, set_code="SWSH7", rarity=Rarities.RareRainbow, - effect=unimplemented + condition=_raihan_condition, + effect=attach_from_discard( + predicate=is_basic_energy_card, count=1, target="choice", + prompt="Attach a basic Energy card from your discard pile to 1 of your Pokémon.", + then=_raihan_search, + ), ) diff --git a/spirit/game/scripts/cards/SWSH7/RapidStrikeScrolloftheFlyingDragon_153.py b/spirit/game/scripts/cards/SWSH7/RapidStrikeScrolloftheFlyingDragon_153.py index 62a2e64..e435fac 100644 --- a/spirit/game/scripts/cards/SWSH7/RapidStrikeScrolloftheFlyingDragon_153.py +++ b/spirit/game/scripts/cards/SWSH7/RapidStrikeScrolloftheFlyingDragon_153.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import PokemonToolCardDef, Attack, subtypes_for +from spirit.game.attributes import Rarities, PokemonTypes + + +def _is_rapid_strike(board, player_id, pokemon): + return "Rapid Strike" in subtypes_for(pokemon.archetype_id) + + +async def _meteor(ctx): + """Discard 2 Energy from this Pokemon. 90 damage to 1 of your opponent's Pokemon (no W/R on Bench).""" + await ctx.discard_energy_from(ctx.attacker, 2, prompt="Discard 2 Energy from this Pok\u00e9mon") + targets = ctx.opponent_pokemon_in_play() + if not targets: + return + target = await ctx.choose_pokemon(targets, "Choose 1 of your opponent's Pok\u00e9mon") + if target is not None: + await ctx.deal_damage(90, target=target, apply_modifiers=(target is ctx.defender)) + card = PokemonToolCardDef( guid="f7a2e7e8-f18b-5d20-8e43-2f8a18506552", @@ -11,5 +27,13 @@ card = PokemonToolCardDef( collector_number=153, set_code="SWSH7", rarity=Rarities.Uncommon, - effect=unimplemented + granted_abilities=[ + Attack( + title="Meteor", + game_text="Discard 2 Energy from this Pok\u00e9mon. This attack does 90 damage to 1 of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", + cost={PokemonTypes.FIRE: 1, PokemonTypes.LIGHTNING: 1}, + effect=_meteor, + condition=_is_rapid_strike, + ), + ], ) diff --git a/spirit/game/scripts/cards/SWSH7/RayquazaVMAX_111.py b/spirit/game/scripts/cards/SWSH7/RayquazaVMAX_111.py index ab71cac..c8e4c61 100644 --- a/spirit/game/scripts/cards/SWSH7/RayquazaVMAX_111.py +++ b/spirit/game/scripts/cards/SWSH7/RayquazaVMAX_111.py @@ -1,5 +1,39 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.trainers import is_basic_energy_card + + +async def azure_pulse(ctx): + """Once per turn: you may discard your hand and draw 3 cards.""" + if not await ctx.ask_yes_no("Discard your hand and draw 3 cards?"): + return + await ctx.discard_cards(ctx.hand()) + await ctx.draw_cards(3) + + +def _is_basic_type(card, type_value): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_basic_energy_card(card) and type_value in types + + +async def max_burst(ctx): + """You may discard any amount of basic Fire or Lightning Energy from + this Pokemon; +80 damage for each card discarded this way.""" + attached = [ + e for e in ctx.attached_energies(ctx.attacker) + if _is_basic_type(e, PokemonTypes.FIRE.value) + or _is_basic_type(e, PokemonTypes.LIGHTNING.value) + ] + picks = [] + if attached: + picks = await ctx.choose_cards( + attached, len(attached), minimum=0, + prompt="Discard any amount of basic Fire or Lightning Energy from this Pokémon.", + ) + if picks: + await ctx.discard_cards(picks) + await ctx.deal_damage(20 + 80 * len(picks)) + card = PokemonCardDef( guid="e31a9323-5de3-549a-9f42-e74704534e1a", @@ -21,7 +55,8 @@ card = PokemonCardDef( Ability( title="Azure Pulse", game_text="Once during your turn, you may discard your hand and draw 3 cards.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + effect=azure_pulse, ), Attack( title="Max Burst", @@ -29,7 +64,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 1, PokemonTypes.LIGHTNING: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=max_burst, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/RayquazaVMAX_217.py b/spirit/game/scripts/cards/SWSH7/RayquazaVMAX_217.py index 123c257..3baba2b 100644 --- a/spirit/game/scripts/cards/SWSH7/RayquazaVMAX_217.py +++ b/spirit/game/scripts/cards/SWSH7/RayquazaVMAX_217.py @@ -1,5 +1,39 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.trainers import is_basic_energy_card + + +async def azure_pulse(ctx): + """Once per turn: you may discard your hand and draw 3 cards.""" + if not await ctx.ask_yes_no("Discard your hand and draw 3 cards?"): + return + await ctx.discard_cards(ctx.hand()) + await ctx.draw_cards(3) + + +def _is_basic_type(card, type_value): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_basic_energy_card(card) and type_value in types + + +async def max_burst(ctx): + """You may discard any amount of basic Fire or Lightning Energy from + this Pokemon; +80 damage for each card discarded this way.""" + attached = [ + e for e in ctx.attached_energies(ctx.attacker) + if _is_basic_type(e, PokemonTypes.FIRE.value) + or _is_basic_type(e, PokemonTypes.LIGHTNING.value) + ] + picks = [] + if attached: + picks = await ctx.choose_cards( + attached, len(attached), minimum=0, + prompt="Discard any amount of basic Fire or Lightning Energy from this Pokémon.", + ) + if picks: + await ctx.discard_cards(picks) + await ctx.deal_damage(20 + 80 * len(picks)) + card = PokemonCardDef( guid="9f19a7bc-b348-5b1d-8a80-731200fb445c", @@ -21,7 +55,8 @@ card = PokemonCardDef( Ability( title="Azure Pulse", game_text="Once during your turn, you may discard your hand and draw 3 cards.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + effect=azure_pulse, ), Attack( title="Max Burst", @@ -29,7 +64,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 1, PokemonTypes.LIGHTNING: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=max_burst, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/RayquazaVMAX_218.py b/spirit/game/scripts/cards/SWSH7/RayquazaVMAX_218.py index b7e6e44..f028020 100644 --- a/spirit/game/scripts/cards/SWSH7/RayquazaVMAX_218.py +++ b/spirit/game/scripts/cards/SWSH7/RayquazaVMAX_218.py @@ -1,5 +1,39 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.trainers import is_basic_energy_card + + +async def azure_pulse(ctx): + """Once per turn: you may discard your hand and draw 3 cards.""" + if not await ctx.ask_yes_no("Discard your hand and draw 3 cards?"): + return + await ctx.discard_cards(ctx.hand()) + await ctx.draw_cards(3) + + +def _is_basic_type(card, type_value): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_basic_energy_card(card) and type_value in types + + +async def max_burst(ctx): + """You may discard any amount of basic Fire or Lightning Energy from + this Pokemon; +80 damage for each card discarded this way.""" + attached = [ + e for e in ctx.attached_energies(ctx.attacker) + if _is_basic_type(e, PokemonTypes.FIRE.value) + or _is_basic_type(e, PokemonTypes.LIGHTNING.value) + ] + picks = [] + if attached: + picks = await ctx.choose_cards( + attached, len(attached), minimum=0, + prompt="Discard any amount of basic Fire or Lightning Energy from this Pokémon.", + ) + if picks: + await ctx.discard_cards(picks) + await ctx.deal_damage(20 + 80 * len(picks)) + card = PokemonCardDef( guid="31c43ed2-1cba-5399-8a1d-48f91b207357", @@ -21,7 +55,8 @@ card = PokemonCardDef( Ability( title="Azure Pulse", game_text="Once during your turn, you may discard your hand and draw 3 cards.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + effect=azure_pulse, ), Attack( title="Max Burst", @@ -29,7 +64,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 1, PokemonTypes.LIGHTNING: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=max_burst, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/RayquazaV_110.py b/spirit/game/scripts/cards/SWSH7/RayquazaV_110.py index 01c98f6..f93eb2e 100644 --- a/spirit/game/scripts/cards/SWSH7/RayquazaV_110.py +++ b/spirit/game/scripts/cards/SWSH7/RayquazaV_110.py @@ -1,5 +1,40 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import mill_attack +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.card_effects.pokemon import energy_provides_type + + +async def spiral_burst(ctx): + """20, +80 for each basic Fire or basic Lightning Energy discarded from + this Pokémon (up to 2 of one type).""" + attacker = ctx.attacker + attached = ctx.attached_energies(attacker) + fire = [c for c in attached if is_basic_energy_card(c) + and energy_provides_type(c, PokemonTypes.FIRE.value)] + lightning = [c for c in attached if is_basic_energy_card(c) + and energy_provides_type(c, PokemonTypes.LIGHTNING.value)] + discarded = [] + if fire and lightning: + choice = await ctx.choose( + "Choose up to 2 Energy to discard from this Pokémon:", + ["Basic Fire Energy", "Basic Lightning Energy"], + ) + pool = fire if choice == 0 else lightning + discarded = await ctx.choose_cards( + pool, min(2, len(pool)), minimum=0, + prompt="Choose up to 2 Energy to discard", + ) + elif fire or lightning: + pool = fire or lightning + discarded = await ctx.choose_cards( + pool, min(2, len(pool)), minimum=0, + prompt="Choose up to 2 Energy to discard", + ) + if discarded: + await ctx.discard_cards(discarded) + await ctx.deal_damage(20 + 80 * len(discarded)) + card = PokemonCardDef( guid="270828f8-8b1e-5d8f-9f22-9c4f2ea8000b", @@ -22,7 +57,7 @@ card = PokemonCardDef( game_text="Discard the top 2 cards of your deck.", cost={PokemonTypes.LIGHTNING: 1}, damage=40, - effect=unimplemented, + effect=mill_attack(2, opponent=False), ), Attack( title="Spiral Burst", @@ -30,7 +65,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 1, PokemonTypes.LIGHTNING: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=spiral_burst, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/RayquazaV_193.py b/spirit/game/scripts/cards/SWSH7/RayquazaV_193.py index 5322cb8..b0ab941 100644 --- a/spirit/game/scripts/cards/SWSH7/RayquazaV_193.py +++ b/spirit/game/scripts/cards/SWSH7/RayquazaV_193.py @@ -1,5 +1,40 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import mill_attack +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.card_effects.pokemon import energy_provides_type + + +async def spiral_burst(ctx): + """20, +80 for each basic Fire or basic Lightning Energy discarded from + this Pokémon (up to 2 of one type).""" + attacker = ctx.attacker + attached = ctx.attached_energies(attacker) + fire = [c for c in attached if is_basic_energy_card(c) + and energy_provides_type(c, PokemonTypes.FIRE.value)] + lightning = [c for c in attached if is_basic_energy_card(c) + and energy_provides_type(c, PokemonTypes.LIGHTNING.value)] + discarded = [] + if fire and lightning: + choice = await ctx.choose( + "Choose up to 2 Energy to discard from this Pokémon:", + ["Basic Fire Energy", "Basic Lightning Energy"], + ) + pool = fire if choice == 0 else lightning + discarded = await ctx.choose_cards( + pool, min(2, len(pool)), minimum=0, + prompt="Choose up to 2 Energy to discard", + ) + elif fire or lightning: + pool = fire or lightning + discarded = await ctx.choose_cards( + pool, min(2, len(pool)), minimum=0, + prompt="Choose up to 2 Energy to discard", + ) + if discarded: + await ctx.discard_cards(discarded) + await ctx.deal_damage(20 + 80 * len(discarded)) + card = PokemonCardDef( guid="85a7ea98-a361-5aa2-a4a9-8971784dddf1", @@ -22,15 +57,15 @@ card = PokemonCardDef( game_text="Discard the top 2 cards of your deck.", cost={PokemonTypes.LIGHTNING: 1}, damage=40, - effect=unimplemented, + effect=mill_attack(2, opponent=False), ), Attack( title="Spiral Burst", - game_text="You may discard up to 2 basic Fire Energy or up to 2 basic Lightning Energy from this Pok\u00e9mon. This attack does 80 more damage for each card you discarded in this way.", + game_text="You may discard up to 2 basic Fire Energy or up to 2 basic Lightning Energy from this Pokémon. This attack does 80 more damage for each card you discarded in this way.", cost={PokemonTypes.FIRE: 1, PokemonTypes.LIGHTNING: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=spiral_burst, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH7/RayquazaV_194.py b/spirit/game/scripts/cards/SWSH7/RayquazaV_194.py index 446e723..5dbd63d 100644 --- a/spirit/game/scripts/cards/SWSH7/RayquazaV_194.py +++ b/spirit/game/scripts/cards/SWSH7/RayquazaV_194.py @@ -1,5 +1,40 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import mill_attack +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.card_effects.pokemon import energy_provides_type + + +async def spiral_burst(ctx): + """20, +80 for each basic Fire or basic Lightning Energy discarded from + this Pokémon (up to 2 of one type).""" + attacker = ctx.attacker + attached = ctx.attached_energies(attacker) + fire = [c for c in attached if is_basic_energy_card(c) + and energy_provides_type(c, PokemonTypes.FIRE.value)] + lightning = [c for c in attached if is_basic_energy_card(c) + and energy_provides_type(c, PokemonTypes.LIGHTNING.value)] + discarded = [] + if fire and lightning: + choice = await ctx.choose( + "Choose up to 2 Energy to discard from this Pokémon:", + ["Basic Fire Energy", "Basic Lightning Energy"], + ) + pool = fire if choice == 0 else lightning + discarded = await ctx.choose_cards( + pool, min(2, len(pool)), minimum=0, + prompt="Choose up to 2 Energy to discard", + ) + elif fire or lightning: + pool = fire or lightning + discarded = await ctx.choose_cards( + pool, min(2, len(pool)), minimum=0, + prompt="Choose up to 2 Energy to discard", + ) + if discarded: + await ctx.discard_cards(discarded) + await ctx.deal_damage(20 + 80 * len(discarded)) + card = PokemonCardDef( guid="d16443f5-fbbd-5dc0-9558-c703387cc38a", @@ -22,15 +57,15 @@ card = PokemonCardDef( game_text="Discard the top 2 cards of your deck.", cost={PokemonTypes.LIGHTNING: 1}, damage=40, - effect=unimplemented, + effect=mill_attack(2, opponent=False), ), Attack( title="Spiral Burst", - game_text="You may discard up to 2 basic Fire Energy or up to 2 basic Lightning Energy from this Pok\u00e9mon. This attack does 80 more damage for each card you discarded in this way.", + game_text="You may discard up to 2 basic Fire Energy or up to 2 basic Lightning Energy from this Pokémon. This attack does 80 more damage for each card you discarded in this way.", cost={PokemonTypes.FIRE: 1, PokemonTypes.LIGHTNING: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=spiral_burst, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH7/RescueCarrier_154.py b/spirit/game/scripts/cards/SWSH7/RescueCarrier_154.py index d81d741..4d3b59b 100644 --- a/spirit/game/scripts/cards/SWSH7/RescueCarrier_154.py +++ b/spirit/game/scripts/cards/SWSH7/RescueCarrier_154.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import ItemCardDef +from spirit.game.attributes import Rarities, AttrID +from spirit.game.session.effects import is_pokemon_card +from spirit.game.card_effects.support_common import recover_from_discard, requires_discard + + +def _low_hp_pokemon(card): + return is_pokemon_card(card) and (card.get_attribute(AttrID.HP, 999) or 0) <= 90 + card = ItemCardDef( guid="9afac356-7eb9-52bb-842f-085b7f5303c5", @@ -11,5 +18,9 @@ card = ItemCardDef( collector_number=154, set_code="SWSH7", rarity=Rarities.Uncommon, - effect=unimplemented + effect=recover_from_discard( + _low_hp_pokemon, count=2, reveal=False, + prompt="Put up to 2 Pokémon with 90 HP or less from your discard pile into your hand", + ), + condition=requires_discard(_low_hp_pokemon), ) diff --git a/spirit/game/scripts/cards/SWSH7/RibbonBadge_155.py b/spirit/game/scripts/cards/SWSH7/RibbonBadge_155.py index 7d4587a..23c832d 100644 --- a/spirit/game/scripts/cards/SWSH7/RibbonBadge_155.py +++ b/spirit/game/scripts/cards/SWSH7/RibbonBadge_155.py @@ -1,15 +1,32 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented +from spirit.game.data_utils import PokemonToolCardDef, is_pokemon_v, def_for from spirit.game.attributes import Rarities +from spirit.game.session.passives import Passive, carrier_pokemon + + +class RibbonBadgePassive(Passive): + def modify_prizes_for_knockout(self, pokemon, ctx, count, carrier): + if carrier_pokemon(carrier) is not pokemon: + return count + if not is_pokemon_v(pokemon.archetype_id): + return count + definition = def_for(pokemon.archetype_id) + name = getattr(definition, "display_name", "") or "" + if "Sylveon" not in name: + return count + if not ctx.is_attack_effect() or ctx.player_id == pokemon.owning_player_id: + return count + return max(0, count - 1) + card = PokemonToolCardDef( guid="34e42b40-8244-5463-a3bb-931672c5d6de", key="SWSH7", name="com.direwolfdigital.cake.data.archetypes.trainer.RibbonBadge.Name", display_name="Ribbon Badge", - searchable_by=["Ribbon Badge", "Item", "Pok\u00c3\u00a9mon Tool"], - subtypes=["Item", "Pok\u00e9mon Tool"], + searchable_by=["Ribbon Badge", "Item", "Pokémon Tool"], + subtypes=["Item", "Pokémon Tool"], collector_number=155, set_code="SWSH7", rarity=Rarities.Uncommon, - effect=unimplemented + passive=RibbonBadgePassive(), ) diff --git a/spirit/game/scripts/cards/SWSH7/Ribombee_79.py b/spirit/game/scripts/cards/SWSH7/Ribombee_79.py index 5ad0ec8..82dc307 100644 --- a/spirit/game/scripts/cards/SWSH7/Ribombee_79.py +++ b/spirit/game/scripts/cards/SWSH7/Ribombee_79.py @@ -1,6 +1,30 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def _tricky_steps(ctx): + await ctx.deal_damage() + active = ctx.opponent_active() + bench = ctx.opponent_bench() + if active is None or not bench or ctx.effects_blocked(active): + return + energies = ctx.attached_energies(active) + if not energies: + return + if not await ctx.ask_yes_no( + "Move an Energy from your opponent's Active Pokémon to 1 of their Benched Pokémon?" + ): + return + picked = await ctx.choose_cards(energies, 1, minimum=1, prompt="Choose an Energy to move") + if not picked: + return + target = await ctx.choose_pokemon( + bench, "Choose the Benched Pokémon to move the Energy to" + ) + if target is not None: + await ctx.move_energy(picked[0], target) + + card = PokemonCardDef( guid="84012bef-c137-55bc-a645-f9eade5ee78d", key="SWSH7", @@ -24,7 +48,7 @@ card = PokemonCardDef( game_text="You may move an Energy from your opponent's Active Pok\u00e9mon to 1 of their Benched Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=_tricky_steps, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/RubberGloves_156.py b/spirit/game/scripts/cards/SWSH7/RubberGloves_156.py index 8d203c0..acca6b8 100644 --- a/spirit/game/scripts/cards/SWSH7/RubberGloves_156.py +++ b/spirit/game/scripts/cards/SWSH7/RubberGloves_156.py @@ -1,15 +1,16 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import PokemonToolCardDef +from spirit.game.attributes import Rarities, PokemonTypes +from spirit.game.card_effects.passives_common import typed_damage_boost_tool card = PokemonToolCardDef( guid="ba9c42ae-b2ad-5af1-a3a5-ed5c308692dd", key="SWSH7", name="com.direwolfdigital.cake.data.archetypes.trainer.RubberGloves.Name", display_name="Rubber Gloves", - searchable_by=["Rubber Gloves", "Item", "Pok\u00c3\u00a9mon Tool"], - subtypes=["Item", "Pok\u00e9mon Tool"], + searchable_by=["Rubber Gloves", "Item", "Pokémon Tool"], + subtypes=["Item", "Pokémon Tool"], collector_number=156, set_code="SWSH7", rarity=Rarities.Uncommon, - effect=unimplemented + passive=typed_damage_boost_tool(PokemonTypes.LIGHTNING, 30), ) diff --git a/spirit/game/scripts/cards/SWSH7/Rufflet_136.py b/spirit/game/scripts/cards/SWSH7/Rufflet_136.py index 69cd969..070c20f 100644 --- a/spirit/game/scripts/cards/SWSH7/Rufflet_136.py +++ b/spirit/game/scripts/cards/SWSH7/Rufflet_136.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import gust_attack card = PokemonCardDef( guid="602a3c2d-ee19-52d4-9e6f-89aafb320699", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Your opponent switches their Active Pok\u00e9mon with 1 of their Benched Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, damage=10, - effect=unimplemented, + effect=gust_attack(opponent_chooses=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Sableye_67.py b/spirit/game/scripts/cards/SWSH7/Sableye_67.py index dcf2b4e..fcd82cc 100644 --- a/spirit/game/scripts/cards/SWSH7/Sableye_67.py +++ b/spirit/game/scripts/cards/SWSH7/Sableye_67.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.session.effects import is_trainer_card + + +async def corner(ctx): + """40 damage. During your opponent's next turn, the Defending Pokemon + can't retreat.""" + await ctx.deal_damage() + defender = ctx.defender + if defender is not None and not ctx.effects_blocked(defender): + ctx.lock_retreat(defender) + card = PokemonCardDef( guid="194c5817-9bbb-5008-ade8-b3b368bc2d7d", @@ -23,14 +35,15 @@ card = PokemonCardDef( title="Go and Collect", game_text="Search your deck for a Trainer card, reveal it, and put it into your hand. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_to_hand(is_trainer_card, count=1, minimum=0, + reveal=True, prompt="Choose a Trainer card."), ), Attack( title="Corner", game_text="During your opponent's next turn, the Defending Pok\u00e9mon can't retreat.", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=40, - effect=unimplemented, + effect=corner, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Salamence_109.py b/spirit/game/scripts/cards/SWSH7/Salamence_109.py index 8a2f369..6de554e 100644 --- a/spirit/game/scripts/cards/SWSH7/Salamence_109.py +++ b/spirit/game/scripts/cards/SWSH7/Salamence_109.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, has_damage +from spirit.game.card_effects.support_common import opponent_switches +from spirit.game.card_effects.trainers import opponent_has_bench + + +async def intimidating_roar(ctx): + """Once during your turn, you may have your opponent switch their Active + Pokémon with 1 of their Benched Pokémon.""" + if await ctx.ask_yes_no( + "Have your opponent switch their Active Pokémon with 1 of their " + "Benched Pokémon?" + ): + await opponent_switches(ctx) + card = PokemonCardDef( guid="fe67fb86-b935-50da-8200-177adde21841", @@ -21,7 +35,9 @@ card = PokemonCardDef( Ability( title="Intimidating Roar", game_text="Once during your turn, you may have your opponent switch their Active Pok\u00e9mon with 1 of their Benched Pok\u00e9mon.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=lambda board, player_id, pokemon: opponent_has_bench(board, player_id), + effect=intimidating_roar, ), Attack( title="Fierce Dragon", @@ -29,7 +45,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 1, PokemonTypes.WATER: 1}, damage=100, damage_operator="+", - effect=unimplemented, + effect=bonus_if(has_damage("defender"), 120), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Scrafty_99.py b/spirit/game/scripts/cards/SWSH7/Scrafty_99.py index 84f31ff..00750b4 100644 --- a/spirit/game/scripts/cards/SWSH7/Scrafty_99.py +++ b/spirit/game/scripts/cards/SWSH7/Scrafty_99.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import discard_random_from_hand + + +async def shakedown(ctx): + """90 damage, then discard a random card from the opponent's hand.""" + await ctx.deal_damage() + await discard_random_from_hand(ctx, ctx.opponent_id, 1) + card = PokemonCardDef( guid="3f5e3411-b905-5261-9d0a-ec4fdc88a35b", @@ -29,7 +37,7 @@ card = PokemonCardDef( game_text="Discard a random card from your opponent's hand.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 2}, damage=90, - effect=unimplemented, + effect=shakedown, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Scraggy_98.py b/spirit/game/scripts/cards/SWSH7/Scraggy_98.py index 8b54bab..8654eb8 100644 --- a/spirit/game/scripts/cards/SWSH7/Scraggy_98.py +++ b/spirit/game/scripts/cards/SWSH7/Scraggy_98.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.passives_common import protect_next_turn +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, this Pok\u00e9mon takes 10 less damage from attacks (after applying Weakness and Resistance).", cost={PokemonTypes.DARKNESS: 1}, damage=10, - effect=unimplemented, + effect=protect_next_turn(reduce=10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Seedot_5.py b/spirit/game/scripts/cards/SWSH7/Seedot_5.py index a558968..fceaa88 100644 --- a/spirit/game/scripts/cards/SWSH7/Seedot_5.py +++ b/spirit/game/scripts/cards/SWSH7/Seedot_5.py @@ -1,6 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +import random + +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def astonish(ctx): + """Choose a random card from the opponent's hand, reveal it, shuffle into their deck.""" + await ctx.deal_damage() + opponent = ctx.opponent_id + hand = ctx.hand(opponent) + if not hand: + return + card = random.choice(hand) + await ctx.reveal_cards([card]) + await ctx.shuffle_into_deck([card], player_id=opponent) + + card = PokemonCardDef( guid="83426808-da28-50fe-a234-da9b9a5971a9", key="SWSH7", @@ -23,7 +38,7 @@ card = PokemonCardDef( game_text="Choose a random card from your opponent's hand. Your opponent reveals that card and shuffles it into their deck.", cost={PokemonTypes.COLORLESS: 2}, damage=10, - effect=unimplemented, + effect=astonish, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Seismitoad_90.py b/spirit/game/scripts/cards/SWSH7/Seismitoad_90.py index e1b25a9..c2867c1 100644 --- a/spirit/game/scripts/cards/SWSH7/Seismitoad_90.py +++ b/spirit/game/scripts/cards/SWSH7/Seismitoad_90.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import ( + apply_defender_attack_cost_raise, apply_defender_retreat_cost_raise, +) + + +async def _shaky_wave(ctx): + await ctx.deal_damage() + await apply_defender_attack_cost_raise(ctx) + await apply_defender_retreat_cost_raise(ctx) card = PokemonCardDef( guid="e8724276-bcbe-5609-b55d-5dbd5e83bda4", @@ -24,7 +33,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, the Defending Pok\u00e9mon's attacks cost Colorless more, and its Retreat Cost is Colorless more.", cost={PokemonTypes.FIGHTING: 1}, damage=60, - effect=unimplemented, + effect=_shaky_wave, ), Attack( title="Hyper Voice", diff --git a/spirit/game/scripts/cards/SWSH7/Sharpedo_36.py b/spirit/game/scripts/cards/SWSH7/Sharpedo_36.py index 43b882d..ff4f76c 100644 --- a/spirit/game/scripts/cards/SWSH7/Sharpedo_36.py +++ b/spirit/game/scripts/cards/SWSH7/Sharpedo_36.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import gust_attack +from spirit.game.card_effects.attacks_common import lock_all_attacks + + +async def jet_bite(ctx): + """Printed damage, then this Pokémon can't attack during your next turn.""" + await ctx.deal_damage() + lock_all_attacks(ctx, ctx.attacker) + card = PokemonCardDef( guid="c1c9c42f-4c81-5219-b7b5-16463fe7f619", @@ -23,14 +32,14 @@ card = PokemonCardDef( title="Taunt", game_text="Switch 1 of your opponent's Benched Pok\u00e9mon with their Active Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=gust_attack(), ), Attack( title="Jet Bite", game_text="During your next turn, this Pok\u00e9mon can't attack.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + effect=jet_bite, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Shelgon_108.py b/spirit/game/scripts/cards/SWSH7/Shelgon_108.py index d7613ce..122b99a 100644 --- a/spirit/game/scripts/cards/SWSH7/Shelgon_108.py +++ b/spirit/game/scripts/cards/SWSH7/Shelgon_108.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import flip_protection card = PokemonCardDef( guid="e27561c5-cb23-52cd-acaf-89431935f8b3", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, during your opponent's next turn, prevent all damage from and effects of attacks done to this Pok\u00e9mon.", cost={PokemonTypes.FIRE: 1, PokemonTypes.WATER: 1}, damage=50, - effect=unimplemented, + effect=flip_protection(prevent=True, effects_too=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Shiftry_97.py b/spirit/game/scripts/cards/SWSH7/Shiftry_97.py index 8111e45..314ae2d 100644 --- a/spirit/game/scripts/cards/SWSH7/Shiftry_97.py +++ b/spirit/game/scripts/cards/SWSH7/Shiftry_97.py @@ -1,5 +1,37 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.session.effects import full_stack +from spirit.game.card_effects.attacks_common import discard_random_from_hand + + +async def shiftadieu(ctx): + """If the opponent's Active has any damage counters, put it and all + attached cards into their hand.""" + target = ctx.defender + if target is None: + return + if target.get_attribute(AttrID.HP, 0) >= ctx.max_hp(target): + return + if ctx.effects_blocked(target): + return + stack = full_stack(target) + await ctx.put_in_hand(stack, reveal=False) + + async def _promote(): + if not await ctx.session._promote_new_active(ctx.opponent_id): + screen_name = ctx.session.players[ctx.opponent_id].screen_name + await ctx.session.end_game( + ctx.player_id, f"{screen_name} has no Pokémon left" + ) + + ctx.deferred_actions.append(_promote) + + +async def nipping_cyclone(ctx): + """130 damage; discard a random card from the opponent's hand.""" + await ctx.deal_damage() + await discard_random_from_hand(ctx, ctx.opponent_id, 1) + card = PokemonCardDef( guid="475d1196-90d5-5551-af14-76a658ec78da", @@ -23,14 +55,14 @@ card = PokemonCardDef( title="Shiftadieu", game_text="If your opponent's Active Pok\u00e9mon has any damage counters on it, put it and all attached cards into your opponent's hand.", cost={PokemonTypes.DARKNESS: 1}, - effect=unimplemented, + effect=shiftadieu, ), Attack( title="Nipping Cyclone", game_text="Discard a random card from your opponent's hand.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 2}, damage=130, - effect=unimplemented, + effect=nipping_cyclone, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/ShoppingCenter_157.py b/spirit/game/scripts/cards/SWSH7/ShoppingCenter_157.py index a8f500c..be2e7b1 100644 --- a/spirit/game/scripts/cards/SWSH7/ShoppingCenter_157.py +++ b/spirit/game/scripts/cards/SWSH7/ShoppingCenter_157.py @@ -1,5 +1,36 @@ -from spirit.game.data_utils import StadiumCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import StadiumCardDef, Ability, Activations +from spirit.game.attributes import Rarities, AttrID, TrainerType + + +def _shopping_center_tools(pokemon_list): + return [ + child for pokemon in pokemon_list for child in pokemon.children + if child.get_attribute(AttrID.TRAINER_TYPE) == TrainerType.POKEMON_TOOL.value + ] + + +def shopping_center_condition(board, player_id, stadium): + return bool(_shopping_center_tools(board.pokemon_in_play(player_id))) + + +async def shopping_center(ctx): + """Put a Pokemon Tool attached to 1 of your Pokemon into your hand.""" + tools = _shopping_center_tools(ctx.my_pokemon_in_play()) + if not tools: + return + picks = await ctx.choose_cards( + tools, 1, minimum=1, prompt="Choose a Pokémon Tool to put into your hand." + ) + await ctx.put_in_hand(picks, reveal=False) + + +SHOPPING_CENTER_ABILITY = Ability( + title="Shopping Center", + game_text="Once during each player's turn, that player may put a Pokémon Tool attached to 1 of their Pokémon into their hand.", + activation=Activations.ONCE_PER_TURN, + effect=shopping_center, + condition=shopping_center_condition, +) card = StadiumCardDef( guid="be8c92e0-b5ec-5f00-9c4b-24d7012f3fea", @@ -11,5 +42,5 @@ card = StadiumCardDef( collector_number=157, set_code="SWSH7", rarity=Rarities.Uncommon, - effect=unimplemented + ability=SHOPPING_CENTER_ABILITY, ) diff --git a/spirit/game/scripts/cards/SWSH7/SingleStrikeScrolloftheFangedDragon_158.py b/spirit/game/scripts/cards/SWSH7/SingleStrikeScrolloftheFangedDragon_158.py index b95e73e..d266a63 100644 --- a/spirit/game/scripts/cards/SWSH7/SingleStrikeScrolloftheFangedDragon_158.py +++ b/spirit/game/scripts/cards/SWSH7/SingleStrikeScrolloftheFangedDragon_158.py @@ -1,15 +1,30 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import PokemonToolCardDef, Attack, subtypes_for +from spirit.game.attributes import Rarities, PokemonTypes +from spirit.game.card_effects.attacks_common import self_energy_discard_attack + + +def _is_single_strike(board, player_id, pokemon): + return "Single Strike" in subtypes_for(pokemon.archetype_id) + card = PokemonToolCardDef( guid="b4f6ffbd-a9b6-5ffb-9a69-61944acb4dc9", key="SWSH7", name="com.direwolfdigital.cake.data.archetypes.trainer.SingleStrikeScrolloftheFangedDragon.Name", display_name="Single Strike Scroll of the Fanged Dragon", - searchable_by=["Single Strike Scroll of the Fanged Dragon", "Single Strike", "Item", "Pok\u00c3\u00a9mon Tool"], - subtypes=["Single Strike", "Item", "Pok\u00e9mon Tool"], + searchable_by=["Single Strike Scroll of the Fanged Dragon", "Single Strike", "Item", "Pokémon Tool"], + subtypes=["Single Strike", "Item", "Pokémon Tool"], collector_number=158, set_code="SWSH7", rarity=Rarities.Uncommon, - effect=unimplemented + granted_abilities=[ + Attack( + title="Superstrong Slash", + game_text="Discard all Energy from this Pokémon.", + cost={PokemonTypes.FIGHTING: 1, PokemonTypes.METAL: 2, PokemonTypes.COLORLESS: 2}, + damage=300, + effect=self_energy_discard_attack(all_energy=True), + condition=_is_single_strike, + ), + ], ) diff --git a/spirit/game/scripts/cards/SWSH7/Skiploom_3.py b/spirit/game/scripts/cards/SWSH7/Skiploom_3.py index 7bcada4..426b83c 100644 --- a/spirit/game/scripts/cards/SWSH7/Skiploom_3.py +++ b/spirit/game/scripts/cards/SWSH7/Skiploom_3.py @@ -1,6 +1,28 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers, evolves_from from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def solar_evolution(ctx): + """When you attach an Energy from hand to this Pokemon on your turn, you + may search for a card that evolves from it and evolve it; shuffle.""" + if ctx.energy_receiver is not ctx.source: + return + if ctx.attaching_player_id != ctx.player_id: + return + if not await ctx.ask_yes_no( + "Search your deck for a card that evolves from this Pokémon and evolve it?" + ): + return + picks = await ctx.search_deck( + lambda c: evolves_from(c.archetype_id, "Skiploom"), + count=1, minimum=0, + prompt="Choose a card that evolves from Skiploom.", + ) + if picks: + await ctx.evolve_pokemon(ctx.source, picks[0]) + await ctx.shuffle_deck() + + card = PokemonCardDef( guid="c38a17af-64fa-50b4-8d42-8825e2320aa3", key="SWSH7", @@ -22,7 +44,8 @@ card = PokemonCardDef( Ability( title="Solar Evolution", game_text="When you attach an Energy card from your hand to this Pok\u00e9mon during your turn, you may search your deck for a card that evolves from this Pok\u00e9mon and put it onto this Pok\u00e9mon to evolve it. Then, shuffle your deck.", - effect=unimplemented, + trigger=Triggers.ON_ENERGY_ATTACHED, + effect=solar_evolution, ), Attack( title="Spinning Attack", diff --git a/spirit/game/scripts/cards/SWSH7/Slaking_131.py b/spirit/game/scripts/cards/SWSH7/Slaking_131.py index 2082db1..fcb391d 100644 --- a/spirit/game/scripts/cards/SWSH7/Slaking_131.py +++ b/spirit/game/scripts/cards/SWSH7/Slaking_131.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_bench + + +def _no_stadium_in_play(board, player_id, pokemon): + stadium_area = board.find_global_area("activeStadium") + return not (stadium_area and stadium_area.children) + card = PokemonCardDef( guid="74cdc778-8cf6-5bd3-9c32-a42fd67dfb5d", @@ -22,7 +29,6 @@ card = PokemonCardDef( Ability( title="Act Freely", game_text="If a Stadium is in play, this Pok\u00e9mon can't attack.", - effect=unimplemented, ), Attack( title="Rout", @@ -30,7 +36,8 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=120, damage_operator="+", - effect=unimplemented, + condition=_no_stadium_in_play, + effect=damage_per(count_bench("opponent"), 30, base=120), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Slakoth_129.py b/spirit/game/scripts/cards/SWSH7/Slakoth_129.py index 191410d..3dae2e7 100644 --- a/spirit/game/scripts/cards/SWSH7/Slakoth_129.py +++ b/spirit/game/scripts/cards/SWSH7/Slakoth_129.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="a39e092e-2e5e-5a6d-a58a-d83177dd4ab4", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="This Pok\u00e9mon is now Asleep.", cost={PokemonTypes.COLORLESS: 2}, damage=30, - effect=unimplemented, + effect=condition_attack(self_conditions=(SpecialConditions.ASLEEP,)), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Smeargle_128.py b/spirit/game/scripts/cards/SWSH7/Smeargle_128.py index 07ccea8..2787848 100644 --- a/spirit/game/scripts/cards/SWSH7/Smeargle_128.py +++ b/spirit/game/scripts/cards/SWSH7/Smeargle_128.py @@ -1,5 +1,26 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.trainers import is_basic_energy_card + + +async def live_painting(ctx): + energies = [c for c in ctx.hand() if is_basic_energy_card(c)] + if not energies: + return + picks = await ctx.choose_cards( + energies, len(energies), minimum=0, + prompt="Reveal any number of basic Energy cards from your hand", + ) + if not picks: + return + await ctx.reveal_cards(picks) + types = set() + for card in picks: + types.update(card.get_attribute(AttrID.POKEMON_TYPES) or []) + amount = 30 * len(types) + if amount > 0: + await ctx.deal_damage(amount) + card = PokemonCardDef( guid="229b1c9e-672b-545d-ae92-3c46649b9aec", @@ -24,7 +45,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=30, damage_operator="+", - effect=unimplemented, + effect=live_painting, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/SnowLeafBadge_159.py b/spirit/game/scripts/cards/SWSH7/SnowLeafBadge_159.py index 94217a1..e6d93b0 100644 --- a/spirit/game/scripts/cards/SWSH7/SnowLeafBadge_159.py +++ b/spirit/game/scripts/cards/SWSH7/SnowLeafBadge_159.py @@ -1,5 +1,28 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented +from spirit.game.data_utils import PokemonToolCardDef, is_pokemon_v, def_for from spirit.game.attributes import Rarities +from spirit.game.session.passives import Passive, carrier_pokemon + + +def _eligible(pokemon): + if pokemon is None or not is_pokemon_v(pokemon.archetype_id): + return False + definition = def_for(pokemon.archetype_id) + name = getattr(definition, "display_name", None) or "" + return "Leafeon" in name or "Glaceon" in name + + +class SnowLeafBadgePassive(Passive): + def modify_retreat_cost(self, cost, pokemon, carrier, board): + holder = carrier_pokemon(carrier) + if pokemon is holder and _eligible(holder): + return 0 + return cost + + def modify_weakness(self, calc, carrier): + holder = carrier_pokemon(carrier) + if calc.target is holder and _eligible(holder): + calc.weakness_applies = False + card = PokemonToolCardDef( guid="e4183947-34e2-5c63-b9d5-d37826e82012", @@ -11,5 +34,5 @@ card = PokemonToolCardDef( collector_number=159, set_code="SWSH7", rarity=Rarities.Uncommon, - effect=unimplemented + passive=SnowLeafBadgePassive(), ) diff --git a/spirit/game/scripts/cards/SWSH7/SpiritMask_160.py b/spirit/game/scripts/cards/SWSH7/SpiritMask_160.py index 6224951..99077f6 100644 --- a/spirit/game/scripts/cards/SWSH7/SpiritMask_160.py +++ b/spirit/game/scripts/cards/SWSH7/SpiritMask_160.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented +from spirit.game.data_utils import PokemonToolCardDef, Ability, Triggers from spirit.game.attributes import Rarities +from spirit.game.card_effects.passives_common import is_in_active_spot + + +async def _spirit_mask_trigger(ctx): + pokemon = ctx.source + if not is_in_active_spot(pokemon): + return + attacker = ctx.damaged_by + if attacker is None or attacker.owning_player_id == pokemon.owning_player_id: + return + await ctx.discard_from_hand( + 1, prompt="Discard a card from your hand", player_id=ctx.opponent_id + ) + card = PokemonToolCardDef( guid="9ac939cb-e501-5866-a295-70b386622693", @@ -11,5 +25,12 @@ card = PokemonToolCardDef( collector_number=160, set_code="SWSH7", rarity=Rarities.Uncommon, - effect=unimplemented + granted_abilities=[ + Ability( + title="Spirit Mask", + game_text="If the Pok\u00e9mon this card is attached to is in the Active Spot and is damaged by an attack from your opponent's Pok\u00e9mon (even if it is Knocked Out), your opponent discards a card from their hand.", + trigger=Triggers.ON_DAMAGED_BY_ATTACK, + effect=_spirit_mask_trigger, + ), + ], ) diff --git a/spirit/game/scripts/cards/SWSH7/StormyMountains_161.py b/spirit/game/scripts/cards/SWSH7/StormyMountains_161.py index 5cd77a9..86c806a 100644 --- a/spirit/game/scripts/cards/SWSH7/StormyMountains_161.py +++ b/spirit/game/scripts/cards/SWSH7/StormyMountains_161.py @@ -1,5 +1,26 @@ -from spirit.game.data_utils import StadiumCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import StadiumCardDef, Ability, Activations +from spirit.game.attributes import Rarities, PokemonTypes, AttrID +from spirit.game.card_effects.support_common import search_to_bench, requires_bench_space +from spirit.game.session.effects import is_basic_pokemon + + +def _is_basic_lightning_or_dragon(card): + if not is_basic_pokemon(card): + return False + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return PokemonTypes.LIGHTNING.value in types or PokemonTypes.DRAGON.value in types + + +STORMY_MOUNTAINS_ABILITY = Ability( + title="Stormy Mountains", + game_text="Once during each player's turn, that player may search their deck for a Basic Lightning Pokémon or Basic Dragon Pokémon and put it onto their Bench. Then, that player shuffles their deck.", + activation=Activations.ONCE_PER_TURN, + effect=search_to_bench( + predicate=_is_basic_lightning_or_dragon, + prompt="Choose a Basic Lightning or Dragon Pokémon to put onto your Bench.", + ), + condition=requires_bench_space(1), +) card = StadiumCardDef( guid="03e89101-083c-5e6a-8136-ea3cd261de99", @@ -11,5 +32,5 @@ card = StadiumCardDef( collector_number=161, set_code="SWSH7", rarity=Rarities.Uncommon, - effect=unimplemented + ability=STORMY_MOUNTAINS_ABILITY, ) diff --git a/spirit/game/scripts/cards/SWSH7/StormyMountains_232.py b/spirit/game/scripts/cards/SWSH7/StormyMountains_232.py index fcd0697..0cbd75e 100644 --- a/spirit/game/scripts/cards/SWSH7/StormyMountains_232.py +++ b/spirit/game/scripts/cards/SWSH7/StormyMountains_232.py @@ -1,5 +1,26 @@ -from spirit.game.data_utils import StadiumCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import StadiumCardDef, Ability, Activations +from spirit.game.attributes import Rarities, PokemonTypes, AttrID +from spirit.game.card_effects.support_common import search_to_bench, requires_bench_space +from spirit.game.session.effects import is_basic_pokemon + + +def _is_basic_lightning_or_dragon(card): + if not is_basic_pokemon(card): + return False + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return PokemonTypes.LIGHTNING.value in types or PokemonTypes.DRAGON.value in types + + +STORMY_MOUNTAINS_ABILITY = Ability( + title="Stormy Mountains", + game_text="Once during each player's turn, that player may search their deck for a Basic Lightning Pokémon or Basic Dragon Pokémon and put it onto their Bench. Then, that player shuffles their deck.", + activation=Activations.ONCE_PER_TURN, + effect=search_to_bench( + predicate=_is_basic_lightning_or_dragon, + prompt="Choose a Basic Lightning or Dragon Pokémon to put onto your Bench.", + ), + condition=requires_bench_space(1), +) card = StadiumCardDef( guid="8ce2028e-5116-55ad-9b4b-dc83f61e174d", @@ -11,5 +32,5 @@ card = StadiumCardDef( collector_number=232, set_code="SWSH7", rarity=Rarities.RareSecret, - effect=unimplemented + ability=STORMY_MOUNTAINS_ABILITY, ) diff --git a/spirit/game/scripts/cards/SWSH7/Stoutland_135.py b/spirit/game/scripts/cards/SWSH7/Stoutland_135.py index 992c4be..70c2ee1 100644 --- a/spirit/game/scripts/cards/SWSH7/Stoutland_135.py +++ b/spirit/game/scripts/cards/SWSH7/Stoutland_135.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus +from spirit.game.card_effects.passives_common import is_in_active_spot, opposing_active +from spirit.game.session.passives import Passive + + +class IntimidatingFangPassive(Passive): + def modify_damage_dealt(self, calc, carrier): + if not is_in_active_spot(carrier): + return + attacker = calc.attacker + if attacker is None or not opposing_active(attacker, carrier): + return + calc.amount = max(0, calc.amount - 30) card = PokemonCardDef( guid="cb599bc2-319e-5fab-98f4-f7456439d764", @@ -22,7 +35,7 @@ card = PokemonCardDef( Ability( title="Intimidating Fang", game_text="As long as this Pok\u00e9mon is in the Active Spot, your opponent's Active Pok\u00e9mon's attacks do 30 less damage (before applying Weakness and Resistance).", - effect=unimplemented, + passive=IntimidatingFangPassive(), ), Attack( title="Knock Away", @@ -30,7 +43,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=120, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(100), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/SuicuneV_173.py b/spirit/game/scripts/cards/SWSH7/SuicuneV_173.py index 4a6c84e..8b58f2b 100644 --- a/spirit/game/scripts/cards/SWSH7/SuicuneV_173.py +++ b/spirit/game/scripts/cards/SWSH7/SuicuneV_173.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import in_active_spot +from spirit.game.card_effects.attacks_common import damage_per, count_bench + + +async def _fleet_footed(ctx): + if await ctx.ask_yes_no("Draw a card?"): + await ctx.draw_cards(1) + card = PokemonCardDef( guid="eeb5dfa0-6828-5da8-b8a9-ec74cfaf7b6c", @@ -21,7 +29,9 @@ card = PokemonCardDef( Ability( title="Fleet-Footed", game_text="Once during your turn, if this Pok\u00e9mon is in the Active Spot, you may draw a card.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=in_active_spot, + effect=_fleet_footed, ), Attack( title="Blizzard Rondo", @@ -29,7 +39,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_bench("both"), 20, base=20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/SuicuneV_31.py b/spirit/game/scripts/cards/SWSH7/SuicuneV_31.py index 66adaca..713f299 100644 --- a/spirit/game/scripts/cards/SWSH7/SuicuneV_31.py +++ b/spirit/game/scripts/cards/SWSH7/SuicuneV_31.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import in_active_spot +from spirit.game.card_effects.attacks_common import damage_per, count_bench + + +async def _fleet_footed(ctx): + if await ctx.ask_yes_no("Draw a card?"): + await ctx.draw_cards(1) + card = PokemonCardDef( guid="589d013e-72d9-5a48-8209-634d920adef0", @@ -21,7 +29,9 @@ card = PokemonCardDef( Ability( title="Fleet-Footed", game_text="Once during your turn, if this Pok\u00e9mon is in the Active Spot, you may draw a card.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=in_active_spot, + effect=_fleet_footed, ), Attack( title="Blizzard Rondo", @@ -29,7 +39,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_bench("both"), 20, base=20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Swablu_132.py b/spirit/game/scripts/cards/SWSH7/Swablu_132.py index d8b6b96..51dffd8 100644 --- a/spirit/game/scripts/cards/SWSH7/Swablu_132.py +++ b/spirit/game/scripts/cards/SWSH7/Swablu_132.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_or_nothing card = PokemonCardDef( guid="8b7bb33f-a764-5e65-81d7-b989f1faa4cd", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Flip a coin. If tails, this attack does nothing.", cost={PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=flip_or_nothing(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/SwitchingCups_162.py b/spirit/game/scripts/cards/SWSH7/SwitchingCups_162.py index d9da634..ac3e7f8 100644 --- a/spirit/game/scripts/cards/SWSH7/SwitchingCups_162.py +++ b/spirit/game/scripts/cards/SWSH7/SwitchingCups_162.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.pokemon import primate_wisdom +from spirit.game.card_effects.support_common import requires_hand +from spirit.game.card_effects.trainers import deck_nonempty + + +def _switching_cups_condition(board, player_id, pokemon=None): + return requires_hand(n=1)(board, player_id) and deck_nonempty(board, player_id) + card = ItemCardDef( guid="fbfb10ea-a91c-56a4-966d-3aea13668aa9", @@ -11,5 +19,6 @@ card = ItemCardDef( collector_number=162, set_code="SWSH7", rarity=Rarities.Uncommon, - effect=unimplemented + effect=primate_wisdom, + condition=_switching_cups_condition, ) diff --git a/spirit/game/scripts/cards/SWSH7/Swoobat_69.py b/spirit/game/scripts/cards/SWSH7/Swoobat_69.py index 1cad406..710643c 100644 --- a/spirit/game/scripts/cards/SWSH7/Swoobat_69.py +++ b/spirit/game/scripts/cards/SWSH7/Swoobat_69.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if card = PokemonCardDef( guid="aa975f79-6e47-5f19-8b68-ff97aa9fb0e5", @@ -26,7 +27,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 1}, damage=30, damage_operator="+", - effect=unimplemented, + effect=bonus_if(lambda ctx: ctx.hand_size() == ctx.hand_size(ctx.opponent_id), 80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/SylveonVMAX_211.py b/spirit/game/scripts/cards/SWSH7/SylveonVMAX_211.py index dc6f048..d319983 100644 --- a/spirit/game/scripts/cards/SWSH7/SylveonVMAX_211.py +++ b/spirit/game/scripts/cards/SWSH7/SylveonVMAX_211.py @@ -1,5 +1,31 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per +from spirit.game.card_effects.pokemon import is_energy_card + + +async def _precious_touch(ctx): + bench = ctx.my_bench() + hand_energy = [c for c in ctx.hand() if is_energy_card(c)] + if not bench or not hand_energy: + return + picks = await ctx.choose_cards(hand_energy, 1, prompt="Choose an Energy card to attach.") + if not picks: + return + target = await ctx.choose_pokemon(bench, "Choose 1 of your Benched Pokémon.") + if target is None: + return + if await ctx.attach_energy(picks[0], target): + await ctx.heal(120, target=target) + + +def _distinct_bench_types(ctx) -> int: + types = set() + for pokemon in ctx.my_bench(): + for t in pokemon.get_attribute(AttrID.POKEMON_TYPES) or []: + types.add(t) + return len(types) + card = PokemonCardDef( guid="77def08f-72f4-5596-ad4b-49d21184b6e1", @@ -23,7 +49,7 @@ card = PokemonCardDef( title="Precious Touch", game_text="Attach an Energy card from your hand to 1 of your Benched Pok\u00e9mon. If you do, heal 120 damage from that Pok\u00e9mon.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=_precious_touch, ), Attack( title="Max Harmony", @@ -31,7 +57,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=70, damage_operator="+", - effect=unimplemented, + effect=damage_per(_distinct_bench_types, 30, base=70), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/SylveonVMAX_212.py b/spirit/game/scripts/cards/SWSH7/SylveonVMAX_212.py index 45e2862..a118fc1 100644 --- a/spirit/game/scripts/cards/SWSH7/SylveonVMAX_212.py +++ b/spirit/game/scripts/cards/SWSH7/SylveonVMAX_212.py @@ -1,5 +1,31 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per +from spirit.game.card_effects.pokemon import is_energy_card + + +async def _precious_touch(ctx): + bench = ctx.my_bench() + hand_energy = [c for c in ctx.hand() if is_energy_card(c)] + if not bench or not hand_energy: + return + picks = await ctx.choose_cards(hand_energy, 1, prompt="Choose an Energy card to attach.") + if not picks: + return + target = await ctx.choose_pokemon(bench, "Choose 1 of your Benched Pokémon.") + if target is None: + return + if await ctx.attach_energy(picks[0], target): + await ctx.heal(120, target=target) + + +def _distinct_bench_types(ctx) -> int: + types = set() + for pokemon in ctx.my_bench(): + for t in pokemon.get_attribute(AttrID.POKEMON_TYPES) or []: + types.add(t) + return len(types) + card = PokemonCardDef( guid="3a32d55c-0bd7-52a6-984b-6bbae1da07e2", @@ -23,7 +49,7 @@ card = PokemonCardDef( title="Precious Touch", game_text="Attach an Energy card from your hand to 1 of your Benched Pok\u00e9mon. If you do, heal 120 damage from that Pok\u00e9mon.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=_precious_touch, ), Attack( title="Max Harmony", @@ -31,7 +57,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=70, damage_operator="+", - effect=unimplemented, + effect=damage_per(_distinct_bench_types, 30, base=70), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/SylveonVMAX_75.py b/spirit/game/scripts/cards/SWSH7/SylveonVMAX_75.py index d54ecad..fc14eab 100644 --- a/spirit/game/scripts/cards/SWSH7/SylveonVMAX_75.py +++ b/spirit/game/scripts/cards/SWSH7/SylveonVMAX_75.py @@ -1,5 +1,31 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per +from spirit.game.card_effects.pokemon import is_energy_card + + +async def _precious_touch(ctx): + bench = ctx.my_bench() + hand_energy = [c for c in ctx.hand() if is_energy_card(c)] + if not bench or not hand_energy: + return + picks = await ctx.choose_cards(hand_energy, 1, prompt="Choose an Energy card to attach.") + if not picks: + return + target = await ctx.choose_pokemon(bench, "Choose 1 of your Benched Pokémon.") + if target is None: + return + if await ctx.attach_energy(picks[0], target): + await ctx.heal(120, target=target) + + +def _distinct_bench_types(ctx) -> int: + types = set() + for pokemon in ctx.my_bench(): + for t in pokemon.get_attribute(AttrID.POKEMON_TYPES) or []: + types.add(t) + return len(types) + card = PokemonCardDef( guid="729566be-e569-555f-9262-48736d25a7f5", @@ -23,7 +49,7 @@ card = PokemonCardDef( title="Precious Touch", game_text="Attach an Energy card from your hand to 1 of your Benched Pok\u00e9mon. If you do, heal 120 damage from that Pok\u00e9mon.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=_precious_touch, ), Attack( title="Max Harmony", @@ -31,7 +57,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=70, damage_operator="+", - effect=unimplemented, + effect=damage_per(_distinct_bench_types, 30, base=70), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/SylveonV_183.py b/spirit/game/scripts/cards/SWSH7/SylveonV_183.py index 4123314..f729354 100644 --- a/spirit/game/scripts/cards/SWSH7/SylveonV_183.py +++ b/spirit/game/scripts/cards/SWSH7/SylveonV_183.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_item_card +from spirit.game.card_effects.support_common import search_to_hand card = PokemonCardDef( guid="1f21bbac-b2ae-5ada-bfac-8a2c382e544d", @@ -21,7 +23,9 @@ card = PokemonCardDef( Ability( title="Dream Gift", game_text="Once during your turn, you may search your deck for an Item card, reveal it, and put it into your hand. Then, shuffle your deck. If you use this Ability, your turn ends.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + ends_turn=True, + effect=search_to_hand(predicate=is_item_card, count=1, minimum=0, reveal=True), ), Attack( title="Magical Shot", diff --git a/spirit/game/scripts/cards/SWSH7/SylveonV_184.py b/spirit/game/scripts/cards/SWSH7/SylveonV_184.py index f62e1bb..93f4424 100644 --- a/spirit/game/scripts/cards/SWSH7/SylveonV_184.py +++ b/spirit/game/scripts/cards/SWSH7/SylveonV_184.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_item_card +from spirit.game.card_effects.support_common import search_to_hand card = PokemonCardDef( guid="ba7049d8-3f85-5d7d-ac83-0b584c6f9854", @@ -21,7 +23,9 @@ card = PokemonCardDef( Ability( title="Dream Gift", game_text="Once during your turn, you may search your deck for an Item card, reveal it, and put it into your hand. Then, shuffle your deck. If you use this Ability, your turn ends.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + ends_turn=True, + effect=search_to_hand(predicate=is_item_card, count=1, minimum=0, reveal=True), ), Attack( title="Magical Shot", diff --git a/spirit/game/scripts/cards/SWSH7/SylveonV_74.py b/spirit/game/scripts/cards/SWSH7/SylveonV_74.py index c1f211a..021ea72 100644 --- a/spirit/game/scripts/cards/SWSH7/SylveonV_74.py +++ b/spirit/game/scripts/cards/SWSH7/SylveonV_74.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_item_card +from spirit.game.card_effects.support_common import search_to_hand card = PokemonCardDef( guid="0cd8c808-01c6-5400-a253-479759a45716", @@ -21,7 +23,9 @@ card = PokemonCardDef( Ability( title="Dream Gift", game_text="Once during your turn, you may search your deck for an Item card, reveal it, and put it into your hand. Then, shuffle your deck. If you use this Ability, your turn ends.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + ends_turn=True, + effect=search_to_hand(predicate=is_item_card, count=1, minimum=0, reveal=True), ), Attack( title="Magical Shot", diff --git a/spirit/game/scripts/cards/SWSH7/Talonflame_140.py b/spirit/game/scripts/cards/SWSH7/Talonflame_140.py index 1171b04..65f222f 100644 --- a/spirit/game/scripts/cards/SWSH7/Talonflame_140.py +++ b/spirit/game/scripts/cards/SWSH7/Talonflame_140.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, count_energy + +_FIRE_ENERGY_ON_SELF = count_energy("self", energy_type=PokemonTypes.FIRE) + + +async def _clutch(ctx): + await ctx.deal_damage() + target = ctx.defender + if target is not None and not ctx.effects_blocked(target): + ctx.lock_retreat(target) + card = PokemonCardDef( guid="d720b952-a2c6-5466-a2de-8e61bf8f9775", @@ -25,7 +36,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, the Defending Pok\u00e9mon can't retreat.", cost={PokemonTypes.COLORLESS: 1}, damage=40, - effect=unimplemented, + effect=_clutch, ), Attack( title="Nitro Dive", @@ -33,7 +44,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=80, damage_operator="+", - effect=unimplemented, + effect=bonus_if(lambda ctx: _FIRE_ENERGY_ON_SELF(ctx) > 0, 80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Tentacruel_27.py b/spirit/game/scripts/cards/SWSH7/Tentacruel_27.py index f3c3de2..a332b59 100644 --- a/spirit/game/scripts/cards/SWSH7/Tentacruel_27.py +++ b/spirit/game/scripts/cards/SWSH7/Tentacruel_27.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="5abc90a5-b646-547f-ac9d-d3958c3224bf", @@ -29,7 +30,7 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Poisoned. During your opponent's next turn, that Pok\u00e9mon can't retreat.", cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, damage=100, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED, no_retreat=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Thievul_105.py b/spirit/game/scripts/cards/SWSH7/Thievul_105.py index 1c17699..365b79e 100644 --- a/spirit/game/scripts/cards/SWSH7/Thievul_105.py +++ b/spirit/game/scripts/cards/SWSH7/Thievul_105.py @@ -1,6 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def _fumbling_hands(ctx): + if not await ctx.ask_yes_no( + "Have each player shuffle their hand into their deck?" + ): + return + moved_mine = await ctx.hand_to_bottom_of_deck(ctx.player_id) + moved_opp = await ctx.hand_to_bottom_of_deck(ctx.opponent_id) + if not (moved_mine or moved_opp): + return + await ctx.draw_cards(4, ctx.player_id) + await ctx.draw_cards(4, ctx.opponent_id) + + card = PokemonCardDef( guid="2d50e350-f893-59c0-8737-1a348f784c32", key="SWSH7", @@ -22,7 +36,8 @@ card = PokemonCardDef( Ability( title="Fumbling Hands", game_text="When you play this Pok\u00e9mon from your hand to evolve 1 of your Pok\u00e9mon during your turn, you may have each player shuffle their hand and put it on the bottom of their deck. If either player put any cards on the bottom of their deck in this way, each player draws 4 cards.", - effect=unimplemented, + trigger=Triggers.ON_EVOLVE, + effect=_fumbling_hands, ), Attack( title="Tail Smack", diff --git a/spirit/game/scripts/cards/SWSH7/ToyCatcher_163.py b/spirit/game/scripts/cards/SWSH7/ToyCatcher_163.py index 1c1d530..f95ea47 100644 --- a/spirit/game/scripts/cards/SWSH7/ToyCatcher_163.py +++ b/spirit/game/scripts/cards/SWSH7/ToyCatcher_163.py @@ -1,5 +1,36 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import ItemCardDef +from spirit.game.attributes import Rarities, AttrID + + +def _opponent_id(board, player_id): + for pid in board.player_ids: + if pid != player_id: + return pid + return None + + +def _toy_catcher_targets(board, player_id): + opponent = _opponent_id(board, player_id) + if opponent is None: + return [] + bench = board.find_player_area(opponent, "bench") + if not bench: + return [] + return [p for p in bench.children if p.get_attribute(AttrID.HP, 0) <= 50] + + +def toy_catcher_condition(board, player_id): + return bool(_toy_catcher_targets(board, player_id)) + + +async def toy_catcher(ctx): + """Switch 1 of the opponent's Benched Pokemon that has 50 HP or less + remaining with their Active Pokemon.""" + targets = [p for p in ctx.opponent_bench() if p.get_attribute(AttrID.HP, 0) <= 50] + target = await ctx.choose_pokemon(targets, "Choose the opponent's new Active Pokémon") + if target is not None: + await ctx.switch_active(ctx.opponent_id, target) + card = ItemCardDef( guid="0fcd3510-0d73-5bbf-9426-6bd9a417b7cc", @@ -11,5 +42,6 @@ card = ItemCardDef( collector_number=163, set_code="SWSH7", rarity=Rarities.Uncommon, - effect=unimplemented + effect=toy_catcher, + condition=toy_catcher_condition, ) diff --git a/spirit/game/scripts/cards/SWSH7/ToyCatcher_233.py b/spirit/game/scripts/cards/SWSH7/ToyCatcher_233.py index 3cb826a..d53ab3b 100644 --- a/spirit/game/scripts/cards/SWSH7/ToyCatcher_233.py +++ b/spirit/game/scripts/cards/SWSH7/ToyCatcher_233.py @@ -1,5 +1,36 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import ItemCardDef +from spirit.game.attributes import Rarities, AttrID + + +def _opponent_id(board, player_id): + for pid in board.player_ids: + if pid != player_id: + return pid + return None + + +def _toy_catcher_targets(board, player_id): + opponent = _opponent_id(board, player_id) + if opponent is None: + return [] + bench = board.find_player_area(opponent, "bench") + if not bench: + return [] + return [p for p in bench.children if p.get_attribute(AttrID.HP, 0) <= 50] + + +def toy_catcher_condition(board, player_id): + return bool(_toy_catcher_targets(board, player_id)) + + +async def toy_catcher(ctx): + """Switch 1 of the opponent's Benched Pokemon that has 50 HP or less + remaining with their Active Pokemon.""" + targets = [p for p in ctx.opponent_bench() if p.get_attribute(AttrID.HP, 0) <= 50] + target = await ctx.choose_pokemon(targets, "Choose the opponent's new Active Pokémon") + if target is not None: + await ctx.switch_active(ctx.opponent_id, target) + card = ItemCardDef( guid="3e1f43f0-84ff-50d0-b68c-8260ebb24078", @@ -11,5 +42,6 @@ card = ItemCardDef( collector_number=233, set_code="SWSH7", rarity=Rarities.RareSecret, - effect=unimplemented + effect=toy_catcher, + condition=toy_catcher_condition, ) diff --git a/spirit/game/scripts/cards/SWSH7/TrevenantVMAX_14.py b/spirit/game/scripts/cards/SWSH7/TrevenantVMAX_14.py index 8288137..c60d4c2 100644 --- a/spirit/game/scripts/cards/SWSH7/TrevenantVMAX_14.py +++ b/spirit/game/scripts/cards/SWSH7/TrevenantVMAX_14.py @@ -1,5 +1,9 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_discard +from spirit.game.session.effects import is_supporter_card + +missing_in_the_forest = damage_per(count_discard("opponent", is_supporter_card), 40) card = PokemonCardDef( guid="11f1fd5d-9176-50c6-8bb1-2248342740a6", @@ -25,7 +29,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=40, damage_operator="x", - effect=unimplemented, + effect=missing_in_the_forest, ), Attack( title="Max Tree", diff --git a/spirit/game/scripts/cards/SWSH7/TrevenantVMAX_206.py b/spirit/game/scripts/cards/SWSH7/TrevenantVMAX_206.py index 007f909..c767a86 100644 --- a/spirit/game/scripts/cards/SWSH7/TrevenantVMAX_206.py +++ b/spirit/game/scripts/cards/SWSH7/TrevenantVMAX_206.py @@ -1,5 +1,9 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_discard +from spirit.game.session.effects import is_supporter_card + +missing_in_the_forest = damage_per(count_discard("opponent", is_supporter_card), 40) card = PokemonCardDef( guid="ff334cce-77ea-5137-baa3-3f35cb84d8c9", @@ -25,7 +29,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=40, damage_operator="x", - effect=unimplemented, + effect=missing_in_the_forest, ), Attack( title="Max Tree", diff --git a/spirit/game/scripts/cards/SWSH7/TrevenantV_13.py b/spirit/game/scripts/cards/SWSH7/TrevenantV_13.py index e61ef7c..67a814b 100644 --- a/spirit/game/scripts/cards/SWSH7/TrevenantV_13.py +++ b/spirit/game/scripts/cards/SWSH7/TrevenantV_13.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import heal_attack +from spirit.game.card_effects.attacks_common import discard_random_from_hand + + +async def shadow_claw(ctx): + """120. Discard a random card from your opponent's hand.""" + await ctx.deal_damage() + await discard_random_from_hand(ctx, player_id=ctx.opponent_id, count=1) card = PokemonCardDef( guid="cd42d379-3507-5b8a-8a42-229f76d0b5d0", @@ -23,14 +31,14 @@ card = PokemonCardDef( game_text="Heal 30 damage from this Pok\u00e9mon.", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=heal_attack(30, target="self"), ), Attack( title="Shadow Claw", game_text="Discard a random card from your opponent's hand.", cost={PokemonTypes.GRASS: 2, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + effect=shadow_claw, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/TrevenantV_168.py b/spirit/game/scripts/cards/SWSH7/TrevenantV_168.py index e64aad8..b1d6181 100644 --- a/spirit/game/scripts/cards/SWSH7/TrevenantV_168.py +++ b/spirit/game/scripts/cards/SWSH7/TrevenantV_168.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import heal_attack +from spirit.game.card_effects.attacks_common import discard_random_from_hand + + +async def shadow_claw(ctx): + """120. Discard a random card from your opponent's hand.""" + await ctx.deal_damage() + await discard_random_from_hand(ctx, player_id=ctx.opponent_id, count=1) card = PokemonCardDef( guid="a12fa25e-a494-5ae6-8f32-25f92a6579c3", @@ -23,14 +31,14 @@ card = PokemonCardDef( game_text="Heal 30 damage from this Pok\u00e9mon.", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=heal_attack(30, target="self"), ), Attack( title="Shadow Claw", game_text="Discard a random card from your opponent's hand.", cost={PokemonTypes.GRASS: 2, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + effect=shadow_claw, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Tropius_6.py b/spirit/game/scripts/cards/SWSH7/Tropius_6.py index 0797f6c..90e758c 100644 --- a/spirit/game/scripts/cards/SWSH7/Tropius_6.py +++ b/spirit/game/scripts/cards/SWSH7/Tropius_6.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if card = PokemonCardDef( guid="7a19eb2c-13ec-59d4-a2d8-13726bc79eb6", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=30, damage_operator="+", - effect=unimplemented, + effect=bonus_if(lambda ctx: ctx.kos_suffered_last_turn() > 0, 90), ), Attack( title="Solar Beam", diff --git a/spirit/game/scripts/cards/SWSH7/TurffieldStadium_234.py b/spirit/game/scripts/cards/SWSH7/TurffieldStadium_234.py index f3ef900..7abf923 100644 --- a/spirit/game/scripts/cards/SWSH7/TurffieldStadium_234.py +++ b/spirit/game/scripts/cards/SWSH7/TurffieldStadium_234.py @@ -1,5 +1,30 @@ -from spirit.game.data_utils import StadiumCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import StadiumCardDef, Ability, Activations +from spirit.game.attributes import Rarities, PokemonTypes, AttrID +from spirit.game.session.effects import is_evolution_pokemon + + +def _is_evolution_grass(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_evolution_pokemon(card) and PokemonTypes.GRASS.value in types + + +async def turffield_stadium(ctx): + """Once during each player's turn, that player may search their deck for + an Evolution Grass Pokemon, reveal it, and put it into their hand.""" + picks = await ctx.search_deck( + _is_evolution_grass, count=1, minimum=0, + prompt="Choose an Evolution Grass Pokémon to put into your hand.", + ) + await ctx.put_in_hand(picks, reveal=True) + await ctx.shuffle_deck() + + +TURFFIELD_STADIUM_ABILITY = Ability( + title="Turffield Stadium", + game_text="Once during each player's turn, that player may search their deck for an Evolution Grass Pokémon, reveal it, and put it into their hand. Then, that player shuffles their deck.", + activation=Activations.ONCE_PER_TURN, + effect=turffield_stadium, +) card = StadiumCardDef( guid="5d0a5bd5-64de-5d59-8401-2fcaa85d7c84", @@ -11,5 +36,5 @@ card = StadiumCardDef( collector_number=234, set_code="SWSH7", rarity=Rarities.RareSecret, - effect=unimplemented + ability=TURFFIELD_STADIUM_ABILITY, ) diff --git a/spirit/game/scripts/cards/SWSH7/UmbreonVMAX_214.py b/spirit/game/scripts/cards/SWSH7/UmbreonVMAX_214.py index 3687cd4..c87aaac 100644 --- a/spirit/game/scripts/cards/SWSH7/UmbreonVMAX_214.py +++ b/spirit/game/scripts/cards/SWSH7/UmbreonVMAX_214.py @@ -1,6 +1,24 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def dark_signal(ctx): + """When you play this Pokémon from your hand to evolve 1 of your Pokémon + during your turn, you may switch 1 of your opponent's Benched Pokémon + with their Active Pokémon.""" + bench = ctx.opponent_bench() + active = ctx.opponent_active() + if not bench or active is None or ctx.effects_blocked(active): + return + if await ctx.ask_yes_no( + "Switch 1 of your opponent's Benched Pokémon with their Active Pokémon?" + ): + target = await ctx.choose_pokemon( + bench, "Choose the opponent's new Active Pokémon" + ) or bench[0] + await ctx.switch_active(ctx.opponent_id, target) + + card = PokemonCardDef( guid="61d1488b-7dcb-5ad2-b373-6d284be885fe", key="SWSH7", @@ -22,7 +40,8 @@ card = PokemonCardDef( Ability( title="Dark Signal", game_text="When you play this Pok\u00e9mon from your hand to evolve 1 of your Pok\u00e9mon during your turn, you may switch 1 of your opponent's Benched Pok\u00e9mon with their Active Pok\u00e9mon.", - effect=unimplemented, + trigger=Triggers.ON_EVOLVE, + effect=dark_signal, ), Attack( title="Max Darkness", diff --git a/spirit/game/scripts/cards/SWSH7/UmbreonVMAX_215.py b/spirit/game/scripts/cards/SWSH7/UmbreonVMAX_215.py index 09d5226..eca4e91 100644 --- a/spirit/game/scripts/cards/SWSH7/UmbreonVMAX_215.py +++ b/spirit/game/scripts/cards/SWSH7/UmbreonVMAX_215.py @@ -1,6 +1,24 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def dark_signal(ctx): + """When you play this Pokémon from your hand to evolve 1 of your Pokémon + during your turn, you may switch 1 of your opponent's Benched Pokémon + with their Active Pokémon.""" + bench = ctx.opponent_bench() + active = ctx.opponent_active() + if not bench or active is None or ctx.effects_blocked(active): + return + if await ctx.ask_yes_no( + "Switch 1 of your opponent's Benched Pokémon with their Active Pokémon?" + ): + target = await ctx.choose_pokemon( + bench, "Choose the opponent's new Active Pokémon" + ) or bench[0] + await ctx.switch_active(ctx.opponent_id, target) + + card = PokemonCardDef( guid="42a3f01c-012f-5d6f-8781-5d132f748fa0", key="SWSH7", @@ -22,7 +40,8 @@ card = PokemonCardDef( Ability( title="Dark Signal", game_text="When you play this Pok\u00e9mon from your hand to evolve 1 of your Pok\u00e9mon during your turn, you may switch 1 of your opponent's Benched Pok\u00e9mon with their Active Pok\u00e9mon.", - effect=unimplemented, + trigger=Triggers.ON_EVOLVE, + effect=dark_signal, ), Attack( title="Max Darkness", diff --git a/spirit/game/scripts/cards/SWSH7/UmbreonVMAX_95.py b/spirit/game/scripts/cards/SWSH7/UmbreonVMAX_95.py index 8574c55..2091512 100644 --- a/spirit/game/scripts/cards/SWSH7/UmbreonVMAX_95.py +++ b/spirit/game/scripts/cards/SWSH7/UmbreonVMAX_95.py @@ -1,6 +1,24 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def dark_signal(ctx): + """When you play this Pokémon from your hand to evolve 1 of your Pokémon + during your turn, you may switch 1 of your opponent's Benched Pokémon + with their Active Pokémon.""" + bench = ctx.opponent_bench() + active = ctx.opponent_active() + if not bench or active is None or ctx.effects_blocked(active): + return + if await ctx.ask_yes_no( + "Switch 1 of your opponent's Benched Pokémon with their Active Pokémon?" + ): + target = await ctx.choose_pokemon( + bench, "Choose the opponent's new Active Pokémon" + ) or bench[0] + await ctx.switch_active(ctx.opponent_id, target) + + card = PokemonCardDef( guid="c74c4d46-2ba2-541c-85a2-87300e56988b", key="SWSH7", @@ -22,7 +40,8 @@ card = PokemonCardDef( Ability( title="Dark Signal", game_text="When you play this Pok\u00e9mon from your hand to evolve 1 of your Pok\u00e9mon during your turn, you may switch 1 of your opponent's Benched Pok\u00e9mon with their Active Pok\u00e9mon.", - effect=unimplemented, + trigger=Triggers.ON_EVOLVE, + effect=dark_signal, ), Attack( title="Max Darkness", diff --git a/spirit/game/scripts/cards/SWSH7/UmbreonV_188.py b/spirit/game/scripts/cards/SWSH7/UmbreonV_188.py index da46000..8222e96 100644 --- a/spirit/game/scripts/cards/SWSH7/UmbreonV_188.py +++ b/spirit/game/scripts/cards/SWSH7/UmbreonV_188.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, has_damage + + +async def mean_look(ctx): + await ctx.deal_damage() + defender = ctx.defender + if defender is not None and not ctx.effects_blocked(defender): + ctx.lock_retreat(defender) + card = PokemonCardDef( guid="e9503568-a733-59f3-9f7f-fe4cdd397b39", @@ -23,7 +32,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, the Defending Pok\u00e9mon can't retreat.", cost={PokemonTypes.DARKNESS: 1}, damage=30, - effect=unimplemented, + effect=mean_look, ), Attack( title="Moonlight Blade", @@ -31,7 +40,7 @@ card = PokemonCardDef( cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 2}, damage=80, damage_operator="+", - effect=unimplemented, + effect=bonus_if(has_damage("self"), 80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/UmbreonV_189.py b/spirit/game/scripts/cards/SWSH7/UmbreonV_189.py index 77661f7..eecd402 100644 --- a/spirit/game/scripts/cards/SWSH7/UmbreonV_189.py +++ b/spirit/game/scripts/cards/SWSH7/UmbreonV_189.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, has_damage + + +async def mean_look(ctx): + await ctx.deal_damage() + defender = ctx.defender + if defender is not None and not ctx.effects_blocked(defender): + ctx.lock_retreat(defender) + card = PokemonCardDef( guid="bf20e5ab-2237-598a-a83e-e44477161a2a", @@ -23,7 +32,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, the Defending Pok\u00e9mon can't retreat.", cost={PokemonTypes.DARKNESS: 1}, damage=30, - effect=unimplemented, + effect=mean_look, ), Attack( title="Moonlight Blade", @@ -31,7 +40,7 @@ card = PokemonCardDef( cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 2}, damage=80, damage_operator="+", - effect=unimplemented, + effect=bonus_if(has_damage("self"), 80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/UmbreonV_94.py b/spirit/game/scripts/cards/SWSH7/UmbreonV_94.py index 1eb2adf..3526777 100644 --- a/spirit/game/scripts/cards/SWSH7/UmbreonV_94.py +++ b/spirit/game/scripts/cards/SWSH7/UmbreonV_94.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, has_damage + + +async def mean_look(ctx): + await ctx.deal_damage() + defender = ctx.defender + if defender is not None and not ctx.effects_blocked(defender): + ctx.lock_retreat(defender) + card = PokemonCardDef( guid="b69dc488-99d7-5c97-b620-03a53bd1d211", @@ -23,7 +32,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, the Defending Pok\u00e9mon can't retreat.", cost={PokemonTypes.DARKNESS: 1}, damage=30, - effect=unimplemented, + effect=mean_look, ), Attack( title="Moonlight Blade", @@ -31,7 +40,7 @@ card = PokemonCardDef( cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 2}, damage=80, damage_operator="+", - effect=unimplemented, + effect=bonus_if(has_damage("self"), 80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/VaporeonVMAX_30.py b/spirit/game/scripts/cards/SWSH7/VaporeonVMAX_30.py index c042275..d8f801b 100644 --- a/spirit/game/scripts/cards/SWSH7/VaporeonVMAX_30.py +++ b/spirit/game/scripts/cards/SWSH7/VaporeonVMAX_30.py @@ -1,5 +1,37 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, has_damage +from spirit.game.card_effects.pokemon import is_energy_card, energy_provides_type +from spirit.game.session.effects import is_water_pokemon + + +def _is_water_energy(card): + return is_energy_card(card) and energy_provides_type(card, PokemonTypes.WATER) + + +async def bubble_pod(ctx): + candidates = [c for c in ctx.discard_pile() if is_water_pokemon(c)] + if not candidates: + return + picks = await ctx.choose_cards( + candidates, 1, minimum=0, + prompt="Choose a Water Pokémon from your discard pile to put onto your Bench.", + ) + if not picks: + return + pokemon = picks[0] + if not await ctx.bench_pokemon(pokemon): + return + energies = [c for c in ctx.discard_pile() if _is_water_energy(c)] + if not energies: + return + picks2 = await ctx.choose_cards( + energies, 3, minimum=0, + prompt="Choose up to 3 Water Energy cards from your discard pile to attach.", + ) + for energy in picks2: + await ctx.attach_energy(energy, pokemon) + card = PokemonCardDef( guid="45eb2749-287c-571c-8b28-bfd418da7845", @@ -23,7 +55,7 @@ card = PokemonCardDef( title="Bubble Pod", game_text="Put a Water Pok\u00e9mon from your discard pile onto your Bench. If you do, attach up to 3 Water Energy cards from your discard pile to that Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=bubble_pod, ), Attack( title="Max Torrent", @@ -31,7 +63,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=100, damage_operator="+", - effect=unimplemented, + effect=bonus_if(has_damage("defender"), 100), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/VaporeonV_172.py b/spirit/game/scripts/cards/SWSH7/VaporeonV_172.py index c4e35f8..03a851f 100644 --- a/spirit/game/scripts/cards/SWSH7/VaporeonV_172.py +++ b/spirit/game/scripts/cards/SWSH7/VaporeonV_172.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import draw_attack, switch_self_attack card = PokemonCardDef( guid="a150ebfb-bccf-5915-acc1-3dfc054fb9cb", @@ -22,14 +23,14 @@ card = PokemonCardDef( title="Triple Draw", game_text="Draw 3 cards.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=draw_attack(3), ), Attack( title="Splash Jump", game_text="Switch this Pok\u00e9mon with 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, damage=90, - effect=unimplemented, + effect=switch_self_attack(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Victini_20.py b/spirit/game/scripts/cards/SWSH7/Victini_20.py index 7a8ce6f..57e239e 100644 --- a/spirit/game/scripts/cards/SWSH7/Victini_20.py +++ b/spirit/game/scripts/cards/SWSH7/Victini_20.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_hand card = PokemonCardDef( guid="11f07bd1-dd96-53c4-90df-7e68a0a23e00", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="You may search your deck for up to 2 cards and put them into your hand. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 2}, damage=30, - effect=unimplemented, + effect=search_to_hand(count=2, minimum=0, reveal=False), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Vigoroth_130.py b/spirit/game/scripts/cards/SWSH7/Vigoroth_130.py index 1b1e51c..a2d53d6 100644 --- a/spirit/game/scripts/cards/SWSH7/Vigoroth_130.py +++ b/spirit/game/scripts/cards/SWSH7/Vigoroth_130.py @@ -1,6 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def shatter(ctx): + """20 damage, then discard a Stadium in play.""" + await ctx.deal_damage() + await ctx.discard_stadium() + card = PokemonCardDef( guid="c7c4b87d-f07b-5a0c-9485-cb50203669fb", key="SWSH7", @@ -24,7 +30,7 @@ card = PokemonCardDef( game_text="Discard a Stadium in play.", cost={PokemonTypes.COLORLESS: 1}, damage=20, - effect=unimplemented, + effect=shatter, ), Attack( title="Slash", diff --git a/spirit/game/scripts/cards/SWSH7/VolcaronaV_170.py b/spirit/game/scripts/cards/SWSH7/VolcaronaV_170.py index ae20da2..2f481e9 100644 --- a/spirit/game/scripts/cards/SWSH7/VolcaronaV_170.py +++ b/spirit/game/scripts/cards/SWSH7/VolcaronaV_170.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import self_energy_discard_attack +from spirit.game.card_effects.trainers import is_basic_energy_card + + +async def surging_flames(ctx): + """20 more damage per basic Energy in your discard; then shuffle those Energy into your deck.""" + energy_cards = [c for c in ctx.discard_pile(ctx.player_id) if is_basic_energy_card(c)] + await ctx.deal_damage(20 + 20 * len(energy_cards)) + if energy_cards: + await ctx.shuffle_into_deck(energy_cards, player_id=ctx.player_id) + + +fire_blast = self_energy_discard_attack(count=1) card = PokemonCardDef( guid="76d41205-ecdd-568e-a76a-3a5c0e808f45", @@ -24,14 +37,14 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=surging_flames, ), Attack( title="Fire Blast", game_text="Discard an Energy from this Pok\u00e9mon.", cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=160, - effect=unimplemented, + effect=fire_blast, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/VolcaronaV_21.py b/spirit/game/scripts/cards/SWSH7/VolcaronaV_21.py index e4ce57c..5fc3c36 100644 --- a/spirit/game/scripts/cards/SWSH7/VolcaronaV_21.py +++ b/spirit/game/scripts/cards/SWSH7/VolcaronaV_21.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import self_energy_discard_attack +from spirit.game.card_effects.trainers import is_basic_energy_card + + +async def surging_flames(ctx): + """20 more damage per basic Energy in your discard; then shuffle those Energy into your deck.""" + energy_cards = [c for c in ctx.discard_pile(ctx.player_id) if is_basic_energy_card(c)] + await ctx.deal_damage(20 + 20 * len(energy_cards)) + if energy_cards: + await ctx.shuffle_into_deck(energy_cards, player_id=ctx.player_id) + + +fire_blast = self_energy_discard_attack(count=1) card = PokemonCardDef( guid="af98160a-b9bc-53aa-92ca-5ede1e0ad8e9", @@ -24,14 +37,14 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=surging_flames, ), Attack( title="Fire Blast", game_text="Discard an Energy from this Pok\u00e9mon.", cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=160, - effect=unimplemented, + effect=fire_blast, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Wishiwashi_46.py b/spirit/game/scripts/cards/SWSH7/Wishiwashi_46.py index 80f76d4..f54fb24 100644 --- a/spirit/game/scripts/cards/SWSH7/Wishiwashi_46.py +++ b/spirit/game/scripts/cards/SWSH7/Wishiwashi_46.py @@ -1,5 +1,24 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per +from spirit.game.card_effects.pokemon import energy_provides_type +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.session.passives import Passive + + +class GroupPowerPassive(Passive): + """This Pokemon gets +150 HP if it has 3 or more Water Energy attached.""" + + def max_hp_bonus(self, pokemon, carrier): + if pokemon is not carrier: + return 0 + count = sum(1 for c in carrier.children + if energy_provides_type(c, PokemonTypes.WATER.value)) + return 150 if count >= 3 else 0 + + +def _basic_energy_count_self(ctx): + return sum(1 for e in ctx.attached_energies(ctx.attacker) if is_basic_energy_card(e)) card = PokemonCardDef( guid="1bb51612-663b-5e74-abc5-2e1d4005b13d", @@ -21,7 +40,7 @@ card = PokemonCardDef( Ability( title="Group Power", game_text="If this Pok\u00e9mon has 3 or more Water Energy attached, it gets +150 HP.", - effect=unimplemented, + passive=GroupPowerPassive(), ), Attack( title="Schooling Shot", @@ -29,7 +48,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=30, damage_operator="+", - effect=unimplemented, + effect=damage_per(_basic_energy_count_self, 30, base=30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH7/Wobbuffet_66.py b/spirit/game/scripts/cards/SWSH7/Wobbuffet_66.py index f538b51..0e6107c 100644 --- a/spirit/game/scripts/cards/SWSH7/Wobbuffet_66.py +++ b/spirit/game/scripts/cards/SWSH7/Wobbuffet_66.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_counters_on + + +async def mirror_pain(ctx): + bench = ctx.my_bench() + if not bench: + return + target = await ctx.choose_pokemon(bench, "Choose 1 of your Benched Pokémon") + if target is None: + return + count = damage_counters_on(lambda c: target)(ctx) + if count > 0 and ctx.defender is not None: + await ctx.deal_damage(count * 10, target=ctx.defender, as_counters=True) + card = PokemonCardDef( guid="aaa3375b-63e4-5044-ac23-7a3a29b128fe", @@ -23,7 +37,7 @@ card = PokemonCardDef( title="Mirror Pain", game_text="Put damage counters on your opponent's Active Pok\u00e9mon equal to the number of damage counters on 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + effect=mirror_pain, ), Attack( title="Headbutt Bounce", diff --git a/spirit/game/scripts/cards/SWSH7/Woobat_68.py b/spirit/game/scripts/cards/SWSH7/Woobat_68.py index 314d5fd..353a1bd 100644 --- a/spirit/game/scripts/cards/SWSH7/Woobat_68.py +++ b/spirit/game/scripts/cards/SWSH7/Woobat_68.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import draw_attack card = PokemonCardDef( guid="d944ea18-b0f5-57e1-8d3f-ac3082d81c49", @@ -23,7 +24,7 @@ card = PokemonCardDef( title="Collect", game_text="Draw a card.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=draw_attack(1), ), Attack( title="Gnaw", diff --git a/spirit/game/scripts/cards/SWSH7/ZinniasResolve_164.py b/spirit/game/scripts/cards/SWSH7/ZinniasResolve_164.py index 00226c8..f270dc1 100644 --- a/spirit/game/scripts/cards/SWSH7/ZinniasResolve_164.py +++ b/spirit/game/scripts/cards/SWSH7/ZinniasResolve_164.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import hand_size_at_least + + +async def zinnias_resolve(ctx): + """Discard 2 other cards, then draw a card for each opposing Pokemon in play.""" + if not await ctx.discard_from_hand(2, prompt="Discard 2 cards for Zinnia's Resolve"): + return + await ctx.draw_cards(len(ctx.opponent_pokemon_in_play())) + card = SupporterCardDef( guid="a6c6b139-7377-53b7-a5ac-160a7fce8981", @@ -11,5 +20,6 @@ card = SupporterCardDef( collector_number=164, set_code="SWSH7", rarity=Rarities.Uncommon, - effect=unimplemented + effect=zinnias_resolve, + condition=hand_size_at_least(3), ) diff --git a/spirit/game/scripts/cards/SWSH7/ZinniasResolve_203.py b/spirit/game/scripts/cards/SWSH7/ZinniasResolve_203.py index 9b5756c..f1df817 100644 --- a/spirit/game/scripts/cards/SWSH7/ZinniasResolve_203.py +++ b/spirit/game/scripts/cards/SWSH7/ZinniasResolve_203.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import hand_size_at_least + + +async def zinnias_resolve(ctx): + """Discard 2 other cards, then draw a card for each opposing Pokemon in play.""" + if not await ctx.discard_from_hand(2, prompt="Discard 2 cards for Zinnia's Resolve"): + return + await ctx.draw_cards(len(ctx.opponent_pokemon_in_play())) + card = SupporterCardDef( guid="06141105-31aa-5b68-b6ea-5bbd8ffa3b33", @@ -11,5 +20,6 @@ card = SupporterCardDef( collector_number=203, set_code="SWSH7", rarity=Rarities.RareUltra, - effect=unimplemented + effect=zinnias_resolve, + condition=hand_size_at_least(3), ) diff --git a/spirit/game/scripts/cards/SWSH7/ZinniasResolve_225.py b/spirit/game/scripts/cards/SWSH7/ZinniasResolve_225.py index 28d92ea..7e67747 100644 --- a/spirit/game/scripts/cards/SWSH7/ZinniasResolve_225.py +++ b/spirit/game/scripts/cards/SWSH7/ZinniasResolve_225.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import hand_size_at_least + + +async def zinnias_resolve(ctx): + """Discard 2 other cards, then draw a card for each opposing Pokemon in play.""" + if not await ctx.discard_from_hand(2, prompt="Discard 2 cards for Zinnia's Resolve"): + return + await ctx.draw_cards(len(ctx.opponent_pokemon_in_play())) + card = SupporterCardDef( guid="c68218d1-4f64-5bb9-a0e6-d2dbe06e7995", @@ -11,5 +20,6 @@ card = SupporterCardDef( collector_number=225, set_code="SWSH7", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=zinnias_resolve, + condition=hand_size_at_least(3), ) diff --git a/spirit/game/scripts/cards/SWSH7/Zoroark_103.py b/spirit/game/scripts/cards/SWSH7/Zoroark_103.py index e918a14..6f438ef 100644 --- a/spirit/game/scripts/cards/SWSH7/Zoroark_103.py +++ b/spirit/game/scripts/cards/SWSH7/Zoroark_103.py @@ -1,4 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.pokemon import ( + phantom_transformation, phantom_transformation_condition, +) +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -22,7 +25,9 @@ card = PokemonCardDef( Ability( title="Phantom Transformation", game_text="Once during your turn, you may choose a Stage 1 Pok\u00e9mon, except any Zoroark, from your discard pile. If you do, discard this Pok\u00e9mon and all attached cards, and put the chosen Pok\u00e9mon in its place.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=phantom_transformation_condition, + effect=phantom_transformation, ), Attack( title="Night Daze", diff --git a/spirit/game/scripts/cards/SWSH7/Zygarde_118.py b/spirit/game/scripts/cards/SWSH7/Zygarde_118.py index 68e6653..f229fbc 100644 --- a/spirit/game/scripts/cards/SWSH7/Zygarde_118.py +++ b/spirit/game/scripts/cards/SWSH7/Zygarde_118.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import count_prizes_taken + + +async def judgment_surge(ctx): + """40 damage to 1 of your opponent's Pokemon for each Prize card your + opponent has taken. (No W/R for a Benched target -- engine default.)""" + amount = 40 * count_prizes_taken("opponent")(ctx) + if amount <= 0: + return + candidates = ctx.opponent_pokemon_in_play() + if not candidates: + return + target = await ctx.choose_pokemon(candidates, "Choose 1 of your opponent's Pokémon") + if target is not None: + await ctx.deal_damage(amount, target=target) + card = PokemonCardDef( guid="5e905c75-3ffb-56f0-8f7a-160f098b09f1", @@ -26,7 +42,7 @@ card = PokemonCardDef( title="Judgment Surge", game_text="This attack does 40 damage to 1 of your opponent's Pok\u00e9mon for each Prize card your opponent has taken. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.GRASS: 1, PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=judgment_surge, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Absol_164.py b/spirit/game/scripts/cards/SWSH8/Absol_164.py index cca3a36..9a5acbc 100644 --- a/spirit/game/scripts/cards/SWSH8/Absol_164.py +++ b/spirit/game/scripts/cards/SWSH8/Absol_164.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.support_common import gust_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Drag Off", game_text="Switch 1 of your opponent's Benched Pok\u00e9mon with their Active Pok\u00e9mon. This attack does 30 damage to the new Active Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + effect=gust_attack(damage_to_new_active=30), ), Attack( title="Slash", diff --git a/spirit/game/scripts/cards/SWSH8/Accelgor_14.py b/spirit/game/scripts/cards/SWSH8/Accelgor_14.py index de68f97..08cbf46 100644 --- a/spirit/game/scripts/cards/SWSH8/Accelgor_14.py +++ b/spirit/game/scripts/cards/SWSH8/Accelgor_14.py @@ -1,5 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.passives import Passive + + +class NinjaTornadoPassive(Passive): + """If this Pokemon moved from Bench to Active this turn, Ninja Tornado + can be used for Grass.""" + + def modify_attack_cost(self, cost, pokemon, carrier, board): + if pokemon is not carrier: + return cost + state = board.turn_state + if state and state.became_active_turn.get(pokemon.entity_id) == state.turn_number: + return {"Grass": 1} + return cost + card = PokemonCardDef( guid="e20dccee-ec10-537b-99fe-b06eab63e0d9", @@ -18,13 +33,13 @@ card = PokemonCardDef( weakness_type=PokemonTypes.FIRE, evolves_from="com.direwolfdigital.cake.data.archetypes.pokemon.Shelmet.Name", family_id=616, + passive=NinjaTornadoPassive(), abilities=[ Attack( title="Ninja Tornado", game_text="If this Pok\u00e9mon moved from your Bench to the Active Spot this turn, this attack can be used for Grass.", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 2}, damage=120, - effect=unimplemented, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/AdventurersDiscovery_224.py b/spirit/game/scripts/cards/SWSH8/AdventurersDiscovery_224.py index f8ec659..0497251 100644 --- a/spirit/game/scripts/cards/SWSH8/AdventurersDiscovery_224.py +++ b/spirit/game/scripts/cards/SWSH8/AdventurersDiscovery_224.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef, is_pokemon_v from spirit.game.attributes import Rarities +from spirit.game.card_effects.support_common import search_to_hand card = SupporterCardDef( guid="8b2a1f93-0a8e-5180-8346-621c218ef61e", @@ -11,5 +12,8 @@ card = SupporterCardDef( collector_number=224, set_code="SWSH8", rarity=Rarities.Uncommon, - effect=unimplemented + effect=search_to_hand( + lambda c: is_pokemon_v(c.archetype_id), count=3, + prompt="Choose up to 3 Pokémon V", + ), ) diff --git a/spirit/game/scripts/cards/SWSH8/AppletunV_26.py b/spirit/game/scripts/cards/SWSH8/AppletunV_26.py index ef63bd6..fb78858 100644 --- a/spirit/game/scripts/cards/SWSH8/AppletunV_26.py +++ b/spirit/game/scripts/cards/SWSH8/AppletunV_26.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import heal_attack card = PokemonCardDef( guid="527eb679-687b-5260-b684-11c4a4316bb7", @@ -28,7 +29,7 @@ card = PokemonCardDef( game_text="Heal 30 damage from this Pok\u00e9mon.", cost={PokemonTypes.GRASS: 2, PokemonTypes.COLORLESS: 1}, damage=100, - effect=unimplemented, + effect=heal_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Araquanid_20.py b/spirit/game/scripts/cards/SWSH8/Araquanid_20.py index b063654..4d6be5a 100644 --- a/spirit/game/scripts/cards/SWSH8/Araquanid_20.py +++ b/spirit/game/scripts/cards/SWSH8/Araquanid_20.py @@ -1,6 +1,25 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def bubble_launch(ctx): + await ctx.deal_damage() + bench = ctx.my_bench() + energies = ctx.attached_energies(ctx.attacker) + if not bench or not energies: + return + picks = await ctx.choose_cards( + energies, 1, prompt="Choose an Energy to move to 1 of your Benched Pokémon" + ) + if not picks: + return + target = await ctx.choose_pokemon( + bench, "Choose a Benched Pokémon to move the Energy to" + ) + if target is not None: + await ctx.move_energy(picks[0], target) + + card = PokemonCardDef( guid="9a29aab5-cc35-5543-afe8-bd6f441356df", key="SWSH8", @@ -29,7 +48,7 @@ card = PokemonCardDef( game_text="Move an Energy from this Pok\u00e9mon to 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 2}, damage=110, - effect=unimplemented, + effect=bubble_launch, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Arcanine_33.py b/spirit/game/scripts/cards/SWSH8/Arcanine_33.py index 63cc1bc..b2cc87d 100644 --- a/spirit/game/scripts/cards/SWSH8/Arcanine_33.py +++ b/spirit/game/scripts/cards/SWSH8/Arcanine_33.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack card = PokemonCardDef( guid="fa4df75e-71a6-5a9b-a7ed-fd2ab7eb26fc", @@ -29,7 +30,7 @@ card = PokemonCardDef( game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=160, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Azumarill_59.py b/spirit/game/scripts/cards/SWSH8/Azumarill_59.py index 66c0e85..a9eeace 100644 --- a/spirit/game/scripts/cards/SWSH8/Azumarill_59.py +++ b/spirit/game/scripts/cards/SWSH8/Azumarill_59.py @@ -1,5 +1,9 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import recover_from_discard, requires_discard +from spirit.game.session.effects import is_pokemon_card, is_supporter_card + +_is_pokemon_or_supporter = lambda c: is_pokemon_card(c) or is_supporter_card(c) card = PokemonCardDef( guid="076f5f84-1c8e-59e3-a57b-f3856fbdd408", @@ -23,7 +27,11 @@ card = PokemonCardDef( title="Dive and Rescue", game_text="Put up to 3 in any combination of Pok\u00e9mon and Supporter cards from your discard pile into your hand.", cost={PokemonTypes.WATER: 1}, - effect=unimplemented, + condition=requires_discard(_is_pokemon_or_supporter), + effect=recover_from_discard( + _is_pokemon_or_supporter, count=3, + prompt="Choose up to 3 Pok\u00e9mon/Supporter cards from your discard pile.", + ), ), Attack( title="Surf", diff --git a/spirit/game/scripts/cards/SWSH8/Basculin_70.py b/spirit/game/scripts/cards/SWSH8/Basculin_70.py index ac44b50..9d45f34 100644 --- a/spirit/game/scripts/cards/SWSH8/Basculin_70.py +++ b/spirit/game/scripts/cards/SWSH8/Basculin_70.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, damage_counters_on card = PokemonCardDef( guid="0b35bd0c-db87-5d36-ad7b-96d32772b7ba", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 1}, damage=30, damage_operator="+", - effect=unimplemented, + effect=damage_per(damage_counters_on("defender"), 10, base=30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Blissey_203.py b/spirit/game/scripts/cards/SWSH8/Blissey_203.py index 4011e70..69692aa 100644 --- a/spirit/game/scripts/cards/SWSH8/Blissey_203.py +++ b/spirit/game/scripts/cards/SWSH8/Blissey_203.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_bench, has_attack_titled +from spirit.game.card_effects.passives_common import prevent_damage_when +from spirit.game.card_effects.pokemon import is_pokemon_vmax + +_has_rollout = has_attack_titled("Let's All Rollout") + + +def _expert_in_roundness_pred(calc, carrier): + return ( + calc.target.owning_player_id == carrier.owning_player_id + and _has_rollout(calc.target) + and is_pokemon_vmax(calc.attacker.archetype_id) + ) + card = PokemonCardDef( guid="d4a569ea-e41b-5229-8588-ae1f7ffd1c17", @@ -21,16 +35,16 @@ card = PokemonCardDef( abilities=[ Ability( title="Expert in Roundness", - game_text="Prevent all damage done to each of your Pok\u00e9mon that has the Let's All Rollout attack by attacks from your opponent's Pok\u00e9mon VMAX.", - effect=unimplemented, + game_text="Prevent all damage done to each of your Pokémon that has the Let's All Rollout attack by attacks from your opponent's Pokémon VMAX.", + passive=prevent_damage_when(_expert_in_roundness_pred), ), Attack( title="Let's All Rollout", - game_text="This attack does 20 damage for each of your Benched Pok\u00e9mon that has the Let's All Rollout attack.", + game_text="This attack does 20 damage for each of your Benched Pokémon that has the Let's All Rollout attack.", cost={PokemonTypes.COLORLESS: 3}, damage=20, damage_operator="x", - effect=unimplemented, + effect=damage_per(count_bench("mine", pred=_has_rollout), 20), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH8/BoltundVMAX_104.py b/spirit/game/scripts/cards/SWSH8/BoltundVMAX_104.py index 40633e4..bd766af 100644 --- a/spirit/game/scripts/cards/SWSH8/BoltundVMAX_104.py +++ b/spirit/game/scripts/cards/SWSH8/BoltundVMAX_104.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy card = PokemonCardDef( guid="eb521319-ab64-512c-8567-6ee94f382ff6", @@ -25,14 +26,14 @@ card = PokemonCardDef( cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 1}, damage=30, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("mine", energy_type=PokemonTypes.LIGHTNING), 30, base=0), ), Attack( title="Max Bolt", game_text="During your next turn, this Pok\u00e9mon can't use Max Bolt.", cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 1}, damage=230, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/BoltundVMAX_267.py b/spirit/game/scripts/cards/SWSH8/BoltundVMAX_267.py index c6598f8..dc6af4b 100644 --- a/spirit/game/scripts/cards/SWSH8/BoltundVMAX_267.py +++ b/spirit/game/scripts/cards/SWSH8/BoltundVMAX_267.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy card = PokemonCardDef( guid="2178b73e-cd1d-56b9-914d-5fcb1c06e43b", @@ -25,14 +26,14 @@ card = PokemonCardDef( cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 1}, damage=30, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("mine", energy_type=PokemonTypes.LIGHTNING), 30, base=0), ), Attack( title="Max Bolt", game_text="During your next turn, this Pok\u00e9mon can't use Max Bolt.", cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 1}, damage=230, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/BreloomV_6.py b/spirit/game/scripts/cards/SWSH8/BreloomV_6.py index 2d65402..a68ccfd 100644 --- a/spirit/game/scripts/cards/SWSH8/BreloomV_6.py +++ b/spirit/game/scripts/cards/SWSH8/BreloomV_6.py @@ -1,6 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def counter(ctx): + """20, plus that much more damage if this Pokémon was damaged by an attack during the opponent's last turn.""" + bonus = ctx.damage_taken_last_turn(ctx.attacker) + await ctx.deal_damage(20 + bonus) + + card = PokemonCardDef( guid="987c6de5-d7c3-58af-9d18-be118960ada4", key="SWSH8", @@ -20,11 +27,11 @@ card = PokemonCardDef( abilities=[ Attack( title="Counter", - game_text="If this Pok\u00e9mon was damaged by an attack during your opponent's last turn, this attack does that much more damage.", + game_text="If this Pokémon was damaged by an attack during your opponent's last turn, this attack does that much more damage.", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=counter, ), Attack( title="Mach Cross", @@ -32,4 +39,4 @@ card = PokemonCardDef( damage=140, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH8/Breloom_5.py b/spirit/game/scripts/cards/SWSH8/Breloom_5.py index c6d6c54..a008304 100644 --- a/spirit/game/scripts/cards/SWSH8/Breloom_5.py +++ b/spirit/game/scripts/cards/SWSH8/Breloom_5.py @@ -1,4 +1,4 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -26,10 +26,10 @@ card = PokemonCardDef( ), Attack( title="Impact Blow", - game_text="During your next turn, this Pok\u00e9mon can't use Impact Blow.", + game_text="During your next turn, this Pokémon can't use Impact Blow.", cost={PokemonTypes.GRASS: 2, PokemonTypes.COLORLESS: 1}, damage=150, - effect=unimplemented, + locks_next_turn=True, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH8/BugCatcher_226.py b/spirit/game/scripts/cards/SWSH8/BugCatcher_226.py index 8182715..646d32a 100644 --- a/spirit/game/scripts/cards/SWSH8/BugCatcher_226.py +++ b/spirit/game/scripts/cards/SWSH8/BugCatcher_226.py @@ -1,6 +1,15 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities + +async def bug_catcher(ctx): + """Draw 2 cards. Flip a coin; if heads, draw 2 more cards.""" + await ctx.draw_cards(2) + heads = (await ctx.flip_coins(1, "Bug Catcher"))[0] + if heads: + await ctx.draw_cards(2) + + card = SupporterCardDef( guid="0c008faf-9553-50c3-b40b-b9249c38d9bb", key="SWSH8", @@ -11,5 +20,5 @@ card = SupporterCardDef( collector_number=226, set_code="SWSH8", rarity=Rarities.Uncommon, - effect=unimplemented + effect=bug_catcher ) diff --git a/spirit/game/scripts/cards/SWSH8/Buneary_212.py b/spirit/game/scripts/cards/SWSH8/Buneary_212.py index e9d2c8b..ebe516c 100644 --- a/spirit/game/scripts/cards/SWSH8/Buneary_212.py +++ b/spirit/game/scripts/cards/SWSH8/Buneary_212.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="f9b28432-ac03-527e-9327-7361d7073393", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=20, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=2, per_heads=20), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH8/Bunnelby_214.py b/spirit/game/scripts/cards/SWSH8/Bunnelby_214.py index da0cfe3..44b2aa4 100644 --- a/spirit/game/scripts/cards/SWSH8/Bunnelby_214.py +++ b/spirit/game/scripts/cards/SWSH8/Bunnelby_214.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.card_effects.attacks_common import recoil_attack +from spirit.game.session.effects import is_pokemon_card card = PokemonCardDef( guid="3ef94741-c37c-5bb0-b495-8c1490db536a", @@ -22,14 +25,17 @@ card = PokemonCardDef( title="Find a Friend", game_text="Search your deck for a Pok\u00e9mon, reveal it, and put it into your hand. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_to_hand( + is_pokemon_card, count=1, minimum=0, reveal=True, + prompt="Choose a Pok\u00e9mon to put into your hand.", + ), ), Attack( title="Take Down", game_text="This Pok\u00e9mon also does 10 damage to itself.", cost={PokemonTypes.COLORLESS: 2}, damage=30, - effect=unimplemented, + effect=recoil_attack(10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Butterfree_3.py b/spirit/game/scripts/cards/SWSH8/Butterfree_3.py index 66d3b08..f567102 100644 --- a/spirit/game/scripts/cards/SWSH8/Butterfree_3.py +++ b/spirit/game/scripts/cards/SWSH8/Butterfree_3.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions + + +async def tricolored_scales(ctx): + """You may make your opponent's Active Pokémon Burned, Confused, and Poisoned.""" + if not await ctx.ask_yes_no( + "Make your opponent's Active Pokémon Burned, Confused, and Poisoned?" + ): + return + target = ctx.opponent_active() + if target is None: + return + for condition in ( + SpecialConditions.BURNED, SpecialConditions.CONFUSED, + SpecialConditions.POISONED, + ): + await ctx.apply_special_condition(target, condition) + card = PokemonCardDef( guid="2dd60b54-9a33-5572-ae63-d7c07cbb1daf", @@ -22,7 +39,8 @@ card = PokemonCardDef( Ability( title="Tricolored Scales", game_text="When you play this Pok\u00e9mon from your hand to evolve 1 of your Pok\u00e9mon during your turn, you may make your opponent's Active Pok\u00e9mon Burned, Confused, and Poisoned.", - effect=unimplemented, + trigger=Triggers.ON_EVOLVE, + effect=tricolored_scales, ), Attack( title="Gust", diff --git a/spirit/game/scripts/cards/SWSH8/Caterpie_1.py b/spirit/game/scripts/cards/SWSH8/Caterpie_1.py index 6f5c1bb..3fa5ab4 100644 --- a/spirit/game/scripts/cards/SWSH8/Caterpie_1.py +++ b/spirit/game/scripts/cards/SWSH8/Caterpie_1.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.support_common import search_to_bench card = PokemonCardDef( guid="d36b8686-b192-5b3e-a01b-bb33476b9663", @@ -22,7 +23,11 @@ card = PokemonCardDef( title="Flock", game_text="Search your deck for a Caterpie and put it onto your Bench. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_to_bench( + predicate=lambda c: c.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) == "Caterpie", + count=1, + prompt="Choose a Caterpie to put onto your Bench.", + ), ), Attack( title="Bug Bite", diff --git a/spirit/game/scripts/cards/SWSH8/CelebiV_245.py b/spirit/game/scripts/cards/SWSH8/CelebiV_245.py index 7872f61..96f7a2d 100644 --- a/spirit/game/scripts/cards/SWSH8/CelebiV_245.py +++ b/spirit/game/scripts/cards/SWSH8/CelebiV_245.py @@ -1,6 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.support_common import distribute_energy, switch_self_attack +from spirit.game.card_effects.trainers import is_grass_energy_card +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def leaflet_dance(ctx): + """Attach any number of Grass Energy cards from your hand, any way you like.""" + grass = [c for c in ctx.hand() if is_grass_energy_card(c)] + if not grass: + return + picks = await ctx.choose_cards( + grass, len(grass), minimum=0, + prompt="Choose any number of Grass Energy cards to attach to your Pokémon.", + ) + if picks: + await distribute_energy(ctx, picks, ctx.my_pokemon_in_play()) + card = PokemonCardDef( guid="6abda803-80cf-59a0-95ee-469b0c2e54ed", key="SWSH8", @@ -22,14 +37,14 @@ card = PokemonCardDef( title="Leaflet Dance", game_text="Attach any number of Grass Energy cards from your hand to your Pok\u00e9mon in any way you like.", cost={PokemonTypes.GRASS: 1}, - effect=unimplemented, + effect=leaflet_dance, ), Attack( title="Slash Back", game_text="Switch this Pok\u00e9mon with 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=60, - effect=unimplemented, + effect=switch_self_attack(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Centiskorch_48.py b/spirit/game/scripts/cards/SWSH8/Centiskorch_48.py index d3de502..f2ca03f 100644 --- a/spirit/game/scripts/cards/SWSH8/Centiskorch_48.py +++ b/spirit/game/scripts/cards/SWSH8/Centiskorch_48.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import boost_own_next_turn card = PokemonCardDef( guid="45371b46-870d-5135-874e-d317ae12c854", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="During your next turn, this Pok\u00e9mon's attacks do 90 more damage to your opponent's Active Pok\u00e9mon (before applying Weakness and Resistance).", cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=boost_own_next_turn(90), ), Attack( title="Burning Train", diff --git a/spirit/game/scripts/cards/SWSH8/ChandelureVMAX_265.py b/spirit/game/scripts/cards/SWSH8/ChandelureVMAX_265.py index a729d2d..443f76b 100644 --- a/spirit/game/scripts/cards/SWSH8/ChandelureVMAX_265.py +++ b/spirit/game/scripts/cards/SWSH8/ChandelureVMAX_265.py @@ -1,5 +1,27 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities, TrainerType +from spirit.game.session.effects import is_trainer_card +from spirit.game.session.passives import Passive +from spirit.game.card_effects.passives_common import is_in_active_spot + + +class CursedShimmerPassive(Passive): + def blocks_trainer_play(self, card, player_id, carrier): + if player_id == carrier.owning_player_id: + return False + if not is_in_active_spot(carrier): + return False + return card.get_attribute(AttrID.TRAINER_TYPE) in ( + TrainerType.POKEMON_TOOL.value, TrainerType.POKEMON_TOOL_F.value, + ) + + +async def max_poltergeist(ctx): + """Opponent reveals hand; 70 damage for each Trainer card found there.""" + hand = await ctx.reveal_hand(ctx.opponent_id) + count = sum(1 for c in hand if is_trainer_card(c)) + await ctx.deal_damage(70 * count) + card = PokemonCardDef( guid="205a08f9-5a0d-5790-9e0f-3e4a6400e7c6", @@ -22,7 +44,7 @@ card = PokemonCardDef( Ability( title="Cursed Shimmer", game_text="As long as this Pok\u00e9mon is in the Active Spot, your opponent can't play any Pok\u00e9mon Tool cards from their hand.", - effect=unimplemented, + passive=CursedShimmerPassive(), ), Attack( title="Max Poltergeist", @@ -30,7 +52,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 1}, damage=70, damage_operator="x", - effect=unimplemented, + effect=max_poltergeist, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/ChandelureVMAX_40.py b/spirit/game/scripts/cards/SWSH8/ChandelureVMAX_40.py index 74552f3..3c38206 100644 --- a/spirit/game/scripts/cards/SWSH8/ChandelureVMAX_40.py +++ b/spirit/game/scripts/cards/SWSH8/ChandelureVMAX_40.py @@ -1,5 +1,27 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities, TrainerType +from spirit.game.session.effects import is_trainer_card +from spirit.game.session.passives import Passive +from spirit.game.card_effects.passives_common import is_in_active_spot + + +class CursedShimmerPassive(Passive): + def blocks_trainer_play(self, card, player_id, carrier): + if player_id == carrier.owning_player_id: + return False + if not is_in_active_spot(carrier): + return False + return card.get_attribute(AttrID.TRAINER_TYPE) in ( + TrainerType.POKEMON_TOOL.value, TrainerType.POKEMON_TOOL_F.value, + ) + + +async def max_poltergeist(ctx): + """Opponent reveals hand; 70 damage for each Trainer card found there.""" + hand = await ctx.reveal_hand(ctx.opponent_id) + count = sum(1 for c in hand if is_trainer_card(c)) + await ctx.deal_damage(70 * count) + card = PokemonCardDef( guid="8fa1e990-b980-514c-8589-5c9a652d6ef9", @@ -22,7 +44,7 @@ card = PokemonCardDef( Ability( title="Cursed Shimmer", game_text="As long as this Pok\u00e9mon is in the Active Spot, your opponent can't play any Pok\u00e9mon Tool cards from their hand.", - effect=unimplemented, + passive=CursedShimmerPassive(), ), Attack( title="Max Poltergeist", @@ -30,7 +52,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 1}, damage=70, damage_operator="x", - effect=unimplemented, + effect=max_poltergeist, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/ChandelureV_247.py b/spirit/game/scripts/cards/SWSH8/ChandelureV_247.py index afd65d1..37ae1f8 100644 --- a/spirit/game/scripts/cards/SWSH8/ChandelureV_247.py +++ b/spirit/game/scripts/cards/SWSH8/ChandelureV_247.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.session.effects import is_trainer_card + + +async def poltergeist(ctx): + """Opponent reveals their hand; 40 damage for each Trainer card found there.""" + cards = await ctx.reveal_hand(of_player=ctx.opponent_id) + count = sum(1 for c in cards if is_trainer_card(c)) + await ctx.deal_damage(40 * count) + card = PokemonCardDef( guid="93b88900-2afa-5180-80dd-291142165c2e", @@ -22,7 +32,7 @@ card = PokemonCardDef( title="Confuse Ray", game_text="Your opponent's Active Pok\u00e9mon is now Confused.", cost={PokemonTypes.FIRE: 1}, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED), ), Attack( title="Poltergeist", @@ -30,7 +40,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 1}, damage=40, damage_operator="x", - effect=unimplemented, + effect=poltergeist, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/ChandelureV_39.py b/spirit/game/scripts/cards/SWSH8/ChandelureV_39.py index f3af478..ebcf909 100644 --- a/spirit/game/scripts/cards/SWSH8/ChandelureV_39.py +++ b/spirit/game/scripts/cards/SWSH8/ChandelureV_39.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.session.effects import is_trainer_card + + +async def poltergeist(ctx): + """Opponent reveals their hand; 40 damage for each Trainer card found there.""" + cards = await ctx.reveal_hand(of_player=ctx.opponent_id) + count = sum(1 for c in cards if is_trainer_card(c)) + await ctx.deal_damage(40 * count) + card = PokemonCardDef( guid="dbfbb3b0-ef85-5940-9f80-9c1c7a858990", @@ -22,7 +32,7 @@ card = PokemonCardDef( title="Confuse Ray", game_text="Your opponent's Active Pok\u00e9mon is now Confused.", cost={PokemonTypes.FIRE: 1}, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED), ), Attack( title="Poltergeist", @@ -30,7 +40,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 1}, damage=40, damage_operator="x", - effect=unimplemented, + effect=poltergeist, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Chansey_202.py b/spirit/game/scripts/cards/SWSH8/Chansey_202.py index 7e9d161..04dae7f 100644 --- a/spirit/game/scripts/cards/SWSH8/Chansey_202.py +++ b/spirit/game/scripts/cards/SWSH8/Chansey_202.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import heal_attack card = PokemonCardDef( guid="3247386c-03e9-5d74-a1f9-6ecbbd44f4bd", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Heal 30 damage from this Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 2}, damage=30, - effect=unimplemented, + effect=heal_attack(30), ), Attack( title="Gentle Slap", diff --git a/spirit/game/scripts/cards/SWSH8/ChiliCilanCress_227.py b/spirit/game/scripts/cards/SWSH8/ChiliCilanCress_227.py index ce8a2ee..4d77be4 100644 --- a/spirit/game/scripts/cards/SWSH8/ChiliCilanCress_227.py +++ b/spirit/game/scripts/cards/SWSH8/ChiliCilanCress_227.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef, subtypes_for from spirit.game.attributes import Rarities +from spirit.game.session.effects import is_pokemon_card +from spirit.game.card_effects.support_common import search_to_hand + + +def _is_fusion_strike_pokemon(card): + return is_pokemon_card(card) and "Fusion Strike" in subtypes_for(card.archetype_id) + card = SupporterCardDef( guid="1785ebb0-0bb9-5195-a65f-6edae527340e", @@ -11,5 +18,8 @@ card = SupporterCardDef( collector_number=227, set_code="SWSH8", rarity=Rarities.Uncommon, - effect=unimplemented + effect=search_to_hand( + _is_fusion_strike_pokemon, count=3, reveal=True, + prompt="Choose up to 3 Fusion Strike Pokémon.", + ), ) diff --git a/spirit/game/scripts/cards/SWSH8/ChiliCilanCress_258.py b/spirit/game/scripts/cards/SWSH8/ChiliCilanCress_258.py index c81f4cc..f7aa43f 100644 --- a/spirit/game/scripts/cards/SWSH8/ChiliCilanCress_258.py +++ b/spirit/game/scripts/cards/SWSH8/ChiliCilanCress_258.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef, subtypes_for from spirit.game.attributes import Rarities +from spirit.game.session.effects import is_pokemon_card +from spirit.game.card_effects.support_common import search_to_hand + + +def _is_fusion_strike_pokemon(card): + return is_pokemon_card(card) and "Fusion Strike" in subtypes_for(card.archetype_id) + card = SupporterCardDef( guid="ea052efc-411d-50d3-91e2-008aa58b9866", @@ -11,5 +18,8 @@ card = SupporterCardDef( collector_number=258, set_code="SWSH8", rarity=Rarities.RareUltra, - effect=unimplemented + effect=search_to_hand( + _is_fusion_strike_pokemon, count=3, reveal=True, + prompt="Choose up to 3 Fusion Strike Pokémon.", + ), ) diff --git a/spirit/game/scripts/cards/SWSH8/ChiliCilanCress_273.py b/spirit/game/scripts/cards/SWSH8/ChiliCilanCress_273.py index f215487..21cd38f 100644 --- a/spirit/game/scripts/cards/SWSH8/ChiliCilanCress_273.py +++ b/spirit/game/scripts/cards/SWSH8/ChiliCilanCress_273.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef, subtypes_for from spirit.game.attributes import Rarities +from spirit.game.session.effects import is_pokemon_card +from spirit.game.card_effects.support_common import search_to_hand + + +def _is_fusion_strike_pokemon(card): + return is_pokemon_card(card) and "Fusion Strike" in subtypes_for(card.archetype_id) + card = SupporterCardDef( guid="1fe4b301-8a19-596a-a8a6-2d3b32237e23", @@ -11,5 +18,8 @@ card = SupporterCardDef( collector_number=273, set_code="SWSH8", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=search_to_hand( + _is_fusion_strike_pokemon, count=3, reveal=True, + prompt="Choose up to 3 Fusion Strike Pokémon.", + ), ) diff --git a/spirit/game/scripts/cards/SWSH8/CinderaceVMAX_45.py b/spirit/game/scripts/cards/SWSH8/CinderaceVMAX_45.py index 7bbb214..df2bdfa 100644 --- a/spirit/game/scripts/cards/SWSH8/CinderaceVMAX_45.py +++ b/spirit/game/scripts/cards/SWSH8/CinderaceVMAX_45.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="07ae4cdd-fbf2-5da7-8616-adebdd551406", @@ -24,7 +25,8 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Burned. During your next turn, this Pok\u00e9mon can't attack.", cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 3}, damage=230, - effect=unimplemented, + effect=condition_attack(SpecialConditions.BURNED), + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/CinderaceV_43.py b/spirit/game/scripts/cards/SWSH8/CinderaceV_43.py index c0c9113..c9ce778 100644 --- a/spirit/game/scripts/cards/SWSH8/CinderaceV_43.py +++ b/spirit/game/scripts/cards/SWSH8/CinderaceV_43.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import self_energy_discard_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Discard 2 Fire Energy from this Pok\u00e9mon.", cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=210, - effect=unimplemented, + effect=self_energy_discard_attack(count=2, energy_type=PokemonTypes.FIRE), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/CinderaceV_44.py b/spirit/game/scripts/cards/SWSH8/CinderaceV_44.py index 875d3a5..a5ba35b 100644 --- a/spirit/game/scripts/cards/SWSH8/CinderaceV_44.py +++ b/spirit/game/scripts/cards/SWSH8/CinderaceV_44.py @@ -1,4 +1,4 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -28,7 +28,7 @@ card = PokemonCardDef( game_text="During your next turn, this Pok\u00e9mon can't attack.", cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 3}, damage=210, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Clawitzer_75.py b/spirit/game/scripts/cards/SWSH8/Clawitzer_75.py index ee70aba..312a252 100644 --- a/spirit/game/scripts/cards/SWSH8/Clawitzer_75.py +++ b/spirit/game/scripts/cards/SWSH8/Clawitzer_75.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import snipe_attack card = PokemonCardDef( guid="240bb828-f7ea-5f8f-9d9b-5886d4b26c25", @@ -23,7 +24,7 @@ card = PokemonCardDef( title="Snipe Shot", game_text="This attack does 50 damage to 1 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.WATER: 1}, - effect=unimplemented, + effect=snipe_attack(50), ), Attack( title="Crabhammer", diff --git a/spirit/game/scripts/cards/SWSH8/Claydol_145.py b/spirit/game/scripts/cards/SWSH8/Claydol_145.py index ae45568..79d3804 100644 --- a/spirit/game/scripts/cards/SWSH8/Claydol_145.py +++ b/spirit/game/scripts/cards/SWSH8/Claydol_145.py @@ -1,5 +1,29 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import opponent_switches +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities + + +async def rapid_spin(ctx): + """Switch self with a Benched Pokemon; if you do, the opponent switches too.""" + await ctx.deal_damage() + bench = ctx.my_bench() + if not bench: + return + target = await ctx.choose_pokemon(bench, "Choose your new Active Pokémon") + await ctx.switch_active(ctx.player_id, target or bench[0]) + await ctx.flush_choreography() + await opponent_switches(ctx) + + +async def ancient_imprint(ctx): + """Put damage counters on the opponent's Active until its remaining HP is 60.""" + defender = ctx.defender + if defender is None: + return + hp = defender.get_attribute(AttrID.HP, 0) + if hp > 60: + await ctx.deal_damage(hp - 60, target=defender, as_counters=True) + card = PokemonCardDef( guid="a055f44f-d87f-5959-8a3c-a6e5281a1677", @@ -21,16 +45,16 @@ card = PokemonCardDef( abilities=[ Attack( title="Rapid Spin", - game_text="Switch this Pok\u00e9mon with 1 of your Benched Pok\u00e9mon. If you do, your opponent switches their Active Pok\u00e9mon with 1 of their Benched Pok\u00e9mon.", + game_text="Switch this Pokémon with 1 of your Benched Pokémon. If you do, your opponent switches their Active Pokémon with 1 of their Benched Pokémon.", cost={PokemonTypes.COLORLESS: 2}, damage=30, - effect=unimplemented, + effect=rapid_spin, ), Attack( title="Ancient Imprint", - game_text="Put damage counters on your opponent's Active Pok\u00e9mon until its remaining HP is 60.", + game_text="Put damage counters on your opponent's Active Pokémon until its remaining HP is 60.", cost={PokemonTypes.FIGHTING: 2}, - effect=unimplemented, + effect=ancient_imprint, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH8/Cloyster_51.py b/spirit/game/scripts/cards/SWSH8/Cloyster_51.py index 47049c1..03835aa 100644 --- a/spirit/game/scripts/cards/SWSH8/Cloyster_51.py +++ b/spirit/game/scripts/cards/SWSH8/Cloyster_51.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import takes_less_passive +from spirit.game.card_effects.attacks_common import snipe_attack card = PokemonCardDef( guid="d66c275c-0f2f-5dde-a821-78b4c810e54e", @@ -22,14 +24,14 @@ card = PokemonCardDef( Ability( title="Shell Armor", game_text="This Pok\u00e9mon takes 30 less damage from attacks (after applying Weakness and Resistance).", - effect=unimplemented, + passive=takes_less_passive(30), ), Attack( title="Aqua Split", game_text="This attack also does 30 damage to 2 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 1}, damage=60, - effect=unimplemented, + effect=snipe_attack(30, pool="bench", count=2, also_base=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Cook_228.py b/spirit/game/scripts/cards/SWSH8/Cook_228.py index 5eb1a2a..5b0c452 100644 --- a/spirit/game/scripts/cards/SWSH8/Cook_228.py +++ b/spirit/game/scripts/cards/SWSH8/Cook_228.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.support_common import heal_item card = SupporterCardDef( guid="89e8f9b7-0e7d-5063-8e1c-29472836845a", @@ -11,5 +12,5 @@ card = SupporterCardDef( collector_number=228, set_code="SWSH8", rarity=Rarities.Uncommon, - effect=unimplemented + effect=heal_item(70, scope="active"), ) diff --git a/spirit/game/scripts/cards/SWSH8/Copperajah_192.py b/spirit/game/scripts/cards/SWSH8/Copperajah_192.py index 338d476..8829c86 100644 --- a/spirit/game/scripts/cards/SWSH8/Copperajah_192.py +++ b/spirit/game/scripts/cards/SWSH8/Copperajah_192.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack card = PokemonCardDef( guid="cf4be9ee-5662-59e2-ac80-b03e2b9dd4ee", @@ -30,7 +31,7 @@ card = PokemonCardDef( game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 3}, damage=160, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Corviknight_190.py b/spirit/game/scripts/cards/SWSH8/Corviknight_190.py index 6fc0940..c56f52e 100644 --- a/spirit/game/scripts/cards/SWSH8/Corviknight_190.py +++ b/spirit/game/scripts/cards/SWSH8/Corviknight_190.py @@ -1,5 +1,25 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import protect_next_turn + + +async def power_cyclone(ctx): + await ctx.deal_damage() + bench = ctx.my_bench() + energies = ctx.attached_energies(ctx.attacker) + if not bench or not energies: + return + picks = await ctx.choose_cards( + energies, 1, prompt="Choose an Energy to move to 1 of your Benched Pokémon" + ) + if not picks: + return + target = await ctx.choose_pokemon( + bench, "Choose a Benched Pokémon to move the Energy to" + ) + if target is not None: + await ctx.move_energy(picks[0], target) + card = PokemonCardDef( guid="1ed74e86-4962-548b-8910-d8fb0d187d5f", @@ -25,14 +45,14 @@ card = PokemonCardDef( game_text="During your opponent's next turn, this Pok\u00e9mon takes 30 less damage from attacks (after applying Weakness and Resistance).", cost={PokemonTypes.METAL: 1}, damage=50, - effect=unimplemented, + effect=protect_next_turn(reduce=30), ), Attack( title="Power Cyclone", game_text="Move an Energy from this Pok\u00e9mon to 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 2}, damage=160, - effect=unimplemented, + effect=power_cyclone, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Corvisquire_220.py b/spirit/game/scripts/cards/SWSH8/Corvisquire_220.py index 4c1cdde..6646a54 100644 --- a/spirit/game/scripts/cards/SWSH8/Corvisquire_220.py +++ b/spirit/game/scripts/cards/SWSH8/Corvisquire_220.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import flip_damage +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -26,7 +27,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=30, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=3, per_heads=30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/CrabominableV_248.py b/spirit/game/scripts/cards/SWSH8/CrabominableV_248.py index 001cf8b..be69b0c 100644 --- a/spirit/game/scripts/cards/SWSH8/CrabominableV_248.py +++ b/spirit/game/scripts/cards/SWSH8/CrabominableV_248.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, damage_counters_on, mill_attack card = PokemonCardDef( guid="670fe908-b30f-5809-b4e7-473a9a7964a8", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Trigger Avalanche", game_text="Discard the top 2 cards of your opponent's deck.", cost={PokemonTypes.WATER: 1}, - effect=unimplemented, + effect=mill_attack(2), ), Attack( title="Destroyer Punch", @@ -30,7 +31,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, damage=90, damage_operator="+", - effect=unimplemented, + effect=damage_per(damage_counters_on("defender"), 60, base=90), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/CrabominableV_76.py b/spirit/game/scripts/cards/SWSH8/CrabominableV_76.py index a8fa14e..2318793 100644 --- a/spirit/game/scripts/cards/SWSH8/CrabominableV_76.py +++ b/spirit/game/scripts/cards/SWSH8/CrabominableV_76.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, damage_counters_on, mill_attack card = PokemonCardDef( guid="ca1238a6-159b-5f03-a953-35e85cbb263e", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Trigger Avalanche", game_text="Discard the top 2 cards of your opponent's deck.", cost={PokemonTypes.WATER: 1}, - effect=unimplemented, + effect=mill_attack(2), ), Attack( title="Destroyer Punch", @@ -30,7 +31,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, damage=90, damage_operator="+", - effect=unimplemented, + effect=damage_per(damage_counters_on("defender"), 60, base=90), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/CrossSwitcher_230.py b/spirit/game/scripts/cards/SWSH8/CrossSwitcher_230.py index c30e161..f1a011c 100644 --- a/spirit/game/scripts/cards/SWSH8/CrossSwitcher_230.py +++ b/spirit/game/scripts/cards/SWSH8/CrossSwitcher_230.py @@ -1,5 +1,48 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef, def_for from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import opponent_has_bench + + +def _named(card, name): + d = def_for(card.archetype_id) + return bool(d) and d.display_name == name + + +def _second_copy(ctx): + return next((c for c in ctx.hand() + if c.entity_id != ctx.source.entity_id + and _named(c, "Cross Switcher")), None) + + +def _playable(board, player_id, card): + """Needs a 2nd Cross Switcher in hand and an opposing Benched Pokemon.""" + hand = board.find_player_area(player_id, "hand") + has_pair = any(c.entity_id != card.entity_id and _named(c, "Cross Switcher") + for c in (hand.children if hand else [])) + return has_pair and opponent_has_bench(board, player_id) + + +async def cross_switcher(ctx): + """Play 2 at once: discard the 2nd copy, gust an opposing Benched Pokemon + into the Active Spot; if you do, switch your own Active with a Benched one.""" + pair = _second_copy(ctx) + if pair is None: + return + await ctx.discard_cards([pair]) + target = await ctx.choose_pokemon( + ctx.opponent_bench(), "Choose the opponent's new Active Pokémon" + ) + if target is None or not await ctx.switch_active(ctx.opponent_id, target): + return + my_bench = ctx.my_bench() + if not my_bench: + return + # Flush the gust so both clients see it land before the own-side pick. + await ctx.flush_choreography() + mine = await ctx.choose_pokemon(my_bench, "Choose your new Active Pokémon") + if mine is not None: + await ctx.switch_active(ctx.player_id, mine) + card = ItemCardDef( guid="4643a72f-8166-5787-a6f5-2605f06339ad", @@ -11,5 +54,6 @@ card = ItemCardDef( collector_number=230, set_code="SWSH8", rarity=Rarities.Uncommon, - effect=unimplemented + condition=_playable, + effect=cross_switcher, ) diff --git a/spirit/game/scripts/cards/SWSH8/Crossceiver_231.py b/spirit/game/scripts/cards/SWSH8/Crossceiver_231.py index 4293f8f..639e8d3 100644 --- a/spirit/game/scripts/cards/SWSH8/Crossceiver_231.py +++ b/spirit/game/scripts/cards/SWSH8/Crossceiver_231.py @@ -1,5 +1,44 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef, def_for from spirit.game.attributes import Rarities +from spirit.game.session.effects import is_pokemon_card, is_supporter_card + + +def _named(card, name): + d = def_for(card.archetype_id) + return bool(d) and d.display_name == name + + +def _playable(board, player_id, card): + """Needs a 2nd Crossceiver in hand and a Pokemon/Supporter in the discard.""" + hand = board.find_player_area(player_id, "hand") + has_pair = any(c.entity_id != card.entity_id and _named(c, "Crossceiver") + for c in (hand.children if hand else [])) + discard = board.find_player_area(player_id, "discard") + has_target = any(is_pokemon_card(c) or is_supporter_card(c) + for c in (discard.children if discard else [])) + return has_pair and has_target + + +async def crossceiver(ctx): + """Play 2 at once: discard the 2nd copy, then put a Pokemon or a Supporter + from your discard pile into your hand.""" + pair = next((c for c in ctx.hand() + if c.entity_id != ctx.source.entity_id + and _named(c, "Crossceiver")), None) + if pair is None: + return + await ctx.discard_cards([pair]) + candidates = [c for c in ctx.discard_pile() + if is_pokemon_card(c) or is_supporter_card(c)] + if not candidates: + return + picks = await ctx.choose_cards( + candidates, 1, minimum=1, + prompt="Choose a Pokémon or a Supporter card to put into your hand.", + ) + if picks: + await ctx.put_in_hand(picks, reveal=False) + card = ItemCardDef( guid="e82e24fb-7322-5f44-8cb1-fe5e9b9cd43a", @@ -11,5 +50,6 @@ card = ItemCardDef( collector_number=231, set_code="SWSH8", rarity=Rarities.Uncommon, - effect=unimplemented + condition=_playable, + effect=crossceiver, ) diff --git a/spirit/game/scripts/cards/SWSH8/Cufant_191.py b/spirit/game/scripts/cards/SWSH8/Cufant_191.py index 51efb08..4002262 100644 --- a/spirit/game/scripts/cards/SWSH8/Cufant_191.py +++ b/spirit/game/scripts/cards/SWSH8/Cufant_191.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack card = PokemonCardDef( guid="e8419f80-fb86-5e39-89ab-f191b6fa196f", @@ -29,7 +30,7 @@ card = PokemonCardDef( game_text="This Pok\u00e9mon also does 20 damage to itself.", cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 2}, damage=80, - effect=unimplemented, + effect=recoil_attack(20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Dancer_232.py b/spirit/game/scripts/cards/SWSH8/Dancer_232.py index 49883c7..6898c7c 100644 --- a/spirit/game/scripts/cards/SWSH8/Dancer_232.py +++ b/spirit/game/scripts/cards/SWSH8/Dancer_232.py @@ -1,6 +1,14 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities + +async def dancer(ctx): + """Draw 2; if you go second and it's your first turn, draw 3 more.""" + await ctx.draw_cards(2) + if ctx.session.turn_state.turn_number == 2: + await ctx.draw_cards(3) + + card = SupporterCardDef( guid="21e0a783-dd5f-5a87-97df-d9144b2ec548", key="SWSH8", @@ -11,5 +19,5 @@ card = SupporterCardDef( collector_number=232, set_code="SWSH8", rarity=Rarities.Uncommon, - effect=unimplemented + effect=dancer ) diff --git a/spirit/game/scripts/cards/SWSH8/Dancer_259.py b/spirit/game/scripts/cards/SWSH8/Dancer_259.py index 6f15208..26d6153 100644 --- a/spirit/game/scripts/cards/SWSH8/Dancer_259.py +++ b/spirit/game/scripts/cards/SWSH8/Dancer_259.py @@ -1,6 +1,14 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities + +async def dancer(ctx): + """Draw 2; if you go second and it's your first turn, draw 3 more.""" + await ctx.draw_cards(2) + if ctx.session.turn_state.turn_number == 2: + await ctx.draw_cards(3) + + card = SupporterCardDef( guid="d233b9b5-d616-55e0-806e-e3a1f4d4f9d0", key="SWSH8", @@ -11,5 +19,5 @@ card = SupporterCardDef( collector_number=259, set_code="SWSH8", rarity=Rarities.RareUltra, - effect=unimplemented + effect=dancer ) diff --git a/spirit/game/scripts/cards/SWSH8/Dancer_274.py b/spirit/game/scripts/cards/SWSH8/Dancer_274.py index bde1024..065d0cc 100644 --- a/spirit/game/scripts/cards/SWSH8/Dancer_274.py +++ b/spirit/game/scripts/cards/SWSH8/Dancer_274.py @@ -1,6 +1,14 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities + +async def dancer(ctx): + """Draw 2; if you go second and it's your first turn, draw 3 more.""" + await ctx.draw_cards(2) + if ctx.session.turn_state.turn_number == 2: + await ctx.draw_cards(3) + + card = SupporterCardDef( guid="b2b95674-22c8-51c7-9419-c9cfc3cde7bc", key="SWSH8", @@ -11,5 +19,5 @@ card = SupporterCardDef( collector_number=274, set_code="SWSH8", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=dancer ) diff --git a/spirit/game/scripts/cards/SWSH8/Delcatty_211.py b/spirit/game/scripts/cards/SWSH8/Delcatty_211.py index 08aeb59..7fa5491 100644 --- a/spirit/game/scripts/cards/SWSH8/Delcatty_211.py +++ b/spirit/game/scripts/cards/SWSH8/Delcatty_211.py @@ -1,5 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage + + +async def willful_busybody(ctx): + """Opponent reveals hand; choose a card there and put it on the bottom of their deck.""" + hand = ctx.hand(ctx.opponent_id) + if not hand: + return + picks = await ctx.choose_cards( + hand, 1, minimum=1, + prompt="Choose a card from your opponent's hand to put on the bottom of their deck.", + ) + for card in picks: + await ctx.put_on_bottom_of_deck(card) + card = PokemonCardDef( guid="ac81abc3-13e2-566f-8225-e4072e909b3c", @@ -23,7 +38,7 @@ card = PokemonCardDef( title="Willful Busybody", game_text="Your opponent reveals their hand. Choose a card you find there and put it on the bottom of their deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=willful_busybody, ), Attack( title="Double Slap", @@ -31,7 +46,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=50, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=2, per_heads=50), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Deoxys_120.py b/spirit/game/scripts/cards/SWSH8/Deoxys_120.py index dd4207d..dbe8555 100644 --- a/spirit/game/scripts/cards/SWSH8/Deoxys_120.py +++ b/spirit/game/scripts/cards/SWSH8/Deoxys_120.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, subtypes_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if + + +def _has_fusion_strike_energy(ctx): + return any( + "Fusion Strike" in subtypes_for(c.archetype_id) + for c in ctx.attached_energies(ctx.attacker) + ) + card = PokemonCardDef( guid="af08582d-bde3-50d1-b090-a9016a286e8a", @@ -25,7 +34,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=80, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_has_fusion_strike_energy, 80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Diggersby_215.py b/spirit/game/scripts/cards/SWSH8/Diggersby_215.py index 7086219..d51eb6b 100644 --- a/spirit/game/scripts/cards/SWSH8/Diggersby_215.py +++ b/spirit/game/scripts/cards/SWSH8/Diggersby_215.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack card = PokemonCardDef( guid="4b649b5e-c715-5d9a-9afa-56b50ae50a30", @@ -29,7 +30,7 @@ card = PokemonCardDef( game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.COLORLESS: 4}, damage=150, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/DodrioV_201.py b/spirit/game/scripts/cards/SWSH8/DodrioV_201.py index e1c2fb5..a6668a5 100644 --- a/spirit/game/scripts/cards/SWSH8/DodrioV_201.py +++ b/spirit/game/scripts/cards/SWSH8/DodrioV_201.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import boost_own_next_turn +from spirit.game.card_effects.attacks_common import recoil_attack card = PokemonCardDef( guid="779dd8e1-7210-502d-b99e-71cae4a22a57", @@ -24,14 +26,14 @@ card = PokemonCardDef( game_text="During your next turn, this Pok\u00e9mon's attacks do 80 more damage to your opponent's Active Pok\u00e9mon (before applying Weakness and Resistance).", cost={PokemonTypes.COLORLESS: 1}, damage=20, - effect=unimplemented, + effect=boost_own_next_turn(80), ), Attack( title="Rampage Drill", game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.COLORLESS: 3}, damage=160, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Dragapult_130.py b/spirit/game/scripts/cards/SWSH8/Dragapult_130.py index 83a9e45..4ebfde9 100644 --- a/spirit/game/scripts/cards/SWSH8/Dragapult_130.py +++ b/spirit/game/scripts/cards/SWSH8/Dragapult_130.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, subtypes_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_in_play + + +def _is_fusion_strike(card): + return "Fusion Strike" in subtypes_for(card.archetype_id) + card = PokemonCardDef( guid="e4088b77-781f-50f0-93e7-4c8db08f5c7d", @@ -26,7 +32,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 1}, damage=30, damage_operator="x", - effect=unimplemented, + effect=damage_per(count_in_play("mine", pred=_is_fusion_strike), 30), ), Attack( title="Speed Attack", diff --git a/spirit/game/scripts/cards/SWSH8/Drakloak_129.py b/spirit/game/scripts/cards/SWSH8/Drakloak_129.py index 4f2707d..8c07ad4 100644 --- a/spirit/game/scripts/cards/SWSH8/Drakloak_129.py +++ b/spirit/game/scripts/cards/SWSH8/Drakloak_129.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.support_common import switch_self_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -30,7 +31,7 @@ card = PokemonCardDef( game_text="Switch this Pok\u00e9mon with 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=switch_self_attack(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Drednaw_81.py b/spirit/game/scripts/cards/SWSH8/Drednaw_81.py index f394b3e..b4f2ae5 100644 --- a/spirit/game/scripts/cards/SWSH8/Drednaw_81.py +++ b/spirit/game/scripts/cards/SWSH8/Drednaw_81.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus card = PokemonCardDef( guid="f74d18ea-1e17-55a4-887b-f6cfd9c72284", @@ -25,7 +26,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=80, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(50), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Dreepy_128.py b/spirit/game/scripts/cards/SWSH8/Dreepy_128.py index aa87ad4..5471646 100644 --- a/spirit/game/scripts/cards/SWSH8/Dreepy_128.py +++ b/spirit/game/scripts/cards/SWSH8/Dreepy_128.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="f2804406-a2d4-5a74-bc02-78ad24e9f920", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, the Defending Pok\u00e9mon can't retreat.", cost={PokemonTypes.PSYCHIC: 1}, damage=10, - effect=unimplemented, + effect=condition_attack(no_retreat=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Dubwool_223.py b/spirit/game/scripts/cards/SWSH8/Dubwool_223.py index 0606071..6f40f3e 100644 --- a/spirit/game/scripts/cards/SWSH8/Dubwool_223.py +++ b/spirit/game/scripts/cards/SWSH8/Dubwool_223.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.support_common import switch_self_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="You may switch this Pok\u00e9mon with 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=switch_self_attack(optional=True), ), Attack( title="Rolling Tackle", diff --git a/spirit/game/scripts/cards/SWSH8/Durant_184.py b/spirit/game/scripts/cards/SWSH8/Durant_184.py index 630c622..71b48c4 100644 --- a/spirit/game/scripts/cards/SWSH8/Durant_184.py +++ b/spirit/game/scripts/cards/SWSH8/Durant_184.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities, SpecialConditions + + +async def adversity_jaws(ctx): + await ctx.deal_damage() + defender = ctx.defender + if defender is not None: + types = defender.get_attribute(AttrID.POKEMON_TYPES) or [] + if PokemonTypes.FIRE.value in types: + await ctx.apply_special_condition(defender, SpecialConditions.PARALYZED) + card = PokemonCardDef( guid="a177fbf6-57a4-5a7b-b119-2ba16d2db4b1", @@ -24,7 +34,7 @@ card = PokemonCardDef( game_text="If your opponent's Active Pok\u00e9mon is a Fire Pok\u00e9mon, it is now Paralyzed.", cost={PokemonTypes.COLORLESS: 3}, damage=70, - effect=unimplemented, + effect=adversity_jaws, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Eelektrik_96.py b/spirit/game/scripts/cards/SWSH8/Eelektrik_96.py index b807e27..386e535 100644 --- a/spirit/game/scripts/cards/SWSH8/Eelektrik_96.py +++ b/spirit/game/scripts/cards/SWSH8/Eelektrik_96.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack card = PokemonCardDef( guid="eae513e5-9282-5737-8cd4-134ffeeebfaa", @@ -29,7 +30,7 @@ card = PokemonCardDef( game_text="This Pok\u00e9mon also does 20 damage to itself.", cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 2}, damage=80, - effect=unimplemented, + effect=recoil_attack(20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Eelektross_97.py b/spirit/game/scripts/cards/SWSH8/Eelektross_97.py index 2363159..728a99a 100644 --- a/spirit/game/scripts/cards/SWSH8/Eelektross_97.py +++ b/spirit/game/scripts/cards/SWSH8/Eelektross_97.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import recoil_attack + + +def _evolved_this_turn(ctx) -> bool: + state = ctx.session.turn_state + return state.entered_play_turn.get(ctx.attacker.entity_id) == state.turn_number + + +async def upper_shock(ctx): + await ctx.deal_damage() + if _evolved_this_turn(ctx): + await ctx.apply_special_condition(ctx.defender, SpecialConditions.PARALYZED) + card = PokemonCardDef( guid="96f5e8a7-91d3-5068-a633-3b6ec915914f", @@ -24,14 +37,14 @@ card = PokemonCardDef( game_text="If this Pok\u00e9mon evolved from Eelektrik during this turn, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.LIGHTNING: 1}, damage=40, - effect=unimplemented, + effect=upper_shock, ), Attack( title="Wild Charge", game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 2}, damage=160, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Eevee_205.py b/spirit/game/scripts/cards/SWSH8/Eevee_205.py index 3c18448..a50b06c 100644 --- a/spirit/game/scripts/cards/SWSH8/Eevee_205.py +++ b/spirit/game/scripts/cards/SWSH8/Eevee_205.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="0a2e0be3-299c-54ac-908e-e5508808dd06", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 1}, damage=30, damage_operator="x", - effect=unimplemented, + effect=flip_damage(until_tails=True, per_heads=30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Eldegoss_25.py b/spirit/game/scripts/cards/SWSH8/Eldegoss_25.py index 6d80576..c5b7bc0 100644 --- a/spirit/game/scripts/cards/SWSH8/Eldegoss_25.py +++ b/spirit/game/scripts/cards/SWSH8/Eldegoss_25.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import heal_attack card = PokemonCardDef( guid="a27fb782-d916-5c91-83a3-08fe541d979e", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Heal 20 damage from this Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, damage=50, - effect=unimplemented, + effect=heal_attack(20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Electrode_88.py b/spirit/game/scripts/cards/SWSH8/Electrode_88.py index 646ef52..de51ec4 100644 --- a/spirit/game/scripts/cards/SWSH8/Electrode_88.py +++ b/spirit/game/scripts/cards/SWSH8/Electrode_88.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack + + +async def sonic_boom(ctx): + """40. This attack's damage isn't affected by Weakness or Resistance.""" + await ctx.deal_damage(apply_modifiers=False) + card = PokemonCardDef( guid="ce991882-03ac-55cd-a73f-10955608854d", @@ -24,14 +31,14 @@ card = PokemonCardDef( game_text="This attack's damage isn't affected by Weakness or Resistance.", cost={PokemonTypes.COLORLESS: 1}, damage=40, - effect=unimplemented, + effect=sonic_boom, ), Attack( title="Explosion", game_text="This Pok\u00e9mon also does 90 damage to itself.", cost={PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + effect=recoil_attack(90), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/ElesasSparkle_233.py b/spirit/game/scripts/cards/SWSH8/ElesasSparkle_233.py index 19a1dfb..618371d 100644 --- a/spirit/game/scripts/cards/SWSH8/ElesasSparkle_233.py +++ b/spirit/game/scripts/cards/SWSH8/ElesasSparkle_233.py @@ -1,5 +1,34 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef, subtypes_for from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import is_energy_card + + +def _is_fusion_strike_energy(card): + return is_energy_card(card) and "Fusion Strike" in subtypes_for(card.archetype_id) + + +async def elesas_sparkle(ctx): + """Choose up to 2 of your Fusion Strike Pokemon. For each of those + Pokemon, search your deck for a Fusion Strike Energy card and attach it + to that Pokemon. Then, shuffle your deck.""" + candidates = [ + p for p in ctx.my_pokemon_in_play() + if "Fusion Strike" in subtypes_for(p.archetype_id) + ] + if candidates: + targets = await ctx.choose_cards( + candidates, 2, minimum=0, + prompt="Choose up to 2 of your Fusion Strike Pokémon.", + ) + for pokemon in targets: + picks = await ctx.search_deck( + _is_fusion_strike_energy, count=1, minimum=0, + prompt="Choose a Fusion Strike Energy card to attach.", + ) + for card in picks: + await ctx.attach_energy(card, pokemon) + await ctx.shuffle_deck() + card = SupporterCardDef( guid="59444299-ea92-54cc-8b04-3d36161e726d", @@ -11,5 +40,5 @@ card = SupporterCardDef( collector_number=233, set_code="SWSH8", rarity=Rarities.Uncommon, - effect=unimplemented + effect=elesas_sparkle, ) diff --git a/spirit/game/scripts/cards/SWSH8/ElesasSparkle_260.py b/spirit/game/scripts/cards/SWSH8/ElesasSparkle_260.py index 8e5dac6..d103da6 100644 --- a/spirit/game/scripts/cards/SWSH8/ElesasSparkle_260.py +++ b/spirit/game/scripts/cards/SWSH8/ElesasSparkle_260.py @@ -1,5 +1,34 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef, subtypes_for from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import is_energy_card + + +def _is_fusion_strike_energy(card): + return is_energy_card(card) and "Fusion Strike" in subtypes_for(card.archetype_id) + + +async def elesas_sparkle(ctx): + """Choose up to 2 of your Fusion Strike Pokemon. For each of those + Pokemon, search your deck for a Fusion Strike Energy card and attach it + to that Pokemon. Then, shuffle your deck.""" + candidates = [ + p for p in ctx.my_pokemon_in_play() + if "Fusion Strike" in subtypes_for(p.archetype_id) + ] + if candidates: + targets = await ctx.choose_cards( + candidates, 2, minimum=0, + prompt="Choose up to 2 of your Fusion Strike Pokémon.", + ) + for pokemon in targets: + picks = await ctx.search_deck( + _is_fusion_strike_energy, count=1, minimum=0, + prompt="Choose a Fusion Strike Energy card to attach.", + ) + for card in picks: + await ctx.attach_energy(card, pokemon) + await ctx.shuffle_deck() + card = SupporterCardDef( guid="bbd48726-2de5-5f97-a843-408ee1fa0b17", @@ -11,5 +40,5 @@ card = SupporterCardDef( collector_number=260, set_code="SWSH8", rarity=Rarities.RareUltra, - effect=unimplemented + effect=elesas_sparkle, ) diff --git a/spirit/game/scripts/cards/SWSH8/ElesasSparkle_275.py b/spirit/game/scripts/cards/SWSH8/ElesasSparkle_275.py index fbc3ca4..09860ce 100644 --- a/spirit/game/scripts/cards/SWSH8/ElesasSparkle_275.py +++ b/spirit/game/scripts/cards/SWSH8/ElesasSparkle_275.py @@ -1,5 +1,34 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef, subtypes_for from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import is_energy_card + + +def _is_fusion_strike_energy(card): + return is_energy_card(card) and "Fusion Strike" in subtypes_for(card.archetype_id) + + +async def elesas_sparkle(ctx): + """Choose up to 2 of your Fusion Strike Pokemon. For each of those + Pokemon, search your deck for a Fusion Strike Energy card and attach it + to that Pokemon. Then, shuffle your deck.""" + candidates = [ + p for p in ctx.my_pokemon_in_play() + if "Fusion Strike" in subtypes_for(p.archetype_id) + ] + if candidates: + targets = await ctx.choose_cards( + candidates, 2, minimum=0, + prompt="Choose up to 2 of your Fusion Strike Pokémon.", + ) + for pokemon in targets: + picks = await ctx.search_deck( + _is_fusion_strike_energy, count=1, minimum=0, + prompt="Choose a Fusion Strike Energy card to attach.", + ) + for card in picks: + await ctx.attach_energy(card, pokemon) + await ctx.shuffle_deck() + card = SupporterCardDef( guid="e7949fb6-1076-5282-a845-73c624df3ec5", @@ -11,5 +40,5 @@ card = SupporterCardDef( collector_number=275, set_code="SWSH8", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=elesas_sparkle, ) diff --git a/spirit/game/scripts/cards/SWSH8/EspeonVMAX_270.py b/spirit/game/scripts/cards/SWSH8/EspeonVMAX_270.py index 08be0dc..bf87621 100644 --- a/spirit/game/scripts/cards/SWSH8/EspeonVMAX_270.py +++ b/spirit/game/scripts/cards/SWSH8/EspeonVMAX_270.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import count_energy, damage_per +from spirit.game.card_effects.passives_common import attack_effect_shield_passive +from spirit.game.models.board import BoardState + + +def _solar_revelation_protects(target, carrier): + return ( + target.owning_player_id == carrier.owning_player_id + and bool(BoardState.attached_energies(target)) + ) + card = PokemonCardDef( guid="03d00288-c3f3-5434-84cb-c355f569a3c3", @@ -23,7 +34,7 @@ card = PokemonCardDef( Ability( title="Solar Revelation", game_text="Prevent all effects of attacks from your opponent's Pok\u00e9mon done to all of your Pok\u00e9mon that have Energy attached.(Existing effects are not removed. Damage is not an effect.)", - effect=unimplemented, + passive=attack_effect_shield_passive(_solar_revelation_protects), ), Attack( title="Max Mindstorm", @@ -31,7 +42,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 2}, damage=60, damage_operator="x", - effect=unimplemented, + effect=damage_per(count_energy("opponent"), 60), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Excadrill_183.py b/spirit/game/scripts/cards/SWSH8/Excadrill_183.py index 4e94a0f..118139e 100644 --- a/spirit/game/scripts/cards/SWSH8/Excadrill_183.py +++ b/spirit/game/scripts/cards/SWSH8/Excadrill_183.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -30,7 +31,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, the Defending Pok\u00e9mon can't retreat.", cost={PokemonTypes.METAL: 2, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + effect=condition_attack(no_retreat=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Falinks_154.py b/spirit/game/scripts/cards/SWSH8/Falinks_154.py index c30bc82..bc70c1e 100644 --- a/spirit/game/scripts/cards/SWSH8/Falinks_154.py +++ b/spirit/game/scripts/cards/SWSH8/Falinks_154.py @@ -1,5 +1,10 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, count_prizes_remaining + + +def _opponent_one_prize_left(ctx) -> bool: + return count_prizes_remaining("opponent")(ctx) == 1 card = PokemonCardDef( guid="cc667b80-cf84-5438-9cae-507e45d5e85d", @@ -29,7 +34,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 2}, damage=60, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_opponent_one_prize_left, 160), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/FarewellBell_234.py b/spirit/game/scripts/cards/SWSH8/FarewellBell_234.py index 65a9287..03d717c 100644 --- a/spirit/game/scripts/cards/SWSH8/FarewellBell_234.py +++ b/spirit/game/scripts/cards/SWSH8/FarewellBell_234.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented +from spirit.game.data_utils import PokemonToolCardDef, Ability, Triggers from spirit.game.attributes import Rarities +from spirit.game.card_effects.pokemon import is_pokemon_vmax + + +async def _farewell_bell(ctx): + if not ctx.ko_from_attack or not is_pokemon_vmax(ctx.source.archetype_id): + return + picks = await ctx.search_deck( + count=1, minimum=0, prompt="Choose a card to put into your hand." + ) + await ctx.put_in_hand(picks, reveal=False) + await ctx.shuffle_deck() + card = PokemonToolCardDef( guid="d6236064-cf7a-506a-95b6-b7db157d0dea", @@ -11,5 +23,12 @@ card = PokemonToolCardDef( collector_number=234, set_code="SWSH8", rarity=Rarities.Uncommon, - effect=unimplemented + granted_abilities=[ + Ability( + title="Farewell Bell", + game_text="If the Pok\u00e9mon VMAX this card is attached to is Knocked Out by damage from an attack from your opponent's Pok\u00e9mon, search your deck for a card and put it into your hand. Then, shuffle your deck.", + trigger=Triggers.ON_KNOCKED_OUT, + effect=_farewell_bell, + ), + ], ) diff --git a/spirit/game/scripts/cards/SWSH8/Feraligatr_57.py b/spirit/game/scripts/cards/SWSH8/Feraligatr_57.py index 4cd7169..c7c2614 100644 --- a/spirit/game/scripts/cards/SWSH8/Feraligatr_57.py +++ b/spirit/game/scripts/cards/SWSH8/Feraligatr_57.py @@ -1,6 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import discard_opponent_energy_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def rowdy(ctx): + heads = (await ctx.flip_coins(1, "Rowdy"))[0] + pid = ctx.opponent_id if heads else ctx.player_id + await ctx.discard_cards(ctx.deck_top(5, player_id=pid)) + card = PokemonCardDef( guid="8086bf87-3cf4-5202-87fe-46958d1663b6", key="SWSH8", @@ -22,14 +29,15 @@ card = PokemonCardDef( Ability( title="Rowdy", game_text="When you play this Pok\u00e9mon from your hand to evolve 1 of your Pok\u00e9mon during your turn, you must flip a coin. If heads, discard the top 5 cards of your opponent's deck. If tails, discard the top 5 cards of your deck.", - effect=unimplemented, + trigger=Triggers.ON_EVOLVE, + effect=rowdy, ), Attack( title="Crunch", game_text="Discard an Energy from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, damage=140, - effect=unimplemented, + effect=discard_opponent_energy_attack(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Flaaffy_280.py b/spirit/game/scripts/cards/SWSH8/Flaaffy_280.py index f5478c7..6042a14 100644 --- a/spirit/game/scripts/cards/SWSH8/Flaaffy_280.py +++ b/spirit/game/scripts/cards/SWSH8/Flaaffy_280.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import attach_from_discard +from spirit.game.card_effects.pokemon import is_lightning_energy +from spirit.game.card_effects.passives_common import is_in_active_spot + + +def _dynamotor_condition(board, player_id, pokemon): + bench = board.find_player_area(player_id, "bench") + if not bench or not bench.children: + return False + discard = board.find_player_area(player_id, "discard") + cards = discard.children if discard else [] + return any(is_lightning_energy(c) for c in cards) + card = PokemonCardDef( guid="08c40df4-a437-5bf4-95a8-f5b2b5f4719f", @@ -22,7 +35,13 @@ card = PokemonCardDef( Ability( title="Dynamotor", game_text="Once during your turn (before your attack), you may attach a Lightning Energy card from your discard pile to 1 of your Benched Pok\u00e9mon.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=_dynamotor_condition, + effect=attach_from_discard( + predicate=is_lightning_energy, count=1, minimum=0, + target=lambda p: not is_in_active_spot(p), + prompt="Choose a Lightning Energy card to attach to a Benched Pok\u00e9mon", + ), ), Attack( title="Electro Ball", diff --git a/spirit/game/scripts/cards/SWSH8/Frosmoth_85.py b/spirit/game/scripts/cards/SWSH8/Frosmoth_85.py index 7b85cb0..0001718 100644 --- a/spirit/game/scripts/cards/SWSH8/Frosmoth_85.py +++ b/spirit/game/scripts/cards/SWSH8/Frosmoth_85.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack + + +async def blizzard_loop(ctx): + """160 damage, then put all Energy attached to this Pokemon into your + hand.""" + await ctx.deal_damage() + energies = ctx.attached_energies(ctx.attacker) + if energies: + await ctx.put_in_hand(energies, reveal=False) + card = PokemonCardDef( guid="359c4f40-0f78-5596-8d7d-15eea7b13359", @@ -21,16 +32,16 @@ card = PokemonCardDef( abilities=[ Attack( title="Icy Wind", - game_text="Your opponent's Active Pok\u00e9mon is now Asleep.", + game_text="Your opponent's Active Pokémon is now Asleep.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=condition_attack(SpecialConditions.ASLEEP), ), Attack( title="Blizzard Loop", - game_text="Put all Energy attached to this Pok\u00e9mon into your hand.", + game_text="Put all Energy attached to this Pokémon into your hand.", cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, damage=160, - effect=unimplemented, + effect=blizzard_loop, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH8/GalarianCursola_118.py b/spirit/game/scripts/cards/SWSH8/GalarianCursola_118.py index 5182aa3..19b7e2d 100644 --- a/spirit/game/scripts/cards/SWSH8/GalarianCursola_118.py +++ b/spirit/game/scripts/cards/SWSH8/GalarianCursola_118.py @@ -1,5 +1,23 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, is_pokemon_v from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_basic_pokemon + + +async def force_regeneration(ctx): + candidates = [c for c in ctx.discard_pile(ctx.opponent_id) + if is_basic_pokemon(c) and is_pokemon_v(c.archetype_id)] + if not candidates: + return + picks = await ctx.choose_cards( + candidates, 1, minimum=1, + prompt="Choose a Basic Pokémon V from your opponent's discard pile.") + if not picks: + return + card = picks[0] + if not await ctx.bench_pokemon(card): + return + counters = max(0, (ctx.max_hp(card) - 30) // 10) + await ctx.set_damage_counters(card, counters) card = PokemonCardDef( guid="d1bc64ee-e196-54e6-aa94-fa891e449f7a", @@ -24,7 +42,7 @@ card = PokemonCardDef( title="Force Regeneration", game_text="Put a Basic Pok\u00e9mon V from your opponent's discard pile onto their Bench. If you do, put damage counters on that Pok\u00e9mon until its remaining HP is 30.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=force_regeneration, ), Attack( title="Spooky Shot", diff --git a/spirit/game/scripts/cards/SWSH8/GalarianDarmanitan_72.py b/spirit/game/scripts/cards/SWSH8/GalarianDarmanitan_72.py index 4fb7a65..73495d7 100644 --- a/spirit/game/scripts/cards/SWSH8/GalarianDarmanitan_72.py +++ b/spirit/game/scripts/cards/SWSH8/GalarianDarmanitan_72.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions, AttrID +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.session.passives import Passive, effective_max_hp + +_DARUMA_HEADBUTT_COST = {"Water": 2, "Colorless": 1} + + +class _DarumaHeadbuttAltCostPassive(Passive): + """Daruma Headbutt can be paid with [W] alone once this Pokemon has any + damage counters on it.""" + + def modify_attack_cost(self, cost, pokemon, carrier, board): + if pokemon is not carrier or cost != _DARUMA_HEADBUTT_COST: + return cost + if pokemon.get_attribute(AttrID.HP, 0) >= effective_max_hp(board, pokemon): + return cost + return {"Water": 1} + card = PokemonCardDef( guid="dce827bf-e986-5405-9ac9-749e4fc12b9f", @@ -18,20 +35,20 @@ card = PokemonCardDef( weakness_type=PokemonTypes.METAL, evolves_from="com.direwolfdigital.cake.data.archetypes.pokemon.GalarianDarumaka.Name", family_id=554, + passive=_DarumaHeadbuttAltCostPassive(), abilities=[ Attack( title="Powder Snow", - game_text="Your opponent's Active Pok\u00e9mon is now Asleep.", + game_text="Your opponent's Active Pokémon is now Asleep.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=condition_attack(SpecialConditions.ASLEEP), ), Attack( title="Daruma Headbutt", - game_text="If this Pok\u00e9mon has any damage counters on it, this attack can be used for Water.", + game_text="If this Pokémon has any damage counters on it, this attack can be used for Water.", cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, damage=130, - effect=unimplemented, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH8/GalarianDarumaka_71.py b/spirit/game/scripts/cards/SWSH8/GalarianDarumaka_71.py index 9387bdf..9759afe 100644 --- a/spirit/game/scripts/cards/SWSH8/GalarianDarumaka_71.py +++ b/spirit/game/scripts/cards/SWSH8/GalarianDarumaka_71.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="a1be1168-1d52-5c3e-a56e-87c171a4b36b", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Flip a coin. If tails, this Pok\u00e9mon also does 10 damage to itself.", cost={PokemonTypes.WATER: 1}, damage=20, - effect=unimplemented, + effect=flip_damage(tails_self_damage=10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/GalarianMeowth_180.py b/spirit/game/scripts/cards/SWSH8/GalarianMeowth_180.py index 810adcc..3d5a82f 100644 --- a/spirit/game/scripts/cards/SWSH8/GalarianMeowth_180.py +++ b/spirit/game/scripts/cards/SWSH8/GalarianMeowth_180.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import debuff_defender_attacks card = PokemonCardDef( guid="a961a464-3df7-54e5-9212-ef71f3e06169", @@ -23,7 +24,7 @@ card = PokemonCardDef( title="Growl", game_text="During your opponent's next turn, the Defending Pok\u00e9mon's attacks do 20 less damage (before applying Weakness and Resistance).", cost={PokemonTypes.METAL: 1}, - effect=unimplemented, + effect=debuff_defender_attacks(20), ), Attack( title="Slash", diff --git a/spirit/game/scripts/cards/SWSH8/GalarianObstagoon_161.py b/spirit/game/scripts/cards/SWSH8/GalarianObstagoon_161.py index e13846f..6a530f1 100644 --- a/spirit/game/scripts/cards/SWSH8/GalarianObstagoon_161.py +++ b/spirit/game/scripts/cards/SWSH8/GalarianObstagoon_161.py @@ -1,5 +1,26 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, has_damage + + +async def silence(ctx): + """Choose 1 of the opponent's Active Pokemon's attacks; it can't be used + during their next turn.""" + await ctx.deal_damage() + defender = ctx.defender + if defender is None or ctx.effects_blocked(defender): + return + entries = [e for e in (defender.get_attribute(AttrID.PIE_ABILITIES) or []) + if isinstance(e, dict) and e.get("abilityType") == "Attack" + and e.get("abilityID")] + if not entries: + return + titles = [e.get("title", {}).get("id", "Attack") for e in entries] + idx = await ctx.choose("Choose an attack to Silence", titles) + entry = entries[idx] + ctx.session.turn_state.attack_locks[(defender.entity_id, entry["abilityID"])] = \ + ctx.session.turn_state.turn_number + 1 + card = PokemonCardDef( guid="b651cb52-3dac-5464-9504-ecf2a75e8ebd", @@ -24,7 +45,7 @@ card = PokemonCardDef( game_text="Choose 1 of your opponent's Active Pok\u00e9mon's attacks. During your opponent's next turn, that Pok\u00e9mon can't use that attack.", cost={PokemonTypes.DARKNESS: 1}, damage=30, - effect=unimplemented, + effect=silence, ), Attack( title="Merciless Strike", @@ -32,7 +53,7 @@ card = PokemonCardDef( cost={PokemonTypes.DARKNESS: 1}, damage=60, damage_operator="+", - effect=unimplemented, + effect=bonus_if(has_damage("defender"), 90), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/GalarianPerrserker_181.py b/spirit/game/scripts/cards/SWSH8/GalarianPerrserker_181.py index 7f28f53..b85aa96 100644 --- a/spirit/game/scripts/cards/SWSH8/GalarianPerrserker_181.py +++ b/spirit/game/scripts/cards/SWSH8/GalarianPerrserker_181.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_attach_energy +from spirit.game.card_effects.trainers import is_basic_energy_card card = PokemonCardDef( guid="b39ec35b-8850-503e-9f95-fd2e124e5eb7", @@ -24,7 +26,7 @@ card = PokemonCardDef( title="Call to Muster", game_text="Search your deck for up to 2 basic Energy cards and attach them to your Pok\u00e9mon in any way you like. Then, shuffle your deck.", cost={PokemonTypes.METAL: 1}, - effect=unimplemented, + effect=search_attach_energy(is_basic_energy_card, count=2), ), Attack( title="Headbang", diff --git a/spirit/game/scripts/cards/SWSH8/GalarianZigzagoon_159.py b/spirit/game/scripts/cards/SWSH8/GalarianZigzagoon_159.py index 78a4cef..3cbcb7b 100644 --- a/spirit/game/scripts/cards/SWSH8/GalarianZigzagoon_159.py +++ b/spirit/game/scripts/cards/SWSH8/GalarianZigzagoon_159.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="8d78897f-0e8b-5acf-9f68-1c0754d3f50b", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.DARKNESS: 1}, damage=10, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Garbodor_169.py b/spirit/game/scripts/cards/SWSH8/Garbodor_169.py index 941c595..d82dfff 100644 --- a/spirit/game/scripts/cards/SWSH8/Garbodor_169.py +++ b/spirit/game/scripts/cards/SWSH8/Garbodor_169.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="03620be6-6bcd-5e93-afb5-626bf07dd1b4", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Poisoned.", cost={PokemonTypes.DARKNESS: 1}, damage=30, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED), ), Attack( title="Sludge Whirlpool", diff --git a/spirit/game/scripts/cards/SWSH8/GengarVMAX_157.py b/spirit/game/scripts/cards/SWSH8/GengarVMAX_157.py index a965979..1b76dd7 100644 --- a/spirit/game/scripts/cards/SWSH8/GengarVMAX_157.py +++ b/spirit/game/scripts/cards/SWSH8/GengarVMAX_157.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, is_pokemon_v from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_in_play, lock_all_attacks +from spirit.game.card_effects.pokemon import is_pokemon_gx + + +async def g_max_swallow_up(ctx): + """250. During your next turn, this Pokemon can't attack.""" + await ctx.deal_damage() + lock_all_attacks(ctx, ctx.attacker) + card = PokemonCardDef( guid="a4a73ef9-b533-50f2-a837-be54d2b9f75e", @@ -25,14 +34,20 @@ card = PokemonCardDef( cost={PokemonTypes.DARKNESS: 2}, damage=60, damage_operator="x", - effect=unimplemented, + effect=damage_per( + count_in_play( + "opponent", + pred=lambda c: is_pokemon_v(c.archetype_id) or is_pokemon_gx(c.archetype_id), + ), + 60, + ), ), Attack( title="G-Max Swallow Up", game_text="During your next turn, this Pok\u00e9mon can't attack.", cost={PokemonTypes.DARKNESS: 3}, damage=250, - effect=unimplemented, + effect=g_max_swallow_up, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/GengarVMAX_271.py b/spirit/game/scripts/cards/SWSH8/GengarVMAX_271.py index 5e495cd..f55ddca 100644 --- a/spirit/game/scripts/cards/SWSH8/GengarVMAX_271.py +++ b/spirit/game/scripts/cards/SWSH8/GengarVMAX_271.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, is_pokemon_v from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_in_play, lock_all_attacks +from spirit.game.card_effects.pokemon import is_pokemon_gx + + +async def g_max_swallow_up(ctx): + """250. During your next turn, this Pokemon can't attack.""" + await ctx.deal_damage() + lock_all_attacks(ctx, ctx.attacker) + card = PokemonCardDef( guid="473bf9ce-569f-5b57-9765-f858d26dfeb9", @@ -25,14 +34,20 @@ card = PokemonCardDef( cost={PokemonTypes.DARKNESS: 2}, damage=60, damage_operator="x", - effect=unimplemented, + effect=damage_per( + count_in_play( + "opponent", + pred=lambda c: is_pokemon_v(c.archetype_id) or is_pokemon_gx(c.archetype_id), + ), + 60, + ), ), Attack( title="G-Max Swallow Up", game_text="During your next turn, this Pok\u00e9mon can't attack.", cost={PokemonTypes.DARKNESS: 3}, damage=250, - effect=unimplemented, + effect=g_max_swallow_up, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/GengarV_156.py b/spirit/game/scripts/cards/SWSH8/GengarV_156.py index 5d87c43..193b56c 100644 --- a/spirit/game/scripts/cards/SWSH8/GengarV_156.py +++ b/spirit/game/scripts/cards/SWSH8/GengarV_156.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack + + +async def pain_explosion(ctx): + """190 damage; put 3 damage counters on this Pokemon.""" + await ctx.deal_damage() + await ctx.deal_damage(30, target=ctx.attacker, apply_modifiers=False, as_counters=True) + card = PokemonCardDef( guid="775bf90d-367b-5c15-8120-1fc1b29570ec", @@ -23,14 +31,14 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Asleep.", cost={PokemonTypes.DARKNESS: 2}, damage=40, - effect=unimplemented, + effect=condition_attack(SpecialConditions.ASLEEP), ), Attack( title="Pain Explosion", game_text="Put 3 damage counters on this Pok\u00e9mon.", cost={PokemonTypes.DARKNESS: 3}, damage=190, - effect=unimplemented, + effect=pain_explosion, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Gligar_140.py b/spirit/game/scripts/cards/SWSH8/Gligar_140.py index 7477c97..ea28a38 100644 --- a/spirit/game/scripts/cards/SWSH8/Gligar_140.py +++ b/spirit/game/scripts/cards/SWSH8/Gligar_140.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="1ac16b4c-84c4-5bc5-a772-985cd8032d73", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Poison Sting", game_text="Your opponent's Active Pok\u00e9mon is now Poisoned.", cost={PokemonTypes.FIGHTING: 1}, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED), ), Attack( title="Pierce", diff --git a/spirit/game/scripts/cards/SWSH8/Gliscor_141.py b/spirit/game/scripts/cards/SWSH8/Gliscor_141.py index dcfd046..f7a489e 100644 --- a/spirit/game/scripts/cards/SWSH8/Gliscor_141.py +++ b/spirit/game/scripts/cards/SWSH8/Gliscor_141.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack, discard_opponent_energy_attack card = PokemonCardDef( guid="f3cbdf35-397d-5f4e-8fe9-ae37ca558099", @@ -24,14 +25,14 @@ card = PokemonCardDef( game_text="Discard an Energy from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=discard_opponent_energy_attack(), ), Attack( title="Venomous Hit", game_text="Your opponent's Active Pok\u00e9mon is now Poisoned.", cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=100, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Golem_137.py b/spirit/game/scripts/cards/SWSH8/Golem_137.py index e287c11..7577a43 100644 --- a/spirit/game/scripts/cards/SWSH8/Golem_137.py +++ b/spirit/game/scripts/cards/SWSH8/Golem_137.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack +from spirit.game.card_effects.pokemon import in_active_spot + + +async def desperate_blast(ctx): + """Active Spot only: if Knocked Out by damage from an opponent's attack, + put 10 damage counters on the Attacking Pokemon.""" + if not ctx.ko_from_attack or not in_active_spot(ctx.board, ctx.player_id, ctx.source): + return + attacker = ctx.ko_attacker + if attacker is None: + return + await ctx.deal_damage(100, target=attacker, apply_modifiers=False, as_counters=True) + card = PokemonCardDef( guid="360da923-3054-5c81-b67c-46a19b9bd2f8", @@ -22,14 +36,15 @@ card = PokemonCardDef( Ability( title="Desperate Blast", game_text="If this Pok\u00e9mon is in the Active Spot and is Knocked Out by damage from an attack from your opponent's Pok\u00e9mon, put 10 damage counters on the Attacking Pok\u00e9mon.", - effect=unimplemented, + trigger=Triggers.ON_KNOCKED_OUT, + effect=desperate_blast, ), Attack( title="Double-Edge", game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 2}, damage=160, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Goodra_197.py b/spirit/game/scripts/cards/SWSH8/Goodra_197.py index 1d5b59a..8e7b9c7 100644 --- a/spirit/game/scripts/cards/SWSH8/Goodra_197.py +++ b/spirit/game/scripts/cards/SWSH8/Goodra_197.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import energy_attach_tax_passive card = PokemonCardDef( guid="bb0a1c6d-4123-5c55-be80-e9291bcb78cc", @@ -21,7 +22,7 @@ card = PokemonCardDef( Ability( title="Slimy Room", game_text="As long as this Pok\u00e9mon is in the Active Spot, whenever your opponent tries to attach an Energy card from their hand to a Pok\u00e9mon, they must flip a coin. If tails, your opponent discards that Energy card instead of attaching it, and this doesn't use up their Energy attachment for the turn.", - effect=unimplemented, + passive=energy_attach_tax_passive(opponents_only=True, while_active=True), ), Attack( title="Buster Tail", diff --git a/spirit/game/scripts/cards/SWSH8/Gorebyss_67.py b/spirit/game/scripts/cards/SWSH8/Gorebyss_67.py index f016792..9cee446 100644 --- a/spirit/game/scripts/cards/SWSH8/Gorebyss_67.py +++ b/spirit/game/scripts/cards/SWSH8/Gorebyss_67.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, subtypes_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import heal_attack +from spirit.game.card_effects.passives_common import ability_lock_passive, opposing_pokemon + + +def _rapid_strike_lock(pokemon, carrier): + return opposing_pokemon(pokemon, carrier) and "Rapid Strike" in subtypes_for(pokemon.archetype_id) card = PokemonCardDef( guid="2f698a4c-92c4-55f9-a0b5-99cb79912b5c", @@ -22,14 +28,14 @@ card = PokemonCardDef( Ability( title="Rapid Strike Canceler", game_text="Your opponent's Rapid Strike Pok\u00e9mon in play have no Abilities.", - effect=unimplemented, + passive=ability_lock_passive(_rapid_strike_lock), ), Attack( title="Draining Kiss", game_text="Heal 30 damage from this Pok\u00e9mon.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 1}, damage=50, - effect=unimplemented, + effect=heal_attack(30, target="self"), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Granbull_116.py b/spirit/game/scripts/cards/SWSH8/Granbull_116.py index 6be4307..2804fb6 100644 --- a/spirit/game/scripts/cards/SWSH8/Granbull_116.py +++ b/spirit/game/scripts/cards/SWSH8/Granbull_116.py @@ -1,5 +1,24 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID, TrainerType + +_TOOL_TYPES = (TrainerType.POKEMON_TOOL.value, TrainerType.POKEMON_TOOL_F.value) + + +def _is_tool_card(card): + return card.get_attribute(AttrID.TRAINER_TYPE) in _TOOL_TYPES + + +async def dig_up(ctx): + """On evolve: you may put up to 2 Pokemon Tool cards from your discard pile into your hand.""" + tools = [c for c in ctx.discard_pile() if _is_tool_card(c)] + if not tools: + return + picks = await ctx.choose_cards( + tools, 2, minimum=0, + prompt="Choose up to 2 Pokémon Tool cards to put into your hand.", + ) + await ctx.put_in_hand(picks, reveal=False) + card = PokemonCardDef( guid="3a5e5018-64d5-5256-9998-7ca0506ed06c", @@ -22,7 +41,8 @@ card = PokemonCardDef( Ability( title="Dig Up", game_text="When you play this Pok\u00e9mon from your hand to evolve 1 of your Pok\u00e9mon during your turn, you may put up to 2 Pok\u00e9mon Tool cards from your discard pile into your hand.", - effect=unimplemented, + trigger=Triggers.ON_EVOLVE, + effect=dig_up, ), Attack( title="Bite", diff --git a/spirit/game/scripts/cards/SWSH8/GreedentVMAX_218.py b/spirit/game/scripts/cards/SWSH8/GreedentVMAX_218.py index a64af40..3ef901b 100644 --- a/spirit/game/scripts/cards/SWSH8/GreedentVMAX_218.py +++ b/spirit/game/scripts/cards/SWSH8/GreedentVMAX_218.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.support_common import draw_attack + + +async def turn_a_profit(ctx): + """30. If the opponent's Basic is Knocked Out by this damage, take 2 more Prizes.""" + defender = ctx.defender + was_basic = ( + defender is not None + and defender.get_attribute(AttrID.STAGE) == PokemonStage.BASIC.value + ) + await ctx.deal_damage() + if was_basic and defender in ctx.knockouts: + ctx.extra_prizes += 2 + card = PokemonCardDef( guid="85df8316-8135-5eba-ada9-8fb201378dc6", @@ -24,14 +38,14 @@ card = PokemonCardDef( game_text="If your opponent's Basic Pok\u00e9mon is Knocked Out by damage from this attack, take 2 more Prize cards.", cost={PokemonTypes.COLORLESS: 2}, damage=30, - effect=unimplemented, + effect=turn_a_profit, ), Attack( title="Max Gimme Gimme", game_text="Draw 3 cards.", cost={PokemonTypes.COLORLESS: 3}, damage=160, - effect=unimplemented, + effect=draw_attack(3), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/GreedentVMAX_272.py b/spirit/game/scripts/cards/SWSH8/GreedentVMAX_272.py index 2a6d773..c7d0911 100644 --- a/spirit/game/scripts/cards/SWSH8/GreedentVMAX_272.py +++ b/spirit/game/scripts/cards/SWSH8/GreedentVMAX_272.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.support_common import draw_attack + + +async def turn_a_profit(ctx): + """30. If the opponent's Basic is Knocked Out by this damage, take 2 more Prizes.""" + defender = ctx.defender + was_basic = ( + defender is not None + and defender.get_attribute(AttrID.STAGE) == PokemonStage.BASIC.value + ) + await ctx.deal_damage() + if was_basic and defender in ctx.knockouts: + ctx.extra_prizes += 2 + card = PokemonCardDef( guid="2904c289-5bcb-5b39-a1b8-0a60b1d2df7e", @@ -24,14 +38,14 @@ card = PokemonCardDef( game_text="If your opponent's Basic Pok\u00e9mon is Knocked Out by damage from this attack, take 2 more Prize cards.", cost={PokemonTypes.COLORLESS: 2}, damage=30, - effect=unimplemented, + effect=turn_a_profit, ), Attack( title="Max Gimme Gimme", game_text="Draw 3 cards.", cost={PokemonTypes.COLORLESS: 3}, damage=160, - effect=unimplemented, + effect=draw_attack(3), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/GreedentV_217.py b/spirit/game/scripts/cards/SWSH8/GreedentV_217.py index 9920b97..081aaf7 100644 --- a/spirit/game/scripts/cards/SWSH8/GreedentV_217.py +++ b/spirit/game/scripts/cards/SWSH8/GreedentV_217.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.support_common import draw_attack card = PokemonCardDef( guid="fb93220a-360b-5958-b908-c7cd6eb5055b", @@ -23,14 +25,14 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.COLORLESS: 2}, damage=40, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), Attack( title="Nom-Nom-Nom Incisors", game_text="Draw 3 cards.", cost={PokemonTypes.COLORLESS: 3}, damage=120, - effect=unimplemented, + effect=draw_attack(3), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/GreedentV_256.py b/spirit/game/scripts/cards/SWSH8/GreedentV_256.py index a830b7c..6ef40fa 100644 --- a/spirit/game/scripts/cards/SWSH8/GreedentV_256.py +++ b/spirit/game/scripts/cards/SWSH8/GreedentV_256.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.support_common import draw_attack card = PokemonCardDef( guid="940d3d40-fae0-54e5-93f5-83dbdab5ee41", @@ -23,14 +25,14 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.COLORLESS: 2}, damage=40, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), Attack( title="Nom-Nom-Nom Incisors", game_text="Draw 3 cards.", cost={PokemonTypes.COLORLESS: 3}, damage=120, - effect=unimplemented, + effect=draw_attack(3), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/GreedentV_257.py b/spirit/game/scripts/cards/SWSH8/GreedentV_257.py index b7bac93..10d490a 100644 --- a/spirit/game/scripts/cards/SWSH8/GreedentV_257.py +++ b/spirit/game/scripts/cards/SWSH8/GreedentV_257.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.support_common import draw_attack card = PokemonCardDef( guid="790a30c6-421a-566f-ab54-38587b9a1469", @@ -23,14 +25,14 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.COLORLESS: 2}, damage=40, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), Attack( title="Nom-Nom-Nom Incisors", game_text="Draw 3 cards.", cost={PokemonTypes.COLORLESS: 3}, damage=120, - effect=unimplemented, + effect=draw_attack(3), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/GreninjaV_73.py b/spirit/game/scripts/cards/SWSH8/GreninjaV_73.py index d09765c..64bb72e 100644 --- a/spirit/game/scripts/cards/SWSH8/GreninjaV_73.py +++ b/spirit/game/scripts/cards/SWSH8/GreninjaV_73.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="d0761bfd-6814-5f15-a761-5ebe8261dc99", @@ -29,7 +30,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, damage=80, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=3, per_heads=80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Grimmsnarl_178.py b/spirit/game/scripts/cards/SWSH8/Grimmsnarl_178.py index 414bbd9..1ca0c51 100644 --- a/spirit/game/scripts/cards/SWSH8/Grimmsnarl_178.py +++ b/spirit/game/scripts/cards/SWSH8/Grimmsnarl_178.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if + + +def _bench_two_or_fewer(ctx): + return len(ctx.my_bench()) <= 2 + card = PokemonCardDef( guid="b7c3f347-56bc-582d-b9ca-36de009c0bc0", @@ -30,7 +36,7 @@ card = PokemonCardDef( cost={PokemonTypes.DARKNESS: 2}, damage=100, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_bench_two_or_fewer, 140), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Growlithe_32.py b/spirit/game/scripts/cards/SWSH8/Growlithe_32.py index d58ee81..864f1b5 100644 --- a/spirit/game/scripts/cards/SWSH8/Growlithe_32.py +++ b/spirit/game/scripts/cards/SWSH8/Growlithe_32.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_attach_energy +from spirit.game.card_effects.pokemon import energy_provides_type + + +def _is_fire_energy(card): + return energy_provides_type(card, PokemonTypes.FIRE.value) + card = PokemonCardDef( guid="06924b6a-7a00-521b-aaf2-4fe135b9f1fa", @@ -22,7 +29,7 @@ card = PokemonCardDef( title="Warm Up", game_text="Search your deck for a Fire Energy card and attach it to 1 of your Pok\u00e9mon. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_attach_energy(predicate=_is_fire_energy, count=1, distribute=False), ), Attack( title="Combustion", diff --git a/spirit/game/scripts/cards/SWSH8/Grubbin_18.py b/spirit/game/scripts/cards/SWSH8/Grubbin_18.py index 025dcba..2b27721 100644 --- a/spirit/game/scripts/cards/SWSH8/Grubbin_18.py +++ b/spirit/game/scripts/cards/SWSH8/Grubbin_18.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_or_nothing +from spirit.game.card_effects.support_common import attach_from_discard +from spirit.game.card_effects.pokemon import is_lightning_energy card = PokemonCardDef( guid="ea90c12b-2582-5ae7-92dc-7aa45fc8e37d", @@ -22,14 +25,17 @@ card = PokemonCardDef( title="Energize", game_text="Attach a Lightning Energy card from your discard pile to this Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=attach_from_discard( + predicate=is_lightning_energy, count=1, target="self", + prompt="Choose a Lightning Energy card to attach.", + ), ), Attack( title="Surprise Attack", game_text="Flip a coin. If tails, this attack does nothing.", cost={PokemonTypes.COLORLESS: 3}, damage=50, - effect=unimplemented, + effect=flip_or_nothing(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Hariyama_143.py b/spirit/game/scripts/cards/SWSH8/Hariyama_143.py index e630fea..fb9fea3 100644 --- a/spirit/game/scripts/cards/SWSH8/Hariyama_143.py +++ b/spirit/game/scripts/cards/SWSH8/Hariyama_143.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import guts_survive_passive card = PokemonCardDef( guid="7274b474-ca05-58b3-80b8-660997eeb80f", @@ -22,7 +23,7 @@ card = PokemonCardDef( Ability( title="Guts", game_text="If this Pok\u00e9mon would be Knocked Out by damage from an attack, flip a coin. If heads, this Pok\u00e9mon is not Knocked Out, and its remaining HP becomes 10.", - effect=unimplemented, + passive=guts_survive_passive(hp_floor=10, title="Guts", flip=True), ), Attack( title="Hammer In", diff --git a/spirit/game/scripts/cards/SWSH8/Hawlucha_216.py b/spirit/game/scripts/cards/SWSH8/Hawlucha_216.py index fa02e54..01c87bb 100644 --- a/spirit/game/scripts/cards/SWSH8/Hawlucha_216.py +++ b/spirit/game/scripts/cards/SWSH8/Hawlucha_216.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import discard_opponent_energy_attack card = PokemonCardDef( guid="ce8af50e-bd7e-5f9c-ad3a-3ee8787d7825", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Discard a Special Energy from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, damage=20, - effect=unimplemented, + effect=discard_opponent_energy_attack(special_only=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Heatmor_41.py b/spirit/game/scripts/cards/SWSH8/Heatmor_41.py index f552a55..4067c9e 100644 --- a/spirit/game/scripts/cards/SWSH8/Heatmor_41.py +++ b/spirit/game/scripts/cards/SWSH8/Heatmor_41.py @@ -1,5 +1,28 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import count_energy +from spirit.game.card_effects.pokemon import is_energy_card +from spirit.game.card_effects.support_common import attach_from_discard + +_ENERGY_ON_SELF = count_energy("self") + + +def is_fire_energy_card(card): + return is_energy_card(card) and PokemonTypes.FIRE.value in (card.get_attribute(AttrID.POKEMON_TYPES) or []) + + +async def exciting_flame(ctx): + await ctx.deal_damage() + total_cost = sum(ctx.ability.cost.values()) + if _ENERGY_ON_SELF(ctx) < total_cost + 3: + return + bench = ctx.opponent_bench() + if not bench: + return + target = await ctx.choose_pokemon(bench, "Choose 1 of your opponent's Benched Pokémon") + if target is not None: + await ctx.deal_damage(180, target=target, apply_modifiers=False) + card = PokemonCardDef( guid="c87d6fb9-2d20-5cfa-ad8f-532b59c883ea", @@ -23,14 +46,14 @@ card = PokemonCardDef( game_text="Attach a Fire Energy card from your discard pile to this Pok\u00e9mon.", cost={PokemonTypes.FIRE: 1}, damage=20, - effect=unimplemented, + effect=attach_from_discard(predicate=is_fire_energy_card, count=1, target="self"), ), Attack( title="Exciting Flame", game_text="If this Pok\u00e9mon has at least 3 extra Energy attached (in addition to this attack's cost), this attack also does 180 damage to 1 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=90, - effect=unimplemented, + effect=exciting_flame, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Heliolisk_99.py b/spirit/game/scripts/cards/SWSH8/Heliolisk_99.py index 8f0b309..4d40567 100644 --- a/spirit/game/scripts/cards/SWSH8/Heliolisk_99.py +++ b/spirit/game/scripts/cards/SWSH8/Heliolisk_99.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import snipe_attack card = PokemonCardDef( guid="f0aecbcd-f120-5aa3-a2be-d802c03a6e1a", @@ -29,7 +30,7 @@ card = PokemonCardDef( game_text="This attack also does 20 damage to 1 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 1}, damage=60, - effect=unimplemented, + effect=snipe_attack(20, pool="bench", side="opponent", also_base=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/HoopaV_253.py b/spirit/game/scripts/cards/SWSH8/HoopaV_253.py index 6a3a618..9740309 100644 --- a/spirit/game/scripts/cards/SWSH8/HoopaV_253.py +++ b/spirit/game/scripts/cards/SWSH8/HoopaV_253.py @@ -1,5 +1,27 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.passives import Passive + + +class TwoFacedPassive(Passive): + """As long as this Pokemon is IN PLAY it is Psychic and Darkness type + (a live type rewrite, so the deck/discard card stays printed Darkness).""" + + def modify_pokemon_types(self, types, pokemon, carrier): + if pokemon is not carrier: + return types + both = [PokemonTypes.PSYCHIC.value, PokemonTypes.DARKNESS.value] + return both + [t for t in types if t not in both] + + +async def shadow_impact(ctx): + """170. Put 3 damage counters on 1 of your Pokemon.""" + await ctx.deal_damage() + own = ctx.my_pokemon_in_play() + if own: + target = await ctx.choose_pokemon(own, "Choose 1 of your Pokémon") + if target is not None: + await ctx.deal_damage(30, target=target, apply_modifiers=False, as_counters=True) card = PokemonCardDef( guid="05bc0901-d9ea-5491-81eb-e1db95f4837a", @@ -21,14 +43,14 @@ card = PokemonCardDef( Ability( title="Two-Faced", game_text="As long as this Pok\u00e9mon is in play, it is Psychic and Darkness type.", - effect=unimplemented, + passive=TwoFacedPassive(), ), Attack( title="Shadow Impact", game_text="Put 3 damage counters on 1 of your Pok\u00e9mon.", cost={PokemonTypes.DARKNESS: 2, PokemonTypes.COLORLESS: 1}, damage=170, - effect=unimplemented, + effect=shadow_impact, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Huntail_66.py b/spirit/game/scripts/cards/SWSH8/Huntail_66.py index 226ca1b..a1e8497 100644 --- a/spirit/game/scripts/cards/SWSH8/Huntail_66.py +++ b/spirit/game/scripts/cards/SWSH8/Huntail_66.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, subtypes_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import opponent_attack_tax card = PokemonCardDef( guid="df99ed28-b66c-52ac-a494-ed6652d9ce6d", @@ -22,7 +23,10 @@ card = PokemonCardDef( Ability( title="Single Strike Jammer", game_text="Your opponent's Single Strike Pok\u00e9mon's attacks cost Colorless more.", - effect=unimplemented, + passive=opponent_attack_tax( + 1, + target_pred=lambda p: "Single Strike" in subtypes_for(p.archetype_id), + ), ), Attack( title="Cavernous Chomp", diff --git a/spirit/game/scripts/cards/SWSH8/Impidimp_176.py b/spirit/game/scripts/cards/SWSH8/Impidimp_176.py index 7aefa51..1c54556 100644 --- a/spirit/game/scripts/cards/SWSH8/Impidimp_176.py +++ b/spirit/game/scripts/cards/SWSH8/Impidimp_176.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus card = PokemonCardDef( guid="7a75dcf2-6fa1-523f-bdaf-fe50cc34bf32", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.DARKNESS: 1}, damage=10, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Indeedee_127.py b/spirit/game/scripts/cards/SWSH8/Indeedee_127.py index fad08ae..56d080f 100644 --- a/spirit/game/scripts/cards/SWSH8/Indeedee_127.py +++ b/spirit/game/scripts/cards/SWSH8/Indeedee_127.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_bench card = PokemonCardDef( guid="86e151db-78db-59f6-ba24-9f207b622c14", @@ -23,7 +24,7 @@ card = PokemonCardDef( title="Call for Family", game_text="Search your deck for up to 2 Basic Pok\u00e9mon and put them onto your Bench. Then, shuffle your deck.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=search_to_bench(count=2), ), Attack( title="Heart Sign", diff --git a/spirit/game/scripts/cards/SWSH8/InteleonVMAX_266.py b/spirit/game/scripts/cards/SWSH8/InteleonVMAX_266.py index a5c483f..d1eb1b7 100644 --- a/spirit/game/scripts/cards/SWSH8/InteleonVMAX_266.py +++ b/spirit/game/scripts/cards/SWSH8/InteleonVMAX_266.py @@ -1,5 +1,56 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.support_common import requires_hand +from spirit.game.card_effects.trainers import is_energy_card + + +def _is_water_energy(card) -> bool: + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_energy_card(card) and PokemonTypes.WATER.value in types + + +def _double_gunner_condition(board, player_id, pokemon): + opponent_id = next((pid for pid in board.player_ids if pid != player_id), None) + bench = board.find_player_area(opponent_id, "bench") if opponent_id else None + if not bench or not bench.children: + return False + return requires_hand(_is_water_energy)(board, player_id, pokemon) + + +async def _double_gunner(ctx): + """Discard a Water Energy from hand; choose 2 of the opponent's Benched + Pokemon and put 2 damage counters on each.""" + discarded = await ctx.discard_from_hand( + 1, predicate=_is_water_energy, + prompt="Discard a Water Energy card to use Double Gunner", + ) + if not discarded: + return + bench = ctx.opponent_bench() + if not bench: + return + targets = await ctx.choose_cards( + bench, 2, minimum=1, prompt="Choose up to 2 of your opponent's Benched Pokémon", + ) + for target in targets: + await ctx.deal_damage(20, target=target, apply_modifiers=False, as_counters=True) + + +async def _g_max_spiral(ctx): + """70, +70 more if you put an attached Energy into your hand.""" + energies = ctx.attached_energies(ctx.attacker) + bonus = 0 + if energies and await ctx.ask_yes_no( + "Put an Energy attached to this Pokémon into your hand? If you do, this attack does 70 more damage." + ): + picks = await ctx.choose_cards( + energies, 1, prompt="Choose an Energy card to put into your hand", + ) + if picks: + await ctx.put_in_hand(picks, reveal=False) + bonus = 70 + await ctx.deal_damage(70 + bonus) + card = PokemonCardDef( guid="829e0d34-0bb2-553d-8101-93b2f5b3ec37", @@ -22,7 +73,9 @@ card = PokemonCardDef( Ability( title="Double Gunner", game_text="You must discard a Water Energy card from your hand in order to use this Ability. Once during your turn, you may choose 2 of your opponent's Benched Pok\u00e9mon and put 2 damage counters on each of them.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=_double_gunner_condition, + effect=_double_gunner, ), Attack( title="G-Max Spiral", @@ -30,7 +83,7 @@ card = PokemonCardDef( cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 1}, damage=70, damage_operator="+", - effect=unimplemented, + effect=_g_max_spiral, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/InteleonVMAX_79.py b/spirit/game/scripts/cards/SWSH8/InteleonVMAX_79.py index d7d93f1..adfd37b 100644 --- a/spirit/game/scripts/cards/SWSH8/InteleonVMAX_79.py +++ b/spirit/game/scripts/cards/SWSH8/InteleonVMAX_79.py @@ -1,5 +1,56 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.support_common import requires_hand +from spirit.game.card_effects.trainers import is_energy_card + + +def _is_water_energy(card) -> bool: + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_energy_card(card) and PokemonTypes.WATER.value in types + + +def _double_gunner_condition(board, player_id, pokemon): + opponent_id = next((pid for pid in board.player_ids if pid != player_id), None) + bench = board.find_player_area(opponent_id, "bench") if opponent_id else None + if not bench or not bench.children: + return False + return requires_hand(_is_water_energy)(board, player_id, pokemon) + + +async def _double_gunner(ctx): + """Discard a Water Energy from hand; choose 2 of the opponent's Benched + Pokemon and put 2 damage counters on each.""" + discarded = await ctx.discard_from_hand( + 1, predicate=_is_water_energy, + prompt="Discard a Water Energy card to use Double Gunner", + ) + if not discarded: + return + bench = ctx.opponent_bench() + if not bench: + return + targets = await ctx.choose_cards( + bench, 2, minimum=1, prompt="Choose up to 2 of your opponent's Benched Pokémon", + ) + for target in targets: + await ctx.deal_damage(20, target=target, apply_modifiers=False, as_counters=True) + + +async def _g_max_spiral(ctx): + """70, +70 more if you put an attached Energy into your hand.""" + energies = ctx.attached_energies(ctx.attacker) + bonus = 0 + if energies and await ctx.ask_yes_no( + "Put an Energy attached to this Pokémon into your hand? If you do, this attack does 70 more damage." + ): + picks = await ctx.choose_cards( + energies, 1, prompt="Choose an Energy card to put into your hand", + ) + if picks: + await ctx.put_in_hand(picks, reveal=False) + bonus = 70 + await ctx.deal_damage(70 + bonus) + card = PokemonCardDef( guid="ce99ea43-7ce3-57c6-9c1f-c7cb7e38938a", @@ -21,16 +72,18 @@ card = PokemonCardDef( abilities=[ Ability( title="Double Gunner", - game_text="You must discard a Water Energy card from your hand in order to use this Ability. Once during your turn, you may choose 2 of your opponent's Benched Pok\u00e9mon and put 2 damage counters on each of them.", - effect=unimplemented, + game_text="You must discard a Water Energy card from your hand in order to use this Ability. Once during your turn, you may choose 2 of your opponent's Benched Pokémon and put 2 damage counters on each of them.", + activation=Activations.ONCE_PER_TURN, + condition=_double_gunner_condition, + effect=_double_gunner, ), Attack( title="G-Max Spiral", - game_text="You may put an Energy attached to this Pok\u00e9mon into your hand. If you do, this attack does 70 more damage.", + game_text="You may put an Energy attached to this Pokémon into your hand. If you do, this attack does 70 more damage.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 1}, damage=70, damage_operator="+", - effect=unimplemented, + effect=_g_max_spiral, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH8/InteleonV_78.py b/spirit/game/scripts/cards/SWSH8/InteleonV_78.py index 8e194fb..66f1dce 100644 --- a/spirit/game/scripts/cards/SWSH8/InteleonV_78.py +++ b/spirit/game/scripts/cards/SWSH8/InteleonV_78.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import snipe_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -28,7 +29,7 @@ card = PokemonCardDef( game_text="This attack also does 20 damage to 1 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + effect=snipe_attack(20, also_base=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Jigglypuff_110.py b/spirit/game/scripts/cards/SWSH8/Jigglypuff_110.py index 345ef13..56114f3 100644 --- a/spirit/game/scripts/cards/SWSH8/Jigglypuff_110.py +++ b/spirit/game/scripts/cards/SWSH8/Jigglypuff_110.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import damage_per, count_bench, has_attack_titled +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -29,7 +30,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=20, damage_operator="x", - effect=unimplemented, + effect=damage_per(count_bench("mine", has_attack_titled("Let's All Rollout")), 20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Jynx_112.py b/spirit/game/scripts/cards/SWSH8/Jynx_112.py index acf423c..40e75ef 100644 --- a/spirit/game/scripts/cards/SWSH8/Jynx_112.py +++ b/spirit/game/scripts/cards/SWSH8/Jynx_112.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.support_common import draw_attack card = PokemonCardDef( guid="6cddf973-9104-51fa-a0c2-f9638e97d06a", @@ -23,14 +25,14 @@ card = PokemonCardDef( title="Double Draw", game_text="Draw 2 cards.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=draw_attack(2), ), Attack( title="Dazzle Dance", - game_text="Your opponent's Active Pok\u00e9mon is now Confused.", + game_text="Your opponent's Active Pokémon is now Confused.", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH8/Kangaskhan_204.py b/spirit/game/scripts/cards/SWSH8/Kangaskhan_204.py index 5817c69..00a8860 100644 --- a/spirit/game/scripts/cards/SWSH8/Kangaskhan_204.py +++ b/spirit/game/scripts/cards/SWSH8/Kangaskhan_204.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus card = PokemonCardDef( guid="288b9752-f884-529f-9a58-5d1bc2ae2f72", @@ -29,7 +30,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 3}, damage=60, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(100), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Klefki_186.py b/spirit/game/scripts/cards/SWSH8/Klefki_186.py index 1e1392d..4655271 100644 --- a/spirit/game/scripts/cards/SWSH8/Klefki_186.py +++ b/spirit/game/scripts/cards/SWSH8/Klefki_186.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import draw_attack card = PokemonCardDef( guid="8ac9414e-3b1a-52da-b443-9dfc5613c9da", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Draw 2 cards.", cost={PokemonTypes.COLORLESS: 1}, damage=10, - effect=unimplemented, + effect=draw_attack(2), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Landorus_148.py b/spirit/game/scripts/cards/SWSH8/Landorus_148.py index 283930d..5a28628 100644 --- a/spirit/game/scripts/cards/SWSH8/Landorus_148.py +++ b/spirit/game/scripts/cards/SWSH8/Landorus_148.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import switch_self_attack + + +async def earthen_boom(ctx): + await ctx.deal_damage() + await ctx.move_energy_freely([ctx.attacker], ctx.my_bench()) card = PokemonCardDef( guid="8ec0e39e-20bf-5eef-88c9-9dd44433c60d", @@ -23,14 +29,14 @@ card = PokemonCardDef( game_text="You may switch this Pok\u00e9mon with 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, damage=20, - effect=unimplemented, + effect=switch_self_attack(optional=True), ), Attack( title="Earthen Boom", game_text="Move all Energy from this Pok\u00e9mon to your Benched Pok\u00e9mon in any way you like.", cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=120, - effect=unimplemented, + effect=earthen_boom, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Lapras_54.py b/spirit/game/scripts/cards/SWSH8/Lapras_54.py index 4e4cbae..9ef2fbf 100644 --- a/spirit/game/scripts/cards/SWSH8/Lapras_54.py +++ b/spirit/game/scripts/cards/SWSH8/Lapras_54.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack + + +async def splash_arch(ctx): + """Put all Energy attached to this Pokémon into your hand. 100 damage to + 1 of your opponent's Benched Pokémon (no W/R).""" + energies = ctx.attached_energies(ctx.attacker) + if energies: + await ctx.put_in_hand(list(energies), reveal=False) + bench = ctx.opponent_bench() + if bench: + target = await ctx.choose_pokemon( + bench, "Choose 1 of your opponent's Benched Pokémon" + ) + if target is not None: + await ctx.deal_damage(100, target=target, apply_modifiers=False) + card = PokemonCardDef( guid="f3332db4-1d0d-571e-8049-75d336c136a9", @@ -22,13 +39,13 @@ card = PokemonCardDef( title="Icy Wind", game_text="Your opponent's Active Pok\u00e9mon is now Asleep.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=condition_attack(SpecialConditions.ASLEEP), ), Attack( title="Splash Arch", game_text="Put all Energy attached to this Pok\u00e9mon into your hand. This attack does 100 damage to 1 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=splash_arch, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Latias_193.py b/spirit/game/scripts/cards/SWSH8/Latias_193.py index 3185311..7e451c2 100644 --- a/spirit/game/scripts/cards/SWSH8/Latias_193.py +++ b/spirit/game/scripts/cards/SWSH8/Latias_193.py @@ -1,5 +1,68 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.card_effects.passives_common import prevent_damage_when +from spirit.game.card_effects.pokemon import is_pokemon_vmax + + +def _psychic_energy_in_hand(ctx): + return [ + c for c in ctx.hand() + if is_basic_energy_card(c) + and PokemonTypes.PSYCHIC.value in (c.get_attribute(AttrID.POKEMON_TYPES) or []) + ] + + +def _my_latios(ctx): + return [ + p for p in ctx.my_pokemon_in_play() + if p.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) == "Latios" + ] + + +def _red_assist_condition(board, player_id, pokemon): + hand = board.find_player_area(player_id, "hand") + if not hand or not any( + is_basic_energy_card(c) + and PokemonTypes.PSYCHIC.value in (c.get_attribute(AttrID.POKEMON_TYPES) or []) + for c in hand.children + ): + return False + return any( + p.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) == "Latios" + for p in board.pokemon_in_play(player_id) + ) + + +async def _red_assist(ctx): + energies = _psychic_energy_in_hand(ctx) + targets = _my_latios(ctx) + if not energies or not targets: + return + if not await ctx.ask_yes_no( + "Attach a Psychic Energy card from your hand to 1 of your Latios?" + ): + return + picked = await ctx.choose_cards( + energies, 1, minimum=1, prompt="Choose a Psychic Energy card to attach" + ) + if not picked: + return + target = await ctx.choose_pokemon(targets, "Choose the Latios to attach it to") + if target is not None: + await ctx.attach_energy(picked[0], target) + + +async def _dyna_barrier(ctx): + await ctx.deal_damage() + ctx.add_passive_through_opponents_turn( + ctx.attacker, + prevent_damage_when( + lambda calc, carrier: calc.attacker is not None + and is_pokemon_vmax(calc.attacker.archetype_id) + ), + ) + card = PokemonCardDef( guid="50c1332e-cf73-5e50-9daf-c50ca23daa51", @@ -20,14 +83,16 @@ card = PokemonCardDef( Ability( title="Red Assist", game_text="Once during your turn, you may attach a Psychic Energy card from your hand to 1 of your Latios.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=_red_assist_condition, + effect=_red_assist, ), Attack( title="Dyna Barrier", game_text="During your opponent's next turn, prevent all damage done to this Pok\u00e9mon by attacks from Pok\u00e9mon VMAX.", cost={PokemonTypes.FIRE: 1, PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=70, - effect=unimplemented, + effect=_dyna_barrier, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Latios_194.py b/spirit/game/scripts/cards/SWSH8/Latios_194.py index e0d5e5a..11f4467 100644 --- a/spirit/game/scripts/cards/SWSH8/Latios_194.py +++ b/spirit/game/scripts/cards/SWSH8/Latios_194.py @@ -1,5 +1,53 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import self_energy_discard_attack +from spirit.game.card_effects.pokemon import is_energy_card, energy_provides_type + + +def _latias_in_play(board, player_id): + return [ + p for p in board.pokemon_in_play(player_id) + if p.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) == "Latias" + ] + + +def blue_assist_condition(board, player_id, pokemon): + if not _latias_in_play(board, player_id): + return False + hand_area = board.find_player_area(player_id, "hand") + hand = hand_area.children if hand_area else [] + return any( + is_energy_card(c) and energy_provides_type(c, PokemonTypes.PSYCHIC.value) + for c in hand + ) + + +async def blue_assist(ctx): + """Once per turn, you may attach a Psychic Energy card from your hand + to 1 of your Latias.""" + latias_list = _latias_in_play(ctx.board, ctx.player_id) + if not latias_list: + return + hand_energy = [ + c for c in ctx.hand() + if is_energy_card(c) and energy_provides_type(c, PokemonTypes.PSYCHIC.value) + ] + if not hand_energy: + return + if not await ctx.ask_yes_no( + "Attach a Psychic Energy card from your hand to 1 of your Latias?" + ): + return + picks = await ctx.choose_cards( + hand_energy, 1, prompt="Choose a Psychic Energy card to attach", + ) + if not picks: + return + target = await ctx.choose_pokemon(latias_list, "Choose 1 of your Latias") + if target is None: + return + await ctx.attach_energy(picks[0], target) + card = PokemonCardDef( guid="0797b843-dbbd-512b-9582-2f55389fb9cb", @@ -20,14 +68,16 @@ card = PokemonCardDef( Ability( title="Blue Assist", game_text="Once during your turn, you may attach a Psychic Energy card from your hand to 1 of your Latias.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=blue_assist_condition, + effect=blue_assist, ), Attack( title="Luster Purge", game_text="Discard 2 Energy from this Pok\u00e9mon.", cost={PokemonTypes.WATER: 2, PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=210, - effect=unimplemented, + effect=self_energy_discard_attack(count=2), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Leavanny_11.py b/spirit/game/scripts/cards/SWSH8/Leavanny_11.py index cfd4136..c1872c6 100644 --- a/spirit/game/scripts/cards/SWSH8/Leavanny_11.py +++ b/spirit/game/scripts/cards/SWSH8/Leavanny_11.py @@ -1,5 +1,29 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import full_stack + + +async def healing_circle(ctx): + """Heal all damage from each of your Benched Pokemon. If you healed any + damage this way, shuffle this Pokemon and all attached cards into your + deck.""" + healed_any = False + for pokemon in ctx.my_bench(): + if await ctx.heal(ctx.max_hp(pokemon), pokemon) > 0: + healed_any = True + if not healed_any: + return + await ctx.shuffle_into_deck(full_stack(ctx.attacker), ctx.player_id) + + async def _promote(): + if not await ctx.session._promote_new_active(ctx.player_id): + screen_name = ctx.session.players[ctx.player_id].screen_name + await ctx.session.end_game( + ctx.opponent_id, f"{screen_name} has no Pokémon left" + ) + + ctx.deferred_actions.append(_promote) + card = PokemonCardDef( guid="5dad5f7a-4a87-5a64-8afe-be97109e30b9", @@ -23,7 +47,7 @@ card = PokemonCardDef( title="Healing Circle", game_text="Heal all damage from each of your Benched Pok\u00e9mon. If you healed any damage in this way, shuffle this Pok\u00e9mon and all attached cards into your deck.", cost={PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + effect=healing_circle, ), Attack( title="Razor Leaf", diff --git a/spirit/game/scripts/cards/SWSH8/Magcargo_35.py b/spirit/game/scripts/cards/SWSH8/Magcargo_35.py index 3a9b3b6..56cb08c 100644 --- a/spirit/game/scripts/cards/SWSH8/Magcargo_35.py +++ b/spirit/game/scripts/cards/SWSH8/Magcargo_35.py @@ -1,6 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def body_splash(ctx): + """150 damage; flip 3 coins, discard an Energy from this Pokemon for each tails.""" + await ctx.deal_damage() + coins = await ctx.flip_coins(3, ctx.ability.title) + tails = coins.count(False) + if tails: + await ctx.discard_energy_from(ctx.attacker, tails, prompt="Discard Energy from Magcargo") + card = PokemonCardDef( guid="ba513087-fc63-5bc6-a6f4-c90f0722ad20", key="SWSH8", @@ -29,7 +38,7 @@ card = PokemonCardDef( game_text="Flip 3 coins. For each tails, discard an Energy from this Pok\u00e9mon.", cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=150, - effect=unimplemented, + effect=body_splash, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Mandibuzz_173.py b/spirit/game/scripts/cards/SWSH8/Mandibuzz_173.py index 4b7e78d..83d9cd9 100644 --- a/spirit/game/scripts/cards/SWSH8/Mandibuzz_173.py +++ b/spirit/game/scripts/cards/SWSH8/Mandibuzz_173.py @@ -1,5 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.passives import Passive + + +class NoEvolveDefenderPassive(Passive): + def blocks_evolution(self, player_id, target, carrier): + return target is carrier and player_id == carrier.owning_player_id + + +async def bone_block(ctx): + defender = ctx.defender + await ctx.deal_damage() + if defender is None or ctx.effects_blocked(defender): + return + ctx.add_passive_through_opponents_turn(defender, NoEvolveDefenderPassive()) + card = PokemonCardDef( guid="f81b344d-44ea-5465-9d48-e6414b2af7bc", @@ -25,7 +40,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, Pok\u00e9mon can't be played from your opponent's hand to evolve the Defending Pok\u00e9mon.", cost={PokemonTypes.DARKNESS: 1}, damage=20, - effect=unimplemented, + effect=bone_block, ), Attack( title="Dark Cutter", diff --git a/spirit/game/scripts/cards/SWSH8/Mantine_61.py b/spirit/game/scripts/cards/SWSH8/Mantine_61.py index 71b92ea..6706185 100644 --- a/spirit/game/scripts/cards/SWSH8/Mantine_61.py +++ b/spirit/game/scripts/cards/SWSH8/Mantine_61.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.support_common import switch_self_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="You may switch this Pok\u00e9mon with 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.WATER: 1}, damage=20, - effect=unimplemented, + effect=switch_self_attack(optional=True), ), Attack( title="Wave Splash", diff --git a/spirit/game/scripts/cards/SWSH8/Maractus_12.py b/spirit/game/scripts/cards/SWSH8/Maractus_12.py index f2590f0..c4f0bd0 100644 --- a/spirit/game/scripts/cards/SWSH8/Maractus_12.py +++ b/spirit/game/scripts/cards/SWSH8/Maractus_12.py @@ -1,5 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities, TrainerType + + +def _is_tool_card(card): + return card.get_attribute(AttrID.TRAINER_TYPE) in ( + TrainerType.POKEMON_TOOL.value, TrainerType.POKEMON_TOOL_F.value) + + +async def ditch_and_shake(ctx): + """Discard any number of Pokemon Tool cards from hand; 50 damage each.""" + picks = await ctx.discard_from_hand( + len(ctx.hand()), minimum=0, predicate=_is_tool_card, + prompt="Discard any number of Pokémon Tool cards from your hand.", + ) + await ctx.deal_damage(50 * len(picks)) + card = PokemonCardDef( guid="a8756632-c300-52e6-90f9-6098398f810a", @@ -29,7 +44,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=50, damage_operator="x", - effect=unimplemented, + effect=ditch_and_shake, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Marill_58.py b/spirit/game/scripts/cards/SWSH8/Marill_58.py index a33d29d..b049d30 100644 --- a/spirit/game/scripts/cards/SWSH8/Marill_58.py +++ b/spirit/game/scripts/cards/SWSH8/Marill_58.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import ( + snipe_attack, damage_per, count_bench, has_attack_titled, +) card = PokemonCardDef( guid="68675945-433b-59e6-9867-451a3a154215", @@ -22,7 +25,7 @@ card = PokemonCardDef( title="Aqua Liner", game_text="This attack does 20 damage to 1 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.WATER: 1}, - effect=unimplemented, + effect=snipe_attack(20), ), Attack( title="Let's All Rollout", @@ -30,7 +33,9 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=20, damage_operator="x", - effect=unimplemented, + effect=damage_per( + count_bench("mine", has_attack_titled("Let's All Rollout")), 20 + ), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Marshtomp_63.py b/spirit/game/scripts/cards/SWSH8/Marshtomp_63.py index b2b4103..64d4cef 100644 --- a/spirit/game/scripts/cards/SWSH8/Marshtomp_63.py +++ b/spirit/game/scripts/cards/SWSH8/Marshtomp_63.py @@ -1,6 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def energy_loop(ctx): + """80 damage. Put an Energy attached to this Pokemon into your hand.""" + await ctx.deal_damage() + energies = ctx.attached_energies(ctx.attacker) + if not energies: + return + picks = await ctx.choose_cards( + energies, 1, minimum=1, + prompt="Choose an Energy attached to this Pokémon to put into your hand.", + ) + # Already visible in play (attached), so no reveal-on-move is needed. + await ctx.put_in_hand(picks, reveal=False) + + card = PokemonCardDef( guid="12553b2f-ca2a-5b28-b272-ee7b7f157782", key="SWSH8", @@ -29,7 +44,7 @@ card = PokemonCardDef( game_text="Put an Energy attached to this Pok\u00e9mon into your hand.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=80, - effect=unimplemented, + effect=energy_loop, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Mawile_119.py b/spirit/game/scripts/cards/SWSH8/Mawile_119.py index 46a8af5..6228090 100644 --- a/spirit/game/scripts/cards/SWSH8/Mawile_119.py +++ b/spirit/game/scripts/cards/SWSH8/Mawile_119.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import ( + apply_defender_attack_cost_raise, apply_defender_retreat_cost_raise, +) + + +async def chomp_chomp_hold(ctx): + """During your opponent's next turn, the Defending Pokemon's attacks + cost [C] more, and its Retreat Cost is [C] more.""" + await ctx.deal_damage() + await apply_defender_attack_cost_raise(ctx, 1) + await apply_defender_retreat_cost_raise(ctx, 1) + card = PokemonCardDef( guid="6f0fcd6d-eaa7-5e8f-8de2-4eb7e8d4c5cd", @@ -23,7 +35,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, the Defending Pok\u00e9mon's attacks cost Colorless more, and its Retreat Cost is Colorless more.", cost={PokemonTypes.PSYCHIC: 1}, damage=30, - effect=unimplemented, + effect=chomp_chomp_hold, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Melmetal_189.py b/spirit/game/scripts/cards/SWSH8/Melmetal_189.py index 56e2277..4e1f5ef 100644 --- a/spirit/game/scripts/cards/SWSH8/Melmetal_189.py +++ b/spirit/game/scripts/cards/SWSH8/Melmetal_189.py @@ -1,5 +1,28 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.session.passives import Passive +from spirit.game.card_effects.attacks_common import self_energy_discard_attack + + +class _IngotSwingShield(Passive): + """Prevent all damage from an opposing attacker with an Ability.""" + + def prevents_damage(self, calc, carrier): + if not (calc.is_attack and calc.is_opposing and calc.target is carrier): + return False + attacker = calc.attacker + if attacker is None: + return False + return any( + isinstance(entry, dict) and entry.get("abilityType") != "Attack" + for entry in (attacker.get_attribute(AttrID.PIE_ABILITIES) or []) + ) + + +async def ingot_swing(ctx): + await ctx.deal_damage() + ctx.add_passive_through_opponents_turn(ctx.attacker, _IngotSwingShield()) + card = PokemonCardDef( guid="c6c77afc-4ec7-5cbf-88f7-62f24ec7a247", @@ -25,14 +48,14 @@ card = PokemonCardDef( game_text="During your opponent's next turn, prevent all damage done to this Pok\u00e9mon by attacks from Pok\u00e9mon that have an Ability.", cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 2}, damage=80, - effect=unimplemented, + effect=ingot_swing, ), Attack( title="Blasting Hammer", game_text="Discard an Energy from this Pok\u00e9mon.", cost={PokemonTypes.METAL: 2, PokemonTypes.COLORLESS: 2}, damage=150, - effect=unimplemented, + effect=self_energy_discard_attack(count=1), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Meloetta_124.py b/spirit/game/scripts/cards/SWSH8/Meloetta_124.py index 9dec456..575de27 100644 --- a/spirit/game/scripts/cards/SWSH8/Meloetta_124.py +++ b/spirit/game/scripts/cards/SWSH8/Meloetta_124.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, subtypes_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per + + +def _fusion_strike_energy_count(ctx): + total = 0 + for pokemon in ctx.my_pokemon_in_play(): + for energy in ctx.attached_energies(pokemon): + if "Fusion Strike" in subtypes_for(energy.archetype_id): + total += 1 + return total + card = PokemonCardDef( guid="416d73c5-8c62-5d88-a94f-564673022e2e", @@ -25,7 +36,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=70, damage_operator="x", - effect=unimplemented, + effect=damage_per(_fusion_strike_energy_count, 70), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Meltan_188.py b/spirit/game/scripts/cards/SWSH8/Meltan_188.py index 051c025..c3aa335 100644 --- a/spirit/game/scripts/cards/SWSH8/Meltan_188.py +++ b/spirit/game/scripts/cards/SWSH8/Meltan_188.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import heal_attack card = PokemonCardDef( guid="4e74f14d-a3bc-5fe8-b4e0-35be79684057", @@ -23,7 +24,7 @@ card = PokemonCardDef( title="Iron Intake", game_text="Heal 30 damage from this Pok\u00e9mon.", cost={PokemonTypes.METAL: 1}, - effect=unimplemented, + effect=heal_attack(30, target="self"), ), Attack( title="Headbutt", diff --git a/spirit/game/scripts/cards/SWSH8/Meowth_199.py b/spirit/game/scripts/cards/SWSH8/Meowth_199.py index ed058b9..12c200b 100644 --- a/spirit/game/scripts/cards/SWSH8/Meowth_199.py +++ b/spirit/game/scripts/cards/SWSH8/Meowth_199.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import draw_attack card = PokemonCardDef( guid="6d08744a-bdb2-5006-b757-b08b43308d5f", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Draw a card.", cost={PokemonTypes.COLORLESS: 1}, damage=10, - effect=unimplemented, + effect=draw_attack(1), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Metapod_2.py b/spirit/game/scripts/cards/SWSH8/Metapod_2.py index 7c698be..c6c3c0e 100644 --- a/spirit/game/scripts/cards/SWSH8/Metapod_2.py +++ b/spirit/game/scripts/cards/SWSH8/Metapod_2.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import takes_less_passive card = PokemonCardDef( guid="357fe989-3ff5-5fd7-a158-c5f7baa175a4", @@ -22,7 +23,7 @@ card = PokemonCardDef( Ability( title="Exoskeleton", game_text="This Pok\u00e9mon takes 20 less damage from attacks (after applying Weakness and Resistance).", - effect=unimplemented, + passive=takes_less_passive(20), ), Attack( title="Ram", diff --git a/spirit/game/scripts/cards/SWSH8/Minun_90.py b/spirit/game/scripts/cards/SWSH8/Minun_90.py index 3982370..ecd8756 100644 --- a/spirit/game/scripts/cards/SWSH8/Minun_90.py +++ b/spirit/game/scripts/cards/SWSH8/Minun_90.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_bench card = PokemonCardDef( guid="885bf6f4-18da-5c85-85d3-af7791f3ea95", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Call for Family", game_text="Search your deck for up to 2 Basic Pok\u00e9mon and put them onto your Bench. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_to_bench(count=2), ), Attack( title="Static Shock", diff --git a/spirit/game/scripts/cards/SWSH8/Morgrem_177.py b/spirit/game/scripts/cards/SWSH8/Morgrem_177.py index d0d09a6..497e113 100644 --- a/spirit/game/scripts/cards/SWSH8/Morgrem_177.py +++ b/spirit/game/scripts/cards/SWSH8/Morgrem_177.py @@ -1,6 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def crushing_blow(ctx): + """Flip a coin. If heads, discard an Energy from the Defending Pokémon.""" + await ctx.deal_damage() + heads = await ctx.flip_coins(1, "Crushing Blow") + target = ctx.defender + if heads[0] and target is not None and not ctx.effects_blocked(target): + await ctx.discard_energy_from( + target, 1, prompt="Choose Energy to discard from the Defending Pokémon") + card = PokemonCardDef( guid="ee37cae5-f2b7-56af-832a-e3c637e6b0b0", key="SWSH8", @@ -29,7 +39,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, discard an Energy from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.DARKNESS: 2}, damage=40, - effect=unimplemented, + effect=crushing_blow, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Morpeko_109.py b/spirit/game/scripts/cards/SWSH8/Morpeko_109.py index 36e7347..e4f9bcb 100644 --- a/spirit/game/scripts/cards/SWSH8/Morpeko_109.py +++ b/spirit/game/scripts/cards/SWSH8/Morpeko_109.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import snipe_attack card = PokemonCardDef( guid="77b98c50-c758-55ae-be1e-9cc01a3db94f", @@ -20,9 +21,9 @@ card = PokemonCardDef( abilities=[ Attack( title="Targeted Spark", - game_text="This attack does 30 damage to 1 of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", + game_text="This attack does 30 damage to 1 of your opponent's Pokémon. (Don't apply Weakness and Resistance for Benched Pokémon.)", cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=snipe_attack(30, pool="any", count=1), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH8/Morpeko_179.py b/spirit/game/scripts/cards/SWSH8/Morpeko_179.py index 253d7e1..32cbabc 100644 --- a/spirit/game/scripts/cards/SWSH8/Morpeko_179.py +++ b/spirit/game/scripts/cards/SWSH8/Morpeko_179.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, damage_counters_on card = PokemonCardDef( guid="925a7ce1-8534-503b-a370-8c12ab0674bc", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.DARKNESS: 1}, damage=30, damage_operator="x", - effect=unimplemented, + effect=damage_per(damage_counters_on("self"), 30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Musharna_122.py b/spirit/game/scripts/cards/SWSH8/Musharna_122.py index 7dc91ef..ddd9a7f 100644 --- a/spirit/game/scripts/cards/SWSH8/Musharna_122.py +++ b/spirit/game/scripts/cards/SWSH8/Musharna_122.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import count_energy, damage_per +from spirit.game.card_effects.support_common import gust_then + + +async def _sleep_new_active(ctx, new_active): + await ctx.apply_special_condition(new_active, SpecialConditions.ASLEEP) + card = PokemonCardDef( guid="0fc279dc-d3ea-5d46-8f53-9a8dc4b809f6", @@ -24,7 +31,7 @@ card = PokemonCardDef( title="Sleep Inducer", game_text="Switch 1 of your opponent's Benched Pok\u00e9mon with their Active Pok\u00e9mon. The new Active Pok\u00e9mon is now Asleep.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=gust_then(_sleep_new_active), ), Attack( title="Psychic", @@ -32,7 +39,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=30, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("defender"), 30, base=30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Ninetales_31.py b/spirit/game/scripts/cards/SWSH8/Ninetales_31.py index b8c2425..0819bf8 100644 --- a/spirit/game/scripts/cards/SWSH8/Ninetales_31.py +++ b/spirit/game/scripts/cards/SWSH8/Ninetales_31.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import retreat_discount +from spirit.game.card_effects.pokemon import is_energy_card, energy_provides_type + + +def _has_fire_energy(pokemon, carrier): + if pokemon.owning_player_id != carrier.owning_player_id: + return False + return any( + energy_provides_type(e, PokemonTypes.FIRE.value) + for e in pokemon.children if is_energy_card(e) + ) + card = PokemonCardDef( guid="0c345c2e-ac3b-5a35-a963-e80bf2b4d5c7", @@ -22,7 +34,7 @@ card = PokemonCardDef( Ability( title="Byway of the Nine-Tailed Fox", game_text="The Retreat Cost of each of your Pok\u00e9mon that has any Fire Energy attached is ColorlessColorless less.", - effect=unimplemented, + passive=retreat_discount(2, target_pred=_has_fire_energy), ), Attack( title="Flame Tail", diff --git a/spirit/game/scripts/cards/SWSH8/Onix_138.py b/spirit/game/scripts/cards/SWSH8/Onix_138.py index 818a341..f4ef84c 100644 --- a/spirit/game/scripts/cards/SWSH8/Onix_138.py +++ b/spirit/game/scripts/cards/SWSH8/Onix_138.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import protect_next_turn card = PokemonCardDef( guid="92b4ad4c-694b-5b8d-b201-ea054836a7df", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, this Pok\u00e9mon takes 30 less damage from attacks (after applying Weakness and Resistance).", cost={PokemonTypes.COLORLESS: 2}, damage=30, - effect=unimplemented, + effect=protect_next_turn(reduce=30), ), Attack( title="Rock Throw", diff --git a/spirit/game/scripts/cards/SWSH8/Palossand_126.py b/spirit/game/scripts/cards/SWSH8/Palossand_126.py index 1c956dc..9e54212 100644 --- a/spirit/game/scripts/cards/SWSH8/Palossand_126.py +++ b/spirit/game/scripts/cards/SWSH8/Palossand_126.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_basic_pokemon + + +async def oppressing_sandstorm(ctx): + """If your opponent's Active Pokemon is a Basic Pokemon, it is Knocked Out.""" + defender = ctx.defender + if defender is not None and is_basic_pokemon(defender): + await ctx.knock_out(defender) + card = PokemonCardDef( guid="ae538704-d9f1-5dce-8896-481254a76f24", @@ -29,7 +38,7 @@ card = PokemonCardDef( title="Oppressing Sandstorm", game_text="If your opponent's Active Pok\u00e9mon is a Basic Pok\u00e9mon, it is Knocked Out.", cost={PokemonTypes.PSYCHIC: 3, PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=oppressing_sandstorm, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Pancham_149.py b/spirit/game/scripts/cards/SWSH8/Pancham_149.py index 4a9dbe0..5672e58 100644 --- a/spirit/game/scripts/cards/SWSH8/Pancham_149.py +++ b/spirit/game/scripts/cards/SWSH8/Pancham_149.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import energy_provides_type +from spirit.game.card_effects.support_common import search_attach_energy + + +def _is_darkness_energy(card): + return energy_provides_type(card, PokemonTypes.DARKNESS.value) + card = PokemonCardDef( guid="81f21ea7-ad34-5e5d-8d55-19b65fe7784c", @@ -20,9 +27,9 @@ card = PokemonCardDef( abilities=[ Attack( title="Raised Bad", - game_text="Search your deck for up to 2 Darkness Energy cards and attach them to this Pok\u00e9mon. Then, shuffle your deck.", + game_text="Search your deck for up to 2 Darkness Energy cards and attach them to this Pokémon. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_attach_energy(predicate=_is_darkness_energy, count=2, to_self=True), ), Attack( title="Smash Kick", @@ -30,4 +37,4 @@ card = PokemonCardDef( damage=30, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH8/Pangoro_174.py b/spirit/game/scripts/cards/SWSH8/Pangoro_174.py index 45cee4d..bfd7075 100644 --- a/spirit/game/scripts/cards/SWSH8/Pangoro_174.py +++ b/spirit/game/scripts/cards/SWSH8/Pangoro_174.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import mill_attack, discard_random_from_hand + + +async def shakedown(ctx): + await ctx.deal_damage() + await discard_random_from_hand(ctx, player_id=ctx.opponent_id, count=1) + card = PokemonCardDef( guid="cedf4ead-9a15-5fcd-8a48-54d60a596bde", @@ -24,14 +31,14 @@ card = PokemonCardDef( game_text="Discard the top card of your opponent's deck.", cost={PokemonTypes.DARKNESS: 2, PokemonTypes.COLORLESS: 1}, damage=90, - effect=unimplemented, + effect=mill_attack(1), ), Attack( title="Shakedown", game_text="Discard a random card from your opponent's hand.", cost={PokemonTypes.DARKNESS: 3, PokemonTypes.COLORLESS: 1}, damage=150, - effect=unimplemented, + effect=shakedown, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Panpour_68.py b/spirit/game/scripts/cards/SWSH8/Panpour_68.py index faacb5c..e58a0a9 100644 --- a/spirit/game/scripts/cards/SWSH8/Panpour_68.py +++ b/spirit/game/scripts/cards/SWSH8/Panpour_68.py @@ -1,6 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def pry(ctx): + """10 damage. Your opponent reveals their hand.""" + await ctx.deal_damage() + await ctx.reveal_hand(of_player=ctx.opponent_id) + + card = PokemonCardDef( guid="afbb8bd3-1c72-572e-8e73-452af8232207", key="SWSH8", @@ -23,7 +30,7 @@ card = PokemonCardDef( game_text="Your opponent reveals their hand.", cost={PokemonTypes.WATER: 1}, damage=10, - effect=unimplemented, + effect=pry, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Pansear_37.py b/spirit/game/scripts/cards/SWSH8/Pansear_37.py index b3f3052..977ebc4 100644 --- a/spirit/game/scripts/cards/SWSH8/Pansear_37.py +++ b/spirit/game/scripts/cards/SWSH8/Pansear_37.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_or_nothing card = PokemonCardDef( guid="8600526d-aead-560a-bd94-b8db29217f97", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Flip a coin. If tails, this attack does nothing.", cost={PokemonTypes.FIRE: 1}, damage=30, - effect=unimplemented, + effect=flip_or_nothing(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Persian_200.py b/spirit/game/scripts/cards/SWSH8/Persian_200.py index 81a59cf..015ab41 100644 --- a/spirit/game/scripts/cards/SWSH8/Persian_200.py +++ b/spirit/game/scripts/cards/SWSH8/Persian_200.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import draw_attack card = PokemonCardDef( guid="37e70d9a-29c4-5e70-8d90-4f115225550b", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Draw a card.", cost={PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=draw_attack(1), ), Attack( title="Bite", @@ -32,4 +33,4 @@ card = PokemonCardDef( damage=70, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH8/Plusle_89.py b/spirit/game/scripts/cards/SWSH8/Plusle_89.py index 875d242..4c0b539 100644 --- a/spirit/game/scripts/cards/SWSH8/Plusle_89.py +++ b/spirit/game/scripts/cards/SWSH8/Plusle_89.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, def_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if + + +def _minun_attacked_last_turn(ctx): + for pokemon in ctx.my_pokemon_in_play(): + card_def = def_for(pokemon.archetype_id) + if card_def and card_def.display_name == "Minun" and \ + ctx.attack_used_last_turn(entity=pokemon): + return True + return False + card = PokemonCardDef( guid="09d414a6-492a-5dc7-84b1-f72d403d8b26", @@ -24,7 +35,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_minun_attacked_last_turn, 100), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Primeape_134.py b/spirit/game/scripts/cards/SWSH8/Primeape_134.py index 6884627..5bb3234 100644 --- a/spirit/game/scripts/cards/SWSH8/Primeape_134.py +++ b/spirit/game/scripts/cards/SWSH8/Primeape_134.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, defender_is_v card = PokemonCardDef( guid="4cfd01fb-e5fc-5f97-984c-2ca4e10e4211", @@ -25,7 +26,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1}, damage=30, damage_operator="+", - effect=unimplemented, + effect=bonus_if(defender_is_v, 60), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Pyukumuku_77.py b/spirit/game/scripts/cards/SWSH8/Pyukumuku_77.py index 2e1cd24..dcac0ad 100644 --- a/spirit/game/scripts/cards/SWSH8/Pyukumuku_77.py +++ b/spirit/game/scripts/cards/SWSH8/Pyukumuku_77.py @@ -1,6 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def pitch_a_pyukumuku(ctx): + """From hand: you may reveal this and put it on the bottom of the deck, + then draw a card.""" + if not await ctx.ask_yes_no("Reveal this Pokémon and put it on the bottom of your deck?"): + return + await ctx.reveal_cards([ctx.source]) + if await ctx.put_on_bottom_of_deck(ctx.source): + await ctx.draw_cards(1) + + card = PokemonCardDef( guid="95ee77dc-dd7e-56af-b800-bcd502432ae7", key="SWSH8", @@ -21,7 +32,10 @@ card = PokemonCardDef( Ability( title="Pitch a Pyukumuku", game_text="Once during your turn, if this Pok\u00e9mon is in your hand, you may reveal it and put it on the bottom of your deck. If you do, draw a card. You can't use more than 1 Pitch a Pyukumuku Ability each turn.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + usable_from="hand", + shared_once_per_turn="Pitch a Pyukumuku", + effect=pitch_a_pyukumuku, ), Attack( title="Knuckle Punch", diff --git a/spirit/game/scripts/cards/SWSH8/Qwilfish_60.py b/spirit/game/scripts/cards/SWSH8/Qwilfish_60.py index b0f62b6..adeeb35 100644 --- a/spirit/game/scripts/cards/SWSH8/Qwilfish_60.py +++ b/spirit/game/scripts/cards/SWSH8/Qwilfish_60.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import draw_attack card = PokemonCardDef( guid="046a8ac0-e810-5102-9e23-afa546cbfd00", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Collect", game_text="Draw a card.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=draw_attack(1), ), Attack( title="Spike Sting", diff --git a/spirit/game/scripts/cards/SWSH8/RillaboomVMAX_23.py b/spirit/game/scripts/cards/SWSH8/RillaboomVMAX_23.py index 4810f25..b7372d2 100644 --- a/spirit/game/scripts/cards/SWSH8/RillaboomVMAX_23.py +++ b/spirit/game/scripts/cards/SWSH8/RillaboomVMAX_23.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import snipe_attack card = PokemonCardDef( guid="f84cab2e-275e-5666-a8cd-08a381634e38", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="This attack also does 40 damage to 2 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 3}, damage=180, - effect=unimplemented, + effect=snipe_attack(40, pool="bench", count=2, also_base=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/RillaboomV_22.py b/spirit/game/scripts/cards/SWSH8/RillaboomV_22.py index a57f402..a979e28 100644 --- a/spirit/game/scripts/cards/SWSH8/RillaboomV_22.py +++ b/spirit/game/scripts/cards/SWSH8/RillaboomV_22.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import heal_attack card = PokemonCardDef( guid="c4b208b1-047b-5332-91ba-c99f7503ccec", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Heal 30 damage from this Pok\u00e9mon.", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 2}, damage=60, - effect=unimplemented, + effect=heal_attack(30), ), Attack( title="Drum Rush", diff --git a/spirit/game/scripts/cards/SWSH8/Rookidee_219.py b/spirit/game/scripts/cards/SWSH8/Rookidee_219.py index c5630f8..c85eb5d 100644 --- a/spirit/game/scripts/cards/SWSH8/Rookidee_219.py +++ b/spirit/game/scripts/cards/SWSH8/Rookidee_219.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="37a4f138-60cb-52ea-8771-f254055862c9", @@ -25,7 +26,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 1}, damage=10, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=3, per_heads=10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Rotom_94.py b/spirit/game/scripts/cards/SWSH8/Rotom_94.py index 2e5a5a9..cc5db05 100644 --- a/spirit/game/scripts/cards/SWSH8/Rotom_94.py +++ b/spirit/game/scripts/cards/SWSH8/Rotom_94.py @@ -1,6 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def surprise_short(ctx): + """Discard all Pokémon Tools from all of your opponent's Pokémon.""" + tools = [t for t, p in ctx.tools_in_play() + if p.owning_player_id == ctx.opponent_id and not ctx.effects_blocked(p)] + if tools: + await ctx.discard_cards(tools) + + card = PokemonCardDef( guid="03a3bbcb-9d1c-532e-9244-f29fb483bd9c", key="SWSH8", @@ -22,7 +31,7 @@ card = PokemonCardDef( title="Surprise Short", game_text="Discard all Pok\u00e9mon Tools from all of your opponent's Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=surprise_short, ), Attack( title="Static Shock", diff --git a/spirit/game/scripts/cards/SWSH8/SandacondaV_252.py b/spirit/game/scripts/cards/SWSH8/SandacondaV_252.py index c248382..5351c31 100644 --- a/spirit/game/scripts/cards/SWSH8/SandacondaV_252.py +++ b/spirit/game/scripts/cards/SWSH8/SandacondaV_252.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.passives_common import takes_less_passive +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -21,7 +22,7 @@ card = PokemonCardDef( Ability( title="Wall of Sand", game_text="This Pok\u00e9mon takes 30 less damage from attacks (after applying Weakness and Resistance).", - effect=unimplemented, + passive=takes_less_passive(30), ), Attack( title="Land Crush", diff --git a/spirit/game/scripts/cards/SWSH8/Sandshrew_131.py b/spirit/game/scripts/cards/SWSH8/Sandshrew_131.py index b06ea5f..76c5133 100644 --- a/spirit/game/scripts/cards/SWSH8/Sandshrew_131.py +++ b/spirit/game/scripts/cards/SWSH8/Sandshrew_131.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_bench, has_attack_titled + + +async def dig_it_up(ctx): + """Look at the top card of your deck. You may discard that card.""" + top = ctx.deck_top(1) + if not top: + return + card = top[0] + idx = await ctx.present_card_choice( + card, "Discard this card?", ["Discard", "Keep on top"]) + if idx == 0: + await ctx.discard_cards([card]) + card = PokemonCardDef( guid="abd22956-53b9-5d80-a68a-71fab1b8a078", @@ -22,7 +36,7 @@ card = PokemonCardDef( title="Dig It Up", game_text="Look at the top card of your deck. You may discard that card.", cost={PokemonTypes.FIGHTING: 1}, - effect=unimplemented, + effect=dig_it_up, ), Attack( title="Let's All Rollout", @@ -30,7 +44,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=20, damage_operator="x", - effect=unimplemented, + effect=damage_per(count_bench("mine", has_attack_titled("Let's All Rollout")), 20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Sandslash_132.py b/spirit/game/scripts/cards/SWSH8/Sandslash_132.py index 4003e62..6b82ceb 100644 --- a/spirit/game/scripts/cards/SWSH8/Sandslash_132.py +++ b/spirit/game/scripts/cards/SWSH8/Sandslash_132.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import recover_from_discard card = PokemonCardDef( guid="70ac0459-f78b-5f2a-8e3d-fdbb46b7a1bc", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Put a card from your discard pile into your hand.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 1}, damage=60, - effect=unimplemented, + effect=recover_from_discard(count=1, minimum=1, reveal=False, to="hand"), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Schoolboy_238.py b/spirit/game/scripts/cards/SWSH8/Schoolboy_238.py index ac2b228..711177d 100644 --- a/spirit/game/scripts/cards/SWSH8/Schoolboy_238.py +++ b/spirit/game/scripts/cards/SWSH8/Schoolboy_238.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.attacks_common import count_prizes_remaining + +_opponent_prizes_remaining = count_prizes_remaining("opponent") + + +async def schoolboy(ctx): + await ctx.draw_cards(2) + if _opponent_prizes_remaining(ctx) in (1, 3, 5): + await ctx.draw_cards(2) + card = SupporterCardDef( guid="16e7d311-6731-5028-9de4-6421cad76f49", @@ -11,5 +21,5 @@ card = SupporterCardDef( collector_number=238, set_code="SWSH8", rarity=Rarities.Uncommon, - effect=unimplemented + effect=schoolboy ) diff --git a/spirit/game/scripts/cards/SWSH8/Schoolboy_261.py b/spirit/game/scripts/cards/SWSH8/Schoolboy_261.py index 3b00ae2..3859cb9 100644 --- a/spirit/game/scripts/cards/SWSH8/Schoolboy_261.py +++ b/spirit/game/scripts/cards/SWSH8/Schoolboy_261.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.attacks_common import count_prizes_remaining + +_opponent_prizes_remaining = count_prizes_remaining("opponent") + + +async def schoolboy(ctx): + await ctx.draw_cards(2) + if _opponent_prizes_remaining(ctx) in (1, 3, 5): + await ctx.draw_cards(2) + card = SupporterCardDef( guid="2f57fa4f-123e-5e0c-bf3f-5de9bcdc2f53", @@ -11,5 +21,5 @@ card = SupporterCardDef( collector_number=261, set_code="SWSH8", rarity=Rarities.RareUltra, - effect=unimplemented + effect=schoolboy ) diff --git a/spirit/game/scripts/cards/SWSH8/Schoolboy_276.py b/spirit/game/scripts/cards/SWSH8/Schoolboy_276.py index e349e15..36fd886 100644 --- a/spirit/game/scripts/cards/SWSH8/Schoolboy_276.py +++ b/spirit/game/scripts/cards/SWSH8/Schoolboy_276.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.attacks_common import count_prizes_remaining + +_opponent_prizes_remaining = count_prizes_remaining("opponent") + + +async def schoolboy(ctx): + await ctx.draw_cards(2) + if _opponent_prizes_remaining(ctx) in (1, 3, 5): + await ctx.draw_cards(2) + card = SupporterCardDef( guid="136b91ff-ef5b-5a4a-a133-8f45dfb8251b", @@ -11,5 +21,5 @@ card = SupporterCardDef( collector_number=276, set_code="SWSH8", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=schoolboy ) diff --git a/spirit/game/scripts/cards/SWSH8/Schoolgirl_239.py b/spirit/game/scripts/cards/SWSH8/Schoolgirl_239.py index 7b1bf46..1448dfc 100644 --- a/spirit/game/scripts/cards/SWSH8/Schoolgirl_239.py +++ b/spirit/game/scripts/cards/SWSH8/Schoolgirl_239.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.attacks_common import count_prizes_remaining + +_opponent_prizes_remaining = count_prizes_remaining("opponent") + + +async def schoolgirl(ctx): + await ctx.draw_cards(2) + if _opponent_prizes_remaining(ctx) in (2, 4, 6): + await ctx.draw_cards(2) + card = SupporterCardDef( guid="7ab315fe-71a8-581c-bc20-93ce8f972bfb", @@ -11,5 +21,5 @@ card = SupporterCardDef( collector_number=239, set_code="SWSH8", rarity=Rarities.Uncommon, - effect=unimplemented + effect=schoolgirl ) diff --git a/spirit/game/scripts/cards/SWSH8/Schoolgirl_262.py b/spirit/game/scripts/cards/SWSH8/Schoolgirl_262.py index e422104..59cbbd8 100644 --- a/spirit/game/scripts/cards/SWSH8/Schoolgirl_262.py +++ b/spirit/game/scripts/cards/SWSH8/Schoolgirl_262.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.attacks_common import count_prizes_remaining + +_opponent_prizes_remaining = count_prizes_remaining("opponent") + + +async def schoolgirl(ctx): + await ctx.draw_cards(2) + if _opponent_prizes_remaining(ctx) in (2, 4, 6): + await ctx.draw_cards(2) + card = SupporterCardDef( guid="e4635c23-a848-5c8b-9f6d-2fdeda500876", @@ -11,5 +21,5 @@ card = SupporterCardDef( collector_number=262, set_code="SWSH8", rarity=Rarities.RareUltra, - effect=unimplemented + effect=schoolgirl ) diff --git a/spirit/game/scripts/cards/SWSH8/Schoolgirl_277.py b/spirit/game/scripts/cards/SWSH8/Schoolgirl_277.py index 999884a..9d17154 100644 --- a/spirit/game/scripts/cards/SWSH8/Schoolgirl_277.py +++ b/spirit/game/scripts/cards/SWSH8/Schoolgirl_277.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.attacks_common import count_prizes_remaining + +_opponent_prizes_remaining = count_prizes_remaining("opponent") + + +async def schoolgirl(ctx): + await ctx.draw_cards(2) + if _opponent_prizes_remaining(ctx) in (2, 4, 6): + await ctx.draw_cards(2) + card = SupporterCardDef( guid="965bad7d-170a-5af0-b7f4-135f438752a0", @@ -11,5 +21,5 @@ card = SupporterCardDef( collector_number=277, set_code="SWSH8", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=schoolgirl ) diff --git a/spirit/game/scripts/cards/SWSH8/Sewaddle_9.py b/spirit/game/scripts/cards/SWSH8/Sewaddle_9.py index 62e1a01..f041e73 100644 --- a/spirit/game/scripts/cards/SWSH8/Sewaddle_9.py +++ b/spirit/game/scripts/cards/SWSH8/Sewaddle_9.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import energy_provides_type + + +async def grass_munch(ctx): + """10 damage, then discard a Grass Energy from the opponent's Active.""" + await ctx.deal_damage() + target = ctx.opponent_active() + if target is None or ctx.effects_blocked(target): + return + await ctx.discard_energy_from( + target, 1, predicate=lambda c: energy_provides_type(c, PokemonTypes.GRASS.value), + prompt="Choose a Grass Energy to discard from the Defending Pokémon") + card = PokemonCardDef( guid="815eebbc-d71a-53a9-bc69-78595e5d0082", @@ -23,7 +36,7 @@ card = PokemonCardDef( game_text="Discard a Grass Energy from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.GRASS: 1}, damage=10, - effect=unimplemented, + effect=grass_munch, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Shauna_240.py b/spirit/game/scripts/cards/SWSH8/Shauna_240.py index b64fd88..6acd10c 100644 --- a/spirit/game/scripts/cards/SWSH8/Shauna_240.py +++ b/spirit/game/scripts/cards/SWSH8/Shauna_240.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.support_common import shuffle_hand_into_deck_draw card = SupporterCardDef( guid="d0cb09b9-79ce-5b4f-af92-78a2e371c8b1", @@ -11,5 +12,5 @@ card = SupporterCardDef( collector_number=240, set_code="SWSH8", rarity=Rarities.Uncommon, - effect=unimplemented + effect=shuffle_hand_into_deck_draw(5) ) diff --git a/spirit/game/scripts/cards/SWSH8/Shauna_263.py b/spirit/game/scripts/cards/SWSH8/Shauna_263.py index e97f057..276f2d2 100644 --- a/spirit/game/scripts/cards/SWSH8/Shauna_263.py +++ b/spirit/game/scripts/cards/SWSH8/Shauna_263.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.support_common import shuffle_hand_into_deck_draw card = SupporterCardDef( guid="8f400edd-55af-5aa8-bede-c92e7244312b", @@ -11,5 +12,5 @@ card = SupporterCardDef( collector_number=263, set_code="SWSH8", rarity=Rarities.RareUltra, - effect=unimplemented + effect=shuffle_hand_into_deck_draw(5) ) diff --git a/spirit/game/scripts/cards/SWSH8/Shauna_278.py b/spirit/game/scripts/cards/SWSH8/Shauna_278.py index 5162c0b..5c01591 100644 --- a/spirit/game/scripts/cards/SWSH8/Shauna_278.py +++ b/spirit/game/scripts/cards/SWSH8/Shauna_278.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.support_common import shuffle_hand_into_deck_draw card = SupporterCardDef( guid="bef5ea69-139c-59e3-b17a-767aa89ff165", @@ -11,5 +12,5 @@ card = SupporterCardDef( collector_number=278, set_code="SWSH8", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=shuffle_hand_into_deck_draw(5) ) diff --git a/spirit/game/scripts/cards/SWSH8/Sidney_241.py b/spirit/game/scripts/cards/SWSH8/Sidney_241.py index 20f2efb..b1948d8 100644 --- a/spirit/game/scripts/cards/SWSH8/Sidney_241.py +++ b/spirit/game/scripts/cards/SWSH8/Sidney_241.py @@ -1,5 +1,25 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import SupporterCardDef +from spirit.game.attributes import Rarities, AttrID, TrainerType +from spirit.game.session.effects import is_special_energy + + +def _sidney_predicate(card): + trainer_type = card.get_attribute(AttrID.TRAINER_TYPE) + return trainer_type in (TrainerType.POKEMON_TOOL.value, TrainerType.STADIUM.value) \ + or is_special_energy(card) + + +async def sidney(ctx): + """Opponent reveals their hand; discard up to 2 Tool/Special Energy/Stadium cards from it.""" + hand = ctx.hand(ctx.opponent_id) + matches = [c for c in hand if _sidney_predicate(c)] + picks = await ctx.choose_cards( + matches, 2, minimum=0, + prompt="Choose up to 2 cards to discard from your opponent's hand.", + display_cards=hand, + ) + await ctx.discard_cards(picks) + card = SupporterCardDef( guid="03af20bf-db82-5124-8acb-4a23f6c6a7c5", @@ -11,5 +31,5 @@ card = SupporterCardDef( collector_number=241, set_code="SWSH8", rarity=Rarities.Uncommon, - effect=unimplemented + effect=sidney ) diff --git a/spirit/game/scripts/cards/SWSH8/Sidney_264.py b/spirit/game/scripts/cards/SWSH8/Sidney_264.py index 4af1579..922858d 100644 --- a/spirit/game/scripts/cards/SWSH8/Sidney_264.py +++ b/spirit/game/scripts/cards/SWSH8/Sidney_264.py @@ -1,5 +1,25 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import SupporterCardDef +from spirit.game.attributes import Rarities, AttrID, TrainerType +from spirit.game.session.effects import is_special_energy + + +def _sidney_predicate(card): + trainer_type = card.get_attribute(AttrID.TRAINER_TYPE) + return trainer_type in (TrainerType.POKEMON_TOOL.value, TrainerType.STADIUM.value) \ + or is_special_energy(card) + + +async def sidney(ctx): + """Opponent reveals their hand; discard up to 2 Tool/Special Energy/Stadium cards from it.""" + hand = ctx.hand(ctx.opponent_id) + matches = [c for c in hand if _sidney_predicate(c)] + picks = await ctx.choose_cards( + matches, 2, minimum=0, + prompt="Choose up to 2 cards to discard from your opponent's hand.", + display_cards=hand, + ) + await ctx.discard_cards(picks) + card = SupporterCardDef( guid="1d478e58-44e8-5488-8abc-9b15424b831e", @@ -11,5 +31,5 @@ card = SupporterCardDef( collector_number=264, set_code="SWSH8", rarity=Rarities.RareUltra, - effect=unimplemented + effect=sidney ) diff --git a/spirit/game/scripts/cards/SWSH8/Sidney_279.py b/spirit/game/scripts/cards/SWSH8/Sidney_279.py index a1e5927..a44ac90 100644 --- a/spirit/game/scripts/cards/SWSH8/Sidney_279.py +++ b/spirit/game/scripts/cards/SWSH8/Sidney_279.py @@ -1,5 +1,25 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import SupporterCardDef +from spirit.game.attributes import Rarities, AttrID, TrainerType +from spirit.game.session.effects import is_special_energy + + +def _sidney_predicate(card): + trainer_type = card.get_attribute(AttrID.TRAINER_TYPE) + return trainer_type in (TrainerType.POKEMON_TOOL.value, TrainerType.STADIUM.value) \ + or is_special_energy(card) + + +async def sidney(ctx): + """Opponent reveals their hand; discard up to 2 Tool/Special Energy/Stadium cards from it.""" + hand = ctx.hand(ctx.opponent_id) + matches = [c for c in hand if _sidney_predicate(c)] + picks = await ctx.choose_cards( + matches, 2, minimum=0, + prompt="Choose up to 2 cards to discard from your opponent's hand.", + display_cards=hand, + ) + await ctx.discard_cards(picks) + card = SupporterCardDef( guid="22c07183-8f13-587f-9035-7f5ce2fd0a9b", @@ -11,5 +31,5 @@ card = SupporterCardDef( collector_number=279, set_code="SWSH8", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=sidney ) diff --git a/spirit/game/scripts/cards/SWSH8/Sigilyph_123.py b/spirit/game/scripts/cards/SWSH8/Sigilyph_123.py index e6b9aac..8a3fe38 100644 --- a/spirit/game/scripts/cards/SWSH8/Sigilyph_123.py +++ b/spirit/game/scripts/cards/SWSH8/Sigilyph_123.py @@ -1,6 +1,30 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def joust(ctx): + defender = ctx.opponent_active() + if defender is not None and not ctx.effects_blocked(defender): + tools = [tool for tool, pokemon in ctx.tools_in_play() if pokemon is defender] + if tools: + await ctx.discard_cards(tools) + await ctx.deal_damage() + + +async def reflect_energy(ctx): + await ctx.deal_damage() + bench = ctx.my_bench() + energies = ctx.attached_energies(ctx.attacker) + if energies and bench: + picked = await ctx.choose_cards(energies, 1, prompt="Choose an Energy to move") + if picked: + target = await ctx.choose_pokemon( + bench, "Choose a Benched Pokémon to move the Energy to" + ) + if target is not None: + await ctx.move_energy(picked[0], target) + + card = PokemonCardDef( guid="b942edb8-af0e-58a1-b735-fd6f3a2e8361", key="SWSH8", @@ -24,14 +48,14 @@ card = PokemonCardDef( game_text="Before doing damage, discard all Pok\u00e9mon Tools from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, damage=20, - effect=unimplemented, + effect=joust, ), Attack( title="Reflect Energy", game_text="Move an Energy from this Pok\u00e9mon to 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=60, - effect=unimplemented, + effect=reflect_energy, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Simipour_69.py b/spirit/game/scripts/cards/SWSH8/Simipour_69.py index 94a392d..35d2bc0 100644 --- a/spirit/game/scripts/cards/SWSH8/Simipour_69.py +++ b/spirit/game/scripts/cards/SWSH8/Simipour_69.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.session.effects import is_supporter_card + + +async def circus_soaking(ctx): + """Opponent reveals their hand; 60 damage for each Supporter card there.""" + hand = await ctx.reveal_hand(of_player=ctx.opponent_id) + count = sum(1 for c in hand if is_supporter_card(c)) + if count: + await ctx.deal_damage(60 * count) card = PokemonCardDef( guid="1ee72771-e075-5f79-9191-3bf8a805c6b4", @@ -24,7 +34,7 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Asleep.", cost={PokemonTypes.WATER: 1}, damage=20, - effect=unimplemented, + effect=condition_attack(SpecialConditions.ASLEEP), ), Attack( title="Circus Soaking", @@ -32,7 +42,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=60, damage_operator="x", - effect=unimplemented, + effect=circus_soaking, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Simisage_8.py b/spirit/game/scripts/cards/SWSH8/Simisage_8.py index 6195eb5..3665117 100644 --- a/spirit/game/scripts/cards/SWSH8/Simisage_8.py +++ b/spirit/game/scripts/cards/SWSH8/Simisage_8.py @@ -1,6 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def return_effect(ctx): + """30 damage; you may draw cards until you have 6 cards in your hand.""" + await ctx.deal_damage() + if await ctx.ask_yes_no("Draw cards until you have 6 cards in your hand?"): + await ctx.draw_until(6) + + card = PokemonCardDef( guid="0ff8b3ec-a091-5430-a66e-1a30ff97cbb7", key="SWSH8", @@ -24,7 +32,7 @@ card = PokemonCardDef( game_text="You may draw cards until you have 6 cards in your hand.", cost={PokemonTypes.GRASS: 1}, damage=30, - effect=unimplemented, + effect=return_effect, ), Attack( title="Whip Smash", diff --git a/spirit/game/scripts/cards/SWSH8/Simisear_38.py b/spirit/game/scripts/cards/SWSH8/Simisear_38.py index d86f8b0..96d612f 100644 --- a/spirit/game/scripts/cards/SWSH8/Simisear_38.py +++ b/spirit/game/scripts/cards/SWSH8/Simisear_38.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.trainers import is_basic_energy_card + + +async def fling_fire(ctx): + discarded = await ctx.discard_from_hand( + 2, minimum=0, predicate=is_basic_energy_card, + prompt="Discard up to 2 basic Energy cards", + ) + await ctx.deal_damage(60 * len(discarded)) + card = PokemonCardDef( guid="fe6d1b3f-13c8-59d3-9701-e56aa2933627", @@ -30,7 +40,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 1}, damage=60, damage_operator="x", - effect=unimplemented, + effect=fling_fire, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Sizzlipede_47.py b/spirit/game/scripts/cards/SWSH8/Sizzlipede_47.py index 728884c..3d6653b 100644 --- a/spirit/game/scripts/cards/SWSH8/Sizzlipede_47.py +++ b/spirit/game/scripts/cards/SWSH8/Sizzlipede_47.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="3f303c07-c70c-5a30-bcef-0460e1b07aef", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Singe", game_text="Your opponent's Active Pok\u00e9mon is now Burned.", cost={PokemonTypes.FIRE: 1}, - effect=unimplemented, + effect=condition_attack(SpecialConditions.BURNED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Skarmory_182.py b/spirit/game/scripts/cards/SWSH8/Skarmory_182.py index c5faad7..a2ee056 100644 --- a/spirit/game/scripts/cards/SWSH8/Skarmory_182.py +++ b/spirit/game/scripts/cards/SWSH8/Skarmory_182.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import protect_next_turn card = PokemonCardDef( guid="49afb0a2-d30b-59d2-a774-47f283cb8b7f", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, this Pok\u00e9mon takes 30 less damage from attacks (after applying Weakness and Resistance).", cost={PokemonTypes.COLORLESS: 2}, damage=30, - effect=unimplemented, + effect=protect_next_turn(reduce=30), ), Attack( title="Slicing Blade", diff --git a/spirit/game/scripts/cards/SWSH8/SkatersPark_242.py b/spirit/game/scripts/cards/SWSH8/SkatersPark_242.py index 7be82d5..a489dcd 100644 --- a/spirit/game/scripts/cards/SWSH8/SkatersPark_242.py +++ b/spirit/game/scripts/cards/SWSH8/SkatersPark_242.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import StadiumCardDef, unimplemented +from spirit.game.card_effects.trainers import SkatersParkPassive +from spirit.game.data_utils import StadiumCardDef from spirit.game.attributes import Rarities card = StadiumCardDef( @@ -11,5 +12,5 @@ card = StadiumCardDef( collector_number=242, set_code="SWSH8", rarity=Rarities.Uncommon, - effect=unimplemented + passive=SkatersParkPassive(), ) diff --git a/spirit/game/scripts/cards/SWSH8/Skitty_210.py b/spirit/game/scripts/cards/SWSH8/Skitty_210.py index 6b65619..a80c863 100644 --- a/spirit/game/scripts/cards/SWSH8/Skitty_210.py +++ b/spirit/game/scripts/cards/SWSH8/Skitty_210.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_or_nothing card = PokemonCardDef( guid="8097052f-7df4-51ce-be1b-8cdfb7393c55", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Flip a coin. If tails, this attack does nothing.", cost={PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=flip_or_nothing(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Sliggoo_196.py b/spirit/game/scripts/cards/SWSH8/Sliggoo_196.py index a7345de..2a58d8f 100644 --- a/spirit/game/scripts/cards/SWSH8/Sliggoo_196.py +++ b/spirit/game/scripts/cards/SWSH8/Sliggoo_196.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="f934a0f6-8152-53a6-883a-1e95516f50c5", @@ -28,7 +29,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.WATER: 1, PokemonTypes.PSYCHIC: 1}, damage=50, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Smeargle_209.py b/spirit/game/scripts/cards/SWSH8/Smeargle_209.py index aa6e444..ea32b04 100644 --- a/spirit/game/scripts/cards/SWSH8/Smeargle_209.py +++ b/spirit/game/scripts/cards/SWSH8/Smeargle_209.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, subtypes_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import recover_from_discard, requires_discard +from spirit.game.session.effects import is_trainer_card + + +def _is_fusion_strike_trainer(card): + return is_trainer_card(card) and "Fusion Strike" in subtypes_for(card.archetype_id) + + +sketching_trash = recover_from_discard( + predicate=_is_fusion_strike_trainer, count=2, minimum=1, reveal=False, to="hand", + prompt="Choose up to 2 Fusion Strike Trainer cards to put into your hand.", +) card = PokemonCardDef( guid="5401114c-0180-51b0-a83b-8db481b1db3e", @@ -22,7 +34,8 @@ card = PokemonCardDef( title="Sketching Trash", game_text="Put up to 2 Fusion Strike Trainer cards from your discard pile into your hand.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=sketching_trash, + condition=requires_discard(_is_fusion_strike_trainer), ), Attack( title="Tail Whap", diff --git a/spirit/game/scripts/cards/SWSH8/Snom_84.py b/spirit/game/scripts/cards/SWSH8/Snom_84.py index 3274bf5..907e4c8 100644 --- a/spirit/game/scripts/cards/SWSH8/Snom_84.py +++ b/spirit/game/scripts/cards/SWSH8/Snom_84.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.card_effects.pokemon import is_energy_card, energy_provides_type + + +def _is_water_energy(card): + return is_energy_card(card) and energy_provides_type(card, PokemonTypes.WATER.value) + + +find_ice = search_to_hand( + predicate=_is_water_energy, count=2, reveal=True, + prompt="Choose up to 2 Water Energy cards to put into your hand.", +) card = PokemonCardDef( guid="30d21668-3a17-5b2d-96ef-d70f332f1446", @@ -22,7 +34,7 @@ card = PokemonCardDef( title="Find Ice", game_text="Search your deck for up to 2 Water Energy cards, reveal them, and put them into your hand. Then, shuffle your deck.", cost={PokemonTypes.WATER: 1}, - effect=unimplemented, + effect=find_ice, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/SpongyGloves_243.py b/spirit/game/scripts/cards/SWSH8/SpongyGloves_243.py index a611697..059a251 100644 --- a/spirit/game/scripts/cards/SWSH8/SpongyGloves_243.py +++ b/spirit/game/scripts/cards/SWSH8/SpongyGloves_243.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import PokemonToolCardDef +from spirit.game.attributes import Rarities, PokemonTypes +from spirit.game.card_effects.passives_common import typed_damage_boost_tool card = PokemonToolCardDef( guid="3cb5b10a-d183-5ede-938f-21e9195bfe0c", @@ -11,5 +12,5 @@ card = PokemonToolCardDef( collector_number=243, set_code="SWSH8", rarity=Rarities.Uncommon, - effect=unimplemented + passive=typed_damage_boost_tool(PokemonTypes.WATER, 30), ) diff --git a/spirit/game/scripts/cards/SWSH8/Stantler_208.py b/spirit/game/scripts/cards/SWSH8/Stantler_208.py index 086fb1b..02e63f8 100644 --- a/spirit/game/scripts/cards/SWSH8/Stantler_208.py +++ b/spirit/game/scripts/cards/SWSH8/Stantler_208.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy card = PokemonCardDef( guid="41f849e4-8b61-5828-8a38-7deb9a6df20e", @@ -29,7 +30,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=30, damage_operator="x", - effect=unimplemented, + effect=damage_per(count_energy("defender"), 30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Starmie_53.py b/spirit/game/scripts/cards/SWSH8/Starmie_53.py index 5b35398..794e958 100644 --- a/spirit/game/scripts/cards/SWSH8/Starmie_53.py +++ b/spirit/game/scripts/cards/SWSH8/Starmie_53.py @@ -1,5 +1,33 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID, CardType + + +def _is_water_energy_card(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return card.get_attribute(AttrID.CARD_TYPE) == CardType.ENERGY.value \ + and PokemonTypes.WATER.value in types + + +async def multishot_star(ctx): + water_energies = [e for e in ctx.attached_energies(ctx.attacker) + if _is_water_energy_card(e)] + if not water_energies: + return + discarded = await ctx.choose_cards( + water_energies, len(water_energies), minimum=0, + prompt="Discard any amount of Water Energy from this Pokémon", + ) + if not discarded: + return + await ctx.discard_cards(discarded) + targets = ctx.opponent_pokemon_in_play() + if not targets: + return + for _ in discarded: + target = await ctx.choose_pokemon(targets, "Choose 1 of your opponent's Pokémon") + if target is not None: + await ctx.deal_damage(30, target=target, apply_modifiers=False) + card = PokemonCardDef( guid="70fbca44-48d0-572a-adf0-0221f686394c", @@ -23,7 +51,7 @@ card = PokemonCardDef( title="Multishot Star", game_text="Discard any amount of Water Energy from this Pok\u00e9mon. Then, for each Energy card you discarded in this way, choose 1 of your opponent's Pok\u00e9mon and do 30 damage to it. (You can choose the same Pok\u00e9mon more than once.) This damage isn't affected by Weakness or Resistance.", cost={PokemonTypes.WATER: 1}, - effect=unimplemented, + effect=multishot_star, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Staryu_52.py b/spirit/game/scripts/cards/SWSH8/Staryu_52.py index 0f8448c..9fd3233 100644 --- a/spirit/game/scripts/cards/SWSH8/Staryu_52.py +++ b/spirit/game/scripts/cards/SWSH8/Staryu_52.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import energy_provides_type + + +async def soak_in_water(ctx): + """Attach a Water Energy card from your hand to this Pokemon.""" + water = [c for c in ctx.hand() if energy_provides_type(c, PokemonTypes.WATER.value)] + if not water: + return + picks = await ctx.choose_cards(water, 1, prompt="Choose a Water Energy card to attach.") + for card in picks: + await ctx.attach_energy(card, ctx.source) + card = PokemonCardDef( guid="291c4d11-37f9-5734-8245-3492ebc97898", @@ -22,7 +34,7 @@ card = PokemonCardDef( title="Soak in Water", game_text="Attach a Water Energy card from your hand to this Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=soak_in_water, ), Attack( title="Spinning Attack", diff --git a/spirit/game/scripts/cards/SWSH8/Steelix_139.py b/spirit/game/scripts/cards/SWSH8/Steelix_139.py index af54d13..3bfcdef 100644 --- a/spirit/game/scripts/cards/SWSH8/Steelix_139.py +++ b/spirit/game/scripts/cards/SWSH8/Steelix_139.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, damage_counters_on, spread_damage card = PokemonCardDef( guid="61d4af0a-8440-51b4-bc22-8e40bac84462", @@ -25,14 +26,14 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=20, damage_operator="x", - effect=unimplemented, + effect=damage_per(damage_counters_on("self"), 20), ), Attack( title="Earthquake", game_text="This attack also does 30 damage to each of your Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=180, - effect=unimplemented, + effect=spread_damage(30, side="mine", also_base=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Swadloon_10.py b/spirit/game/scripts/cards/SWSH8/Swadloon_10.py index 0127d52..eec31cf 100644 --- a/spirit/game/scripts/cards/SWSH8/Swadloon_10.py +++ b/spirit/game/scripts/cards/SWSH8/Swadloon_10.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus card = PokemonCardDef( guid="af2b2a64-c611-51ad-bcef-019859fa4881", @@ -25,7 +26,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1}, damage=10, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(20), ), Attack( title="Seed Bomb", diff --git a/spirit/game/scripts/cards/SWSH8/Swampert_64.py b/spirit/game/scripts/cards/SWSH8/Swampert_64.py index f678f3b..5081e7e 100644 --- a/spirit/game/scripts/cards/SWSH8/Swampert_64.py +++ b/spirit/game/scripts/cards/SWSH8/Swampert_64.py @@ -1,5 +1,35 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import energy_provides_type +from spirit.game.card_effects.attacks_common import spread_damage +from spirit.game.card_effects.support_common import requires_hand + + +def _is_water_or_fighting_energy(card): + return (energy_provides_type(card, PokemonTypes.WATER.value) + or energy_provides_type(card, PokemonTypes.FIGHTING.value)) + + +async def muddy_maker(ctx): + """Once per turn, you may attach a Water or Fighting Energy card from + your hand to 1 of your Pokemon.""" + if not await ctx.ask_yes_no( + "Attach a Water Energy card or a Fighting Energy card from your hand to 1 of your Pokémon?" + ): + return + energies = [c for c in ctx.hand() if _is_water_or_fighting_energy(c)] + if not energies: + return + picks = await ctx.choose_cards( + energies, 1, prompt="Choose a Water or Fighting Energy card to attach.") + if not picks: + return + target = await ctx.choose_pokemon( + ctx.my_pokemon_in_play(), "Choose 1 of your Pokémon to attach the Energy to.") + if target is None: + return + await ctx.attach_energy(picks[0], target) + card = PokemonCardDef( guid="1a6726ed-c32d-586d-bc40-3719f4603ea9", @@ -22,14 +52,16 @@ card = PokemonCardDef( Ability( title="Muddy Maker", game_text="Once during your turn, you may attach a Water Energy card or a Fighting Energy card from your hand to 1 of your Pok\u00e9mon.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=requires_hand(_is_water_or_fighting_energy), + effect=muddy_maker, ), Attack( title="Earthquake", game_text="This attack also does 20 damage to each of your Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=180, - effect=unimplemented, + effect=spread_damage(20, side="mine", also_base=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Togedemaru_187.py b/spirit/game/scripts/cards/SWSH8/Togedemaru_187.py index 3ef73b0..340c7d0 100644 --- a/spirit/game/scripts/cards/SWSH8/Togedemaru_187.py +++ b/spirit/game/scripts/cards/SWSH8/Togedemaru_187.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_bench, has_attack_titled card = PokemonCardDef( guid="ccfeea18-dac8-5136-b5b6-2941bee9be3d", @@ -25,7 +26,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=20, damage_operator="x", - effect=unimplemented, + effect=damage_per(count_bench("mine", has_attack_titled("Let's All Rollout")), 20), ), Attack( title="Rolling Attack", diff --git a/spirit/game/scripts/cards/SWSH8/Toxel_106.py b/spirit/game/scripts/cards/SWSH8/Toxel_106.py index 6bb91b6..1f4af84 100644 --- a/spirit/game/scripts/cards/SWSH8/Toxel_106.py +++ b/spirit/game/scripts/cards/SWSH8/Toxel_106.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import debuff_defender_attacks card = PokemonCardDef( guid="7db56b84-0fa9-5f1f-89ca-cdefd05004e9", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Growl", game_text="During your opponent's next turn, the Defending Pok\u00e9mon's attacks do 30 less damage (before applying Weakness and Resistance).", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=debuff_defender_attacks(30), ), Attack( title="Tiny Bolt", diff --git a/spirit/game/scripts/cards/SWSH8/Toxicroak_166.py b/spirit/game/scripts/cards/SWSH8/Toxicroak_166.py index 2e172a4..c857d38 100644 --- a/spirit/game/scripts/cards/SWSH8/Toxicroak_166.py +++ b/spirit/game/scripts/cards/SWSH8/Toxicroak_166.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="c9d10739-496e-5726-81dc-cd5094fc02de", @@ -23,7 +24,7 @@ card = PokemonCardDef( title="Severe Poison", game_text="Your opponent's Active Pok\u00e9mon is now Poisoned. During Pok\u00e9mon Checkup, put 4 damage counters on that Pok\u00e9mon instead of 1.", cost={PokemonTypes.DARKNESS: 1}, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED, counters=4), ), Attack( title="Magnum Punch", diff --git a/spirit/game/scripts/cards/SWSH8/Toxtricity_107.py b/spirit/game/scripts/cards/SWSH8/Toxtricity_107.py index 5abd8e3..09eb7a6 100644 --- a/spirit/game/scripts/cards/SWSH8/Toxtricity_107.py +++ b/spirit/game/scripts/cards/SWSH8/Toxtricity_107.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions + + +async def punk_shock(ctx): + """70 damage. You may Paralyze the opponent's Active; if you do, this Pokemon also does 70 to itself.""" + await ctx.deal_damage() + if await ctx.ask_yes_no( + "Make your opponent's Active Pokémon Paralyzed? If you do, this " + "Pokémon also does 70 damage to itself." + ): + await ctx.apply_special_condition(ctx.defender, SpecialConditions.PARALYZED) + await ctx.deal_damage(70, target=ctx.attacker, apply_modifiers=False) + card = PokemonCardDef( guid="1b65c547-a2a7-5e43-b743-681c0bb39b81", @@ -24,7 +36,7 @@ card = PokemonCardDef( game_text="You may choose to make your opponent's Active Pok\u00e9mon Paralyzed. If you do, this Pok\u00e9mon also does 70 damage to itself.", cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 1}, damage=70, - effect=unimplemented, + effect=punk_shock, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Toxtricity_108.py b/spirit/game/scripts/cards/SWSH8/Toxtricity_108.py index 4327953..3f65496 100644 --- a/spirit/game/scripts/cards/SWSH8/Toxtricity_108.py +++ b/spirit/game/scripts/cards/SWSH8/Toxtricity_108.py @@ -1,5 +1,36 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, subtypes_for +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.session.passives import Passive, carrier_pokemon +from spirit.game.card_effects.pokemon import is_pokemon_vmax + + +def _my_pokemon_in_play(carrier): + holder = carrier_pokemon(carrier) + if holder is None or holder.parent is None or holder.parent.parent is None: + return [] + player_entity = holder.parent.parent + result = [] + for area in player_entity.children: + if area.get_attribute(AttrID.NAME) in ("activePokemonArea", "bench"): + result.extend(area.children) + return result + + +class MaximumDownerPassive(Passive): + """If all your Pokemon in play are Fusion Strike, opposing Pokemon + VMAX in play get -30 HP.""" + + def max_hp_bonus(self, pokemon, carrier): + holder = carrier_pokemon(carrier) + if holder is None or pokemon.owning_player_id == holder.owning_player_id: + return 0 + if not is_pokemon_vmax(pokemon.archetype_id): + return 0 + mine = _my_pokemon_in_play(carrier) + if not mine or any("Fusion Strike" not in subtypes_for(p.archetype_id) for p in mine): + return 0 + return -30 + card = PokemonCardDef( guid="2fda4efc-1a09-53e0-aad6-54f3cbbf795b", @@ -21,8 +52,8 @@ card = PokemonCardDef( abilities=[ Ability( title="Maximum Downer", - game_text="If all your Pok\u00e9mon in play are Fusion Strike Pok\u00e9mon, your opponent's Pok\u00e9mon VMAX in play get -30 HP.", - effect=unimplemented, + game_text="If all your Pokémon in play are Fusion Strike Pokémon, your opponent's Pokémon VMAX in play get -30 HP.", + passive=MaximumDownerPassive(), ), Attack( title="Head Bolt", @@ -30,4 +61,4 @@ card = PokemonCardDef( damage=90, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH8/TrainingCourt_282.py b/spirit/game/scripts/cards/SWSH8/TrainingCourt_282.py index d3687ee..df9ae1c 100644 --- a/spirit/game/scripts/cards/SWSH8/TrainingCourt_282.py +++ b/spirit/game/scripts/cards/SWSH8/TrainingCourt_282.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import StadiumCardDef, unimplemented +from spirit.game.data_utils import StadiumCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import TRAINING_COURT_ABILITY card = StadiumCardDef( guid="8fb314ca-2d3d-5117-a5bd-573d2d67bd15", @@ -11,5 +12,5 @@ card = StadiumCardDef( collector_number=282, set_code="SWSH8", rarity=Rarities.RareSecret, - effect=unimplemented + ability=TRAINING_COURT_ABILITY, ) diff --git a/spirit/game/scripts/cards/SWSH8/Trubbish_168.py b/spirit/game/scripts/cards/SWSH8/Trubbish_168.py index 2f3e479..46bc714 100644 --- a/spirit/game/scripts/cards/SWSH8/Trubbish_168.py +++ b/spirit/game/scripts/cards/SWSH8/Trubbish_168.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.support_common import search_to_bench card = PokemonCardDef( guid="c3d01ab7-2342-53c6-91f6-0bb86fdd6f47", @@ -22,14 +24,14 @@ card = PokemonCardDef( title="Call for Family", game_text="Search your deck for a Basic Pok\u00e9mon and put it onto your Bench. Then, shuffle your deck.", cost={PokemonTypes.DARKNESS: 1}, - effect=unimplemented, + effect=search_to_bench(), ), Attack( title="Super Poison Breath", game_text="Your opponent's Active Pok\u00e9mon is now Poisoned.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 1}, damage=20, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/TsareenaV_21.py b/spirit/game/scripts/cards/SWSH8/TsareenaV_21.py index 9d01d12..a461f52 100644 --- a/spirit/game/scripts/cards/SWSH8/TsareenaV_21.py +++ b/spirit/game/scripts/cards/SWSH8/TsareenaV_21.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import full_stack + + +async def queens_orders(ctx): + """You may discard any number of your Benched Pokemon; this attack does + 40 more damage for each Benched Pokemon discarded this way.""" + bench = ctx.my_bench() + picks = [] + if bench: + picks = await ctx.choose_cards( + bench, len(bench), minimum=0, + prompt="Choose any number of your Benched Pokémon to discard.", + ) + for pokemon in picks: + await ctx.discard_cards(full_stack(pokemon)) + await ctx.deal_damage(20 + 40 * len(picks)) + card = PokemonCardDef( guid="6077bcc5-ddf1-5331-a404-1ed85323130c", @@ -24,7 +41,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=queens_orders, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/TsareenaV_246.py b/spirit/game/scripts/cards/SWSH8/TsareenaV_246.py index 3889146..a4f1442 100644 --- a/spirit/game/scripts/cards/SWSH8/TsareenaV_246.py +++ b/spirit/game/scripts/cards/SWSH8/TsareenaV_246.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import full_stack + + +async def queens_orders(ctx): + """You may discard any number of your Benched Pokemon; this attack does + 40 more damage for each Benched Pokemon discarded this way.""" + bench = ctx.my_bench() + picks = [] + if bench: + picks = await ctx.choose_cards( + bench, len(bench), minimum=0, + prompt="Choose any number of your Benched Pokémon to discard.", + ) + for pokemon in picks: + await ctx.discard_cards(full_stack(pokemon)) + await ctx.deal_damage(20 + 40 * len(picks)) + card = PokemonCardDef( guid="d0da8f96-8aab-548e-9d9d-0bcbb84a9089", @@ -24,7 +41,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=queens_orders, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Turtonator_198.py b/spirit/game/scripts/cards/SWSH8/Turtonator_198.py index fef7440..1c54a23 100644 --- a/spirit/game/scripts/cards/SWSH8/Turtonator_198.py +++ b/spirit/game/scripts/cards/SWSH8/Turtonator_198.py @@ -1,5 +1,54 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import ( + PokemonCardDef, Attack, Ability, Triggers, ability_id_for, ABILITIES_BY_ID, +) +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID + +TURTONATOR_198_GUID = "ce380d56-f682-5267-b661-6afc1d22ea1a" + + +async def _shell_trap_trigger(ctx): + """Fires on ON_DAMAGED_BY_ATTACK (put counters if still within the window) + and on BETWEEN_TURNS (cleans up the grant once the window has passed).""" + pokemon = ctx.source + until = getattr(pokemon, "_shell_trap_until", None) + if ctx.damaged_by is not None: + if until is not None and ctx.session.turn_state.turn_number == until: + await ctx.deal_damage( + 80, target=ctx.damaged_by, apply_modifiers=False, as_counters=True + ) + return + if until is not None and ctx.session.turn_state.turn_number >= until: + pokemon._shell_trap_until = None + entries = [ + e for e in (pokemon.get_attribute(AttrID.PIE_ABILITIES) or []) + if not (isinstance(e, dict) and e.get("abilityID") == SHELL_TRAP_GRANTED.ability_id) + ] + await ctx.session._broadcast_entity_attribute(pokemon, AttrID.PIE_ABILITIES, entries) + + +SHELL_TRAP_GRANTED = Ability( + title="Shell Trap", + game_text="During your opponent's next turn, if this Pokémon is damaged by an attack (even if it is Knocked Out), put 8 damage counters on the Attacking Pokémon.", + trigger=(Triggers.ON_DAMAGED_BY_ATTACK, Triggers.BETWEEN_TURNS), + effect=_shell_trap_trigger, +) +SHELL_TRAP_GRANTED.ability_id = ability_id_for(TURTONATOR_198_GUID, 99) +ABILITIES_BY_ID[SHELL_TRAP_GRANTED.ability_id] = SHELL_TRAP_GRANTED + + +async def shell_trap(ctx): + """30. During the opponent's next turn, being damaged by an attack puts + 8 damage counters on the attacker (even if this Pokemon is KO'd).""" + await ctx.deal_damage() + ctx.attacker._shell_trap_until = ctx.session.turn_state.turn_number + 1 + entries = list(ctx.attacker.get_attribute(AttrID.PIE_ABILITIES) or []) + if all(e.get("abilityID") != SHELL_TRAP_GRANTED.ability_id + for e in entries if isinstance(e, dict)): + entries.append(SHELL_TRAP_GRANTED.to_dict()) + await ctx.session._broadcast_entity_attribute( + ctx.attacker, AttrID.PIE_ABILITIES, entries + ) + card = PokemonCardDef( guid="ce380d56-f682-5267-b661-6afc1d22ea1a", @@ -22,7 +71,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, if this Pok\u00e9mon is damaged by an attack (even if it is Knocked Out), put 8 damage counters on the Attacking Pok\u00e9mon.", cost={PokemonTypes.FIRE: 1, PokemonTypes.FIGHTING: 1}, damage=30, - effect=unimplemented, + effect=shell_trap, ), Attack( title="Heat Crash", diff --git a/spirit/game/scripts/cards/SWSH8/Tynamo_95.py b/spirit/game/scripts/cards/SWSH8/Tynamo_95.py index 36fcfd9..698d9c1 100644 --- a/spirit/game/scripts/cards/SWSH8/Tynamo_95.py +++ b/spirit/game/scripts/cards/SWSH8/Tynamo_95.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="11264ce0-b04f-54d8-9f05-9e5008c0d791", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Thunder Wave", game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.LIGHTNING: 1}, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), Attack( title="Tackle", diff --git a/spirit/game/scripts/cards/SWSH8/Victini_36.py b/spirit/game/scripts/cards/SWSH8/Victini_36.py index 40ca702..32768f8 100644 --- a/spirit/game/scripts/cards/SWSH8/Victini_36.py +++ b/spirit/game/scripts/cards/SWSH8/Victini_36.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import attach_from_discard +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.card_effects.passives_common import is_in_active_spot card = PokemonCardDef( guid="e41e0702-a821-5f0f-9f08-18a070a4e3a8", @@ -20,9 +23,13 @@ card = PokemonCardDef( abilities=[ Attack( title="Fiery Cheering", - game_text="Attach a basic Energy card from your discard pile to 1 of your Benched Pok\u00e9mon.", + game_text="Attach a basic Energy card from your discard pile to 1 of your Benched Pokémon.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=attach_from_discard( + predicate=is_basic_energy_card, count=1, + target=lambda p: not is_in_active_spot(p), + prompt="Choose a basic Energy card to attach to a Benched Pokémon", + ), ), Attack( title="Flare", @@ -30,4 +37,4 @@ card = PokemonCardDef( damage=20, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH8/Vikavolt_101.py b/spirit/game/scripts/cards/SWSH8/Vikavolt_101.py index bdce169..2c69f35 100644 --- a/spirit/game/scripts/cards/SWSH8/Vikavolt_101.py +++ b/spirit/game/scripts/cards/SWSH8/Vikavolt_101.py @@ -1,5 +1,19 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import snipe_attack +from spirit.game.card_effects.pokemon import energy_provides_type + + +async def electro_blaster(ctx): + """Discard 2 Lightning Energy from this Pokemon. 200 damage to 1 of your + opponent's Pokemon (no W/R on Benched).""" + await ctx.discard_energy_from( + ctx.attacker, 2, + predicate=lambda c: energy_provides_type(c, PokemonTypes.LIGHTNING.value), + prompt="Choose 2 Lightning Energy to discard from this Pokémon", + ) + await snipe_attack(200, pool="any", count=1)(ctx) + card = PokemonCardDef( guid="69a5225e-f2e2-589b-9c02-ba9b2a8640a4", @@ -26,9 +40,9 @@ card = PokemonCardDef( ), Attack( title="Electro Blaster", - game_text="Discard 2 Lightning Energy from this Pok\u00e9mon. This attack does 200 damage to 1 of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", + game_text="Discard 2 Lightning Energy from this Pokémon. This attack does 200 damage to 1 of your opponent's Pokémon. (Don't apply Weakness and Resistance for Benched Pokémon.)", cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + effect=electro_blaster, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH8/Virizion_15.py b/spirit/game/scripts/cards/SWSH8/Virizion_15.py index 3cf52ed..0c78996 100644 --- a/spirit/game/scripts/cards/SWSH8/Virizion_15.py +++ b/spirit/game/scripts/cards/SWSH8/Virizion_15.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import recover_from_discard +from spirit.game.session.effects import is_pokemon_card card = PokemonCardDef( guid="f6dac522-9795-5252-a8e4-f98403244f6a", @@ -22,7 +24,7 @@ card = PokemonCardDef( title="Bail Out", game_text="Put up to 2 Pok\u00e9mon from your discard pile into your hand.", cost={PokemonTypes.GRASS: 1}, - effect=unimplemented, + effect=recover_from_discard(is_pokemon_card, count=2, to="hand"), ), Attack( title="Solar Beam", diff --git a/spirit/game/scripts/cards/SWSH8/Voltorb_87.py b/spirit/game/scripts/cards/SWSH8/Voltorb_87.py index 32c6f80..22e5629 100644 --- a/spirit/game/scripts/cards/SWSH8/Voltorb_87.py +++ b/spirit/game/scripts/cards/SWSH8/Voltorb_87.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_or_nothing card = PokemonCardDef( guid="774a9ffd-09d3-5fc4-bc17-89ff8084d017", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Flip a coin. If tails, this attack does nothing.", cost={PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=flip_or_nothing(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Vullaby_172.py b/spirit/game/scripts/cards/SWSH8/Vullaby_172.py index 77e4a2e..a536f4d 100644 --- a/spirit/game/scripts/cards/SWSH8/Vullaby_172.py +++ b/spirit/game/scripts/cards/SWSH8/Vullaby_172.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import self_energy_discard_attack card = PokemonCardDef( guid="00f1cd9d-d6cd-5f0f-aaa9-8c795ca1b1c2", @@ -29,7 +30,7 @@ card = PokemonCardDef( game_text="Discard an Energy from this Pok\u00e9mon.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=self_energy_discard_attack(count=1), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Wigglytuff_111.py b/spirit/game/scripts/cards/SWSH8/Wigglytuff_111.py index 34ab687..2bd975f 100644 --- a/spirit/game/scripts/cards/SWSH8/Wigglytuff_111.py +++ b/spirit/game/scripts/cards/SWSH8/Wigglytuff_111.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_hand card = PokemonCardDef( guid="58415683-81fd-59e8-b5c9-2cf643610bac", @@ -23,7 +24,8 @@ card = PokemonCardDef( title="Find Treasure", game_text="Search your deck for up to 2 cards and put them into your hand. Then, shuffle your deck.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=search_to_hand(count=2, minimum=0, reveal=False, + prompt="Choose up to 2 cards to put into your hand."), ), Attack( title="Hyper Voice", diff --git a/spirit/game/scripts/cards/SWSH8/Wooloo_221.py b/spirit/game/scripts/cards/SWSH8/Wooloo_221.py index f71c2bf..d9358f9 100644 --- a/spirit/game/scripts/cards/SWSH8/Wooloo_221.py +++ b/spirit/game/scripts/cards/SWSH8/Wooloo_221.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import ( + discard_opponent_energy_attack, damage_per, count_bench, has_attack_titled, +) card = PokemonCardDef( guid="fd92eed4-3c14-57e5-87db-240e7ac442f7", @@ -20,18 +23,18 @@ card = PokemonCardDef( abilities=[ Attack( title="Derail", - game_text="Discard a Special Energy from your opponent's Active Pok\u00e9mon.", + game_text="Discard a Special Energy from your opponent's Active Pokémon.", cost={PokemonTypes.COLORLESS: 1}, damage=10, - effect=unimplemented, + effect=discard_opponent_energy_attack(special_only=True), ), Attack( title="Let's All Rollout", - game_text="This attack does 20 damage for each of your Benched Pok\u00e9mon that has the Let's All Rollout attack.", + game_text="This attack does 20 damage for each of your Benched Pokémon that has the Let's All Rollout attack.", cost={PokemonTypes.COLORLESS: 2}, damage=20, damage_operator="x", - effect=unimplemented, + effect=damage_per(count_bench("mine", has_attack_titled("Let's All Rollout")), 20), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH8/Wooloo_222.py b/spirit/game/scripts/cards/SWSH8/Wooloo_222.py index 0e742c9..0d41a12 100644 --- a/spirit/game/scripts/cards/SWSH8/Wooloo_222.py +++ b/spirit/game/scripts/cards/SWSH8/Wooloo_222.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus card = PokemonCardDef( guid="aed7ca71-7937-55d0-84ee-b65070ea7f82", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=20, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(40), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Zarude_27.py b/spirit/game/scripts/cards/SWSH8/Zarude_27.py index 4354b35..1a0f475 100644 --- a/spirit/game/scripts/cards/SWSH8/Zarude_27.py +++ b/spirit/game/scripts/cards/SWSH8/Zarude_27.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus card = PokemonCardDef( guid="83c521e4-053f-5d2a-9883-ab6f11d5e485", @@ -29,7 +30,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=40, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Zeraora_102.py b/spirit/game/scripts/cards/SWSH8/Zeraora_102.py index af59522..d07352e 100644 --- a/spirit/game/scripts/cards/SWSH8/Zeraora_102.py +++ b/spirit/game/scripts/cards/SWSH8/Zeraora_102.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack card = PokemonCardDef( guid="d6d1c840-c842-5dab-99aa-54ea4c9af26b", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="This Pok\u00e9mon also does 20 damage to itself.", cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 1}, damage=70, - effect=unimplemented, + effect=recoil_attack(20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH8/Zoroark_171.py b/spirit/game/scripts/cards/SWSH8/Zoroark_171.py index 0411b19..aa199eb 100644 --- a/spirit/game/scripts/cards/SWSH8/Zoroark_171.py +++ b/spirit/game/scripts/cards/SWSH8/Zoroark_171.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="0828c10b-1fd2-5243-9b05-e838c8681b93", @@ -25,7 +26,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 1}, damage=40, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=2, per_heads=40), ), Attack( title="Night Daze", diff --git a/spirit/game/scripts/cards/SWSH9/AcerolasPremonition_129.py b/spirit/game/scripts/cards/SWSH9/AcerolasPremonition_129.py index a8b3035..13109a9 100644 --- a/spirit/game/scripts/cards/SWSH9/AcerolasPremonition_129.py +++ b/spirit/game/scripts/cards/SWSH9/AcerolasPremonition_129.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.session.effects import is_trainer_card + + +async def acerolas_premonition(ctx): + """Opponent reveals their hand; draw a card for each Trainer card found there.""" + cards = await ctx.reveal_hand(ctx.opponent_id, ctx.player_id) + count = sum(1 for c in cards if is_trainer_card(c)) + if count: + await ctx.draw_cards(count) + card = SupporterCardDef( guid="0c386ba6-db75-5ec7-9d70-0d55188018a6", @@ -11,5 +21,5 @@ card = SupporterCardDef( collector_number=129, set_code="SWSH9", rarity=Rarities.Uncommon, - effect=unimplemented + effect=acerolas_premonition ) diff --git a/spirit/game/scripts/cards/SWSH9/Alcremie_71.py b/spirit/game/scripts/cards/SWSH9/Alcremie_71.py index 4e16845..f08ecc7 100644 --- a/spirit/game/scripts/cards/SWSH9/Alcremie_71.py +++ b/spirit/game/scripts/cards/SWSH9/Alcremie_71.py @@ -1,5 +1,17 @@ from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.attributes import AttrID, CardType, PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_special_energy + + +async def rainbow_flavor(ctx): + """10, +40 for each distinct type of basic Energy attached to all of your Pokemon.""" + types = set() + for pokemon in ctx.my_pokemon_in_play(): + for energy in ctx.attached_energies(pokemon): + if energy.get_attribute(AttrID.CARD_TYPE) == CardType.ENERGY.value \ + and not is_special_energy(energy): + types.update(energy.get_attribute(AttrID.POKEMON_TYPES) or []) + await ctx.deal_damage(10 + 40 * len(types)) card = PokemonCardDef( guid="37d5a9ae-c2a9-5bc2-9ba0-da81317e2bca", @@ -19,10 +31,11 @@ card = PokemonCardDef( evolves_from="com.direwolfdigital.cake.data.archetypes.pokemon.Milcery.Name", family_id=868, abilities=[ + # Marker ability: the Caf\u00e9 Master supporter script checks for it on + # the Active before applying its "Your turn ends." clause. Ability( title="Additional Order", game_text="As long as this Pok\u00e9mon is in the Active Spot, your turn does not end when you use Caf\u00e9 Master.", - effect=unimplemented, ), Attack( title="Rainbow Flavor", @@ -30,7 +43,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=10, damage_operator="+", - effect=unimplemented, + effect=rainbow_flavor, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/ArceusVSTAR_123.py b/spirit/game/scripts/cards/SWSH9/ArceusVSTAR_123.py index 4d4fe4c..edc0b63 100644 --- a/spirit/game/scripts/cards/SWSH9/ArceusVSTAR_123.py +++ b/spirit/game/scripts/cards/SWSH9/ArceusVSTAR_123.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations, is_pokemon_v from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_attach_energy +from spirit.game.card_effects.trainers import is_basic_energy_card + + +async def starbirth(ctx): + """VSTAR Power: search your deck for up to 2 cards and put them into your hand.""" + picks = await ctx.search_deck( + None, count=2, minimum=0, prompt="Choose up to 2 cards to put into your hand.", + ) + await ctx.put_in_hand(picks, reveal=False) + await ctx.shuffle_deck() + card = PokemonCardDef( guid="b3e4bb6e-c548-5a90-9649-ba1d0f9a6d52", @@ -22,14 +34,19 @@ card = PokemonCardDef( Ability( title="Starbirth", game_text="During your turn, you may search your deck for up to 2 cards and put them into your hand. Then, shuffle your deck. (You can't use more than 1 VSTAR Power in a game.)", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + vstar=True, + effect=starbirth, ), Attack( title="Trinity Nova", game_text="Search your deck for up to 3 basic Energy cards and attach them to your Pok\u00e9mon V in any way you like. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 3}, damage=200, - effect=unimplemented, + effect=search_attach_energy( + is_basic_energy_card, count=3, + target_pred=lambda p: is_pokemon_v(p.archetype_id), + ), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/ArceusVSTAR_176.py b/spirit/game/scripts/cards/SWSH9/ArceusVSTAR_176.py index 41fd46e..81bec66 100644 --- a/spirit/game/scripts/cards/SWSH9/ArceusVSTAR_176.py +++ b/spirit/game/scripts/cards/SWSH9/ArceusVSTAR_176.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations, is_pokemon_v from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_attach_energy +from spirit.game.card_effects.trainers import is_basic_energy_card + + +async def starbirth(ctx): + """VSTAR Power: search your deck for up to 2 cards and put them into your hand.""" + picks = await ctx.search_deck( + None, count=2, minimum=0, prompt="Choose up to 2 cards to put into your hand.", + ) + await ctx.put_in_hand(picks, reveal=False) + await ctx.shuffle_deck() + card = PokemonCardDef( guid="5ab27d53-e06e-5631-8e00-9f54682cd945", @@ -22,14 +34,19 @@ card = PokemonCardDef( Ability( title="Starbirth", game_text="During your turn, you may search your deck for up to 2 cards and put them into your hand. Then, shuffle your deck. (You can't use more than 1 VSTAR Power in a game.)", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + vstar=True, + effect=starbirth, ), Attack( title="Trinity Nova", game_text="Search your deck for up to 3 basic Energy cards and attach them to your Pok\u00e9mon V in any way you like. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 3}, damage=200, - effect=unimplemented, + effect=search_attach_energy( + is_basic_energy_card, count=3, + target_pred=lambda p: is_pokemon_v(p.archetype_id), + ), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/ArceusVSTAR_184.py b/spirit/game/scripts/cards/SWSH9/ArceusVSTAR_184.py index 6ea87fb..3822c0b 100644 --- a/spirit/game/scripts/cards/SWSH9/ArceusVSTAR_184.py +++ b/spirit/game/scripts/cards/SWSH9/ArceusVSTAR_184.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations, is_pokemon_v from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_attach_energy +from spirit.game.card_effects.trainers import is_basic_energy_card + + +async def starbirth(ctx): + """VSTAR Power: search your deck for up to 2 cards and put them into your hand.""" + picks = await ctx.search_deck( + None, count=2, minimum=0, prompt="Choose up to 2 cards to put into your hand.", + ) + await ctx.put_in_hand(picks, reveal=False) + await ctx.shuffle_deck() + card = PokemonCardDef( guid="61e45df9-a5de-5674-9893-4359b90cccc5", @@ -22,14 +34,19 @@ card = PokemonCardDef( Ability( title="Starbirth", game_text="During your turn, you may search your deck for up to 2 cards and put them into your hand. Then, shuffle your deck. (You can't use more than 1 VSTAR Power in a game.)", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + vstar=True, + effect=starbirth, ), Attack( title="Trinity Nova", game_text="Search your deck for up to 3 basic Energy cards and attach them to your Pok\u00e9mon V in any way you like. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 3}, damage=200, - effect=unimplemented, + effect=search_attach_energy( + is_basic_energy_card, count=3, + target_pred=lambda p: is_pokemon_v(p.archetype_id), + ), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/Axew_110.py b/spirit/game/scripts/cards/SWSH9/Axew_110.py index 4eddc53..cec56be 100644 --- a/spirit/game/scripts/cards/SWSH9/Axew_110.py +++ b/spirit/game/scripts/cards/SWSH9/Axew_110.py @@ -1,5 +1,23 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_pokemon_card + + +async def ultra_evolution(ctx): + """Flip a coin; on heads, search a Haxorus and evolve this Axew with it.""" + heads = (await ctx.flip_coins(1, "Ultra Evolution"))[0] + if not heads: + return + picks = await ctx.search_deck( + lambda c: is_pokemon_card(c) + and c.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) == "Haxorus", + count=1, minimum=0, + prompt="Choose a Haxorus to evolve this Axew.", + ) + if picks: + await ctx.evolve_pokemon(ctx.attacker, picks[0]) + await ctx.shuffle_deck() + card = PokemonCardDef( guid="b2c9db6f-0a37-5c13-b147-5124b8e49527", @@ -21,7 +39,7 @@ card = PokemonCardDef( title="Ultra Evolution", game_text="Flip a coin. If heads, search your deck for a Haxorus and put it onto this Axew to evolve it. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=ultra_evolution, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/Baltoy_58.py b/spirit/game/scripts/cards/SWSH9/Baltoy_58.py index 67c0c95..3778b33 100644 --- a/spirit/game/scripts/cards/SWSH9/Baltoy_58.py +++ b/spirit/game/scripts/cards/SWSH9/Baltoy_58.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="3010c234-790e-5bb6-bdf0-e3f5d351eba0", @@ -25,7 +26,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=20, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=3, per_heads=20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/Barry_130.py b/spirit/game/scripts/cards/SWSH9/Barry_130.py index fea189f..9479b5a 100644 --- a/spirit/game/scripts/cards/SWSH9/Barry_130.py +++ b/spirit/game/scripts/cards/SWSH9/Barry_130.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import hop card = SupporterCardDef( guid="ab263a03-c4aa-50dc-833e-a8e2e65ed86c", @@ -11,5 +12,5 @@ card = SupporterCardDef( collector_number=130, set_code="SWSH9", rarity=Rarities.Uncommon, - effect=unimplemented + effect=hop ) diff --git a/spirit/game/scripts/cards/SWSH9/Barry_167.py b/spirit/game/scripts/cards/SWSH9/Barry_167.py index 6f85632..d5fbe3d 100644 --- a/spirit/game/scripts/cards/SWSH9/Barry_167.py +++ b/spirit/game/scripts/cards/SWSH9/Barry_167.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import hop card = SupporterCardDef( guid="259b4249-f054-565c-a414-e5a75db917fd", @@ -11,5 +12,5 @@ card = SupporterCardDef( collector_number=167, set_code="SWSH9", rarity=Rarities.RareUltra, - effect=unimplemented + effect=hop ) diff --git a/spirit/game/scripts/cards/SWSH9/Beartic_43.py b/spirit/game/scripts/cards/SWSH9/Beartic_43.py index 993d4cb..ca32d64 100644 --- a/spirit/game/scripts/cards/SWSH9/Beartic_43.py +++ b/spirit/game/scripts/cards/SWSH9/Beartic_43.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import lock_defender_attacks + + +async def sheer_cold(ctx): + """40 damage; the Defending Pokemon can't attack during your opponent's next turn.""" + await ctx.deal_damage() + lock_defender_attacks(ctx) card = PokemonCardDef( guid="431093c3-5d00-5b05-8861-479090185f97", @@ -24,7 +31,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, the Defending Pok\u00e9mon can't attack.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 1}, damage=40, - effect=unimplemented, + effect=sheer_cold, ), Attack( title="Frost Smash", diff --git a/spirit/game/scripts/cards/SWSH9/Bibarel_121.py b/spirit/game/scripts/cards/SWSH9/Bibarel_121.py index 737b610..e12bb93 100644 --- a/spirit/game/scripts/cards/SWSH9/Bibarel_121.py +++ b/spirit/game/scripts/cards/SWSH9/Bibarel_121.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_or_nothing + + +async def industrious_incisors(ctx): + if await ctx.ask_yes_no("Draw cards until you have 5 cards in your hand?"): + await ctx.draw_until(5) + + +def _hand_below_five(board, player_id, pokemon): + hand = board.find_player_area(player_id, "hand") + return (len(hand.children) if hand else 0) < 5 + card = PokemonCardDef( guid="18aff19b-238e-52b3-8ec8-a4b592d043a1", @@ -22,14 +34,16 @@ card = PokemonCardDef( Ability( title="Industrious Incisors", game_text="Once during your turn, you may draw cards until you have 5 cards in your hand.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=_hand_below_five, + effect=industrious_incisors, ), Attack( title="Tail Smash", game_text="Flip a coin. If tails, this attack does nothing.", cost={PokemonTypes.COLORLESS: 3}, damage=100, - effect=unimplemented, + effect=flip_or_nothing(), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH9/BlunderPolicy_131.py b/spirit/game/scripts/cards/SWSH9/BlunderPolicy_131.py index 1cdfb21..db29ff2 100644 --- a/spirit/game/scripts/cards/SWSH9/BlunderPolicy_131.py +++ b/spirit/game/scripts/cards/SWSH9/BlunderPolicy_131.py @@ -1,15 +1,29 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented +from spirit.game.data_utils import PokemonToolCardDef from spirit.game.attributes import Rarities +from spirit.game.session.passives import Passive, carrier_pokemon + + +class BlunderPolicyPassive(Passive): + """Holder attacked, its effect flipped coins, any were tails: the owner + draws 3 at the end of the turn (only while the tool is still in play).""" + + async def attack_followup(self, ctx, carrier): + if carrier_pokemon(carrier) is not ctx.attacker: + return + if not any(r != 0 for r in ctx.coin_results): + return + await ctx.draw_cards(3) + card = PokemonToolCardDef( guid="98570270-fd4a-5b05-82c2-d8e03f008051", key="SWSH9", name="com.direwolfdigital.cake.data.archetypes.trainer.BlunderPolicy.Name", display_name="Blunder Policy", - searchable_by=["Blunder Policy", "Item", "Pok\u00c3\u00a9mon Tool"], - subtypes=["Item", "Pok\u00e9mon Tool"], + searchable_by=["Blunder Policy", "Item", "Pokémon Tool"], + subtypes=["Item", "Pokémon Tool"], collector_number=131, set_code="SWSH9", rarity=Rarities.Uncommon, - effect=unimplemented + passive=BlunderPolicyPassive(), ) diff --git a/spirit/game/scripts/cards/SWSH9/Breloom_4.py b/spirit/game/scripts/cards/SWSH9/Breloom_4.py index 16c3345..3f90bf0 100644 --- a/spirit/game/scripts/cards/SWSH9/Breloom_4.py +++ b/spirit/game/scripts/cards/SWSH9/Breloom_4.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack + + +def _used_spore_ball_last_turn(board, player_id, pokemon): + for used_id, _archetype, used_title in board.turn_state.attacks_used_last_turn: + if used_id == pokemon.entity_id and used_title == "Spore Ball": + return True + return False + card = PokemonCardDef( guid="9142cea6-ebb5-5cdd-a7fe-bdbe30354d16", @@ -24,14 +33,14 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Asleep.", cost={PokemonTypes.GRASS: 1}, damage=30, - effect=unimplemented, + effect=condition_attack(SpecialConditions.ASLEEP), ), Attack( title="Powdery Uppercut", game_text="You can use this attack only if this Pok\u00e9mon used Spore Ball during your last turn.", cost={PokemonTypes.GRASS: 1}, damage=130, - effect=unimplemented, + condition=_used_spore_ball_last_turn, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/Buizel_38.py b/spirit/game/scripts/cards/SWSH9/Buizel_38.py index 596da1e..4f821ff 100644 --- a/spirit/game/scripts/cards/SWSH9/Buizel_38.py +++ b/spirit/game/scripts/cards/SWSH9/Buizel_38.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import flip_protection card = PokemonCardDef( guid="8aae580f-dce9-5358-bd11-cb4e30e10155", @@ -20,10 +21,10 @@ card = PokemonCardDef( abilities=[ Attack( title="Agility", - game_text="Flip a coin. If heads, during your opponent's next turn, prevent all damage from and effects of attacks done to this Pok\u00e9mon.", + game_text="Flip a coin. If heads, during your opponent's next turn, prevent all damage from and effects of attacks done to this Pokémon.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 1}, damage=20, - effect=unimplemented, + effect=flip_protection(prevent=True, effects_too=True), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH9/CafMaster_133.py b/spirit/game/scripts/cards/SWSH9/CafMaster_133.py index 0273462..532f5a9 100644 --- a/spirit/game/scripts/cards/SWSH9/CafMaster_133.py +++ b/spirit/game/scripts/cards/SWSH9/CafMaster_133.py @@ -1,15 +1,62 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import ABILITIES_BY_ID, SupporterCardDef +from spirit.game.attributes import Rarities, AttrID +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.session.passives import ability_locked + + +def _additional_order_active(ctx): + """Alcremie's Additional Order: while it's the Active, Café Master does + not end the turn (an ability, so Path to the Peak-style locks apply).""" + active = ctx.my_active() + if active is None or ability_locked(ctx.board, active): + return False + for entry in active.get_attribute(AttrID.PIE_ABILITIES) or []: + if not isinstance(entry, dict): + continue + ability = ABILITIES_BY_ID.get(entry.get("abilityID")) + if ability is not None and ability.title == "Additional Order": + return True + return False + + +async def cafe_master(ctx): + """Choose up to 3 Benched Pokemon; for each, search a different type of + basic Energy card and attach it to that Pokemon. Shuffle. End the turn.""" + bench = ctx.my_bench() + targets = await ctx.choose_cards( + bench, 3, minimum=0, prompt="Choose up to 3 of your Benched Pokémon.", + ) if bench else [] + used_types = [] + for target in targets: + snapshot = list(used_types) + + def _pred(card, _used=snapshot): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_basic_energy_card(card) and bool(types) and types[0] not in _used + + picks = await ctx.search_deck( + _pred, count=1, minimum=0, + prompt="Choose a different type of basic Energy card to attach.", + ) + if picks: + energy = picks[0] + await ctx.attach_energy(energy, target) + types = energy.get_attribute(AttrID.POKEMON_TYPES) or [] + if types: + used_types.append(types[0]) + await ctx.shuffle_deck() + ctx.ends_turn = not _additional_order_active(ctx) + card = SupporterCardDef( guid="1c9c921d-ba43-52f9-aafd-3b1cbabf46a8", key="SWSH9", name="com.direwolfdigital.cake.data.archetypes.trainer.CafMaster.Name", - display_name="Café Master", - searchable_by=["Caf\u00c3\u00a9 Master", "Supporter"], + display_name="Café Master", + searchable_by=["Café Master", "Cafe Master", "Supporter"], subtypes=["Supporter"], collector_number=133, set_code="SWSH9", rarity=Rarities.Uncommon, - effect=unimplemented + effect=cafe_master ) diff --git a/spirit/game/scripts/cards/SWSH9/Castform_116.py b/spirit/game/scripts/cards/SWSH9/Castform_116.py index 2a05028..b32473c 100644 --- a/spirit/game/scripts/cards/SWSH9/Castform_116.py +++ b/spirit/game/scripts/cards/SWSH9/Castform_116.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import draw_attack +from spirit.game.card_effects.trainers import is_basic_energy_card + + +async def _hurricane(ctx): + await ctx.deal_damage() + bench = ctx.my_bench() + if bench: + await ctx.move_energy_freely( + [ctx.attacker], bench, predicate=is_basic_energy_card, max_count=1, + prompt="Choose a basic Energy to move to a Benched Pokémon", + ) + card = PokemonCardDef( guid="6a75722f-d972-5a86-88b9-63f7ee202d1a", @@ -22,14 +35,14 @@ card = PokemonCardDef( title="Double Draw", game_text="Draw 2 cards.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=draw_attack(2), ), Attack( title="Hurricane", game_text="Move a basic Energy from this Pok\u00e9mon to 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=_hurricane, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/CharizardVSTAR_174.py b/spirit/game/scripts/cards/SWSH9/CharizardVSTAR_174.py index 5f1d20f..3eec1fe 100644 --- a/spirit/game/scripts/cards/SWSH9/CharizardVSTAR_174.py +++ b/spirit/game/scripts/cards/SWSH9/CharizardVSTAR_174.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, has_damage, self_energy_discard_attack card = PokemonCardDef( guid="f0613aa9-271a-53ca-be17-73f9ba012530", @@ -25,14 +26,15 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=130, damage_operator="+", - effect=unimplemented, + effect=bonus_if(has_damage("self"), 100), ), Attack( title="Star Blaze", game_text="Discard 2 Energy from this Pok\u00e9mon. (You can't use more than 1 VSTAR Power in a game.)", cost={PokemonTypes.FIRE: 3, PokemonTypes.COLORLESS: 1}, damage=320, - effect=unimplemented, + vstar=True, + effect=self_energy_discard_attack(count=2), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/CharizardVSTAR_18.py b/spirit/game/scripts/cards/SWSH9/CharizardVSTAR_18.py index c4e13fb..5414542 100644 --- a/spirit/game/scripts/cards/SWSH9/CharizardVSTAR_18.py +++ b/spirit/game/scripts/cards/SWSH9/CharizardVSTAR_18.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, has_damage, self_energy_discard_attack card = PokemonCardDef( guid="717356f0-1dfd-5477-aa96-08d899d4b142", @@ -25,14 +26,15 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=130, damage_operator="+", - effect=unimplemented, + effect=bonus_if(has_damage("self"), 100), ), Attack( title="Star Blaze", game_text="Discard 2 Energy from this Pok\u00e9mon. (You can't use more than 1 VSTAR Power in a game.)", cost={PokemonTypes.FIRE: 3, PokemonTypes.COLORLESS: 1}, damage=320, - effect=unimplemented, + vstar=True, + effect=self_energy_discard_attack(count=2), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/CharizardV_153.py b/spirit/game/scripts/cards/SWSH9/CharizardV_153.py index 3ee7d8a..7920586 100644 --- a/spirit/game/scripts/cards/SWSH9/CharizardV_153.py +++ b/spirit/game/scripts/cards/SWSH9/CharizardV_153.py @@ -1,6 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def incinerate(ctx): + """Before doing damage, discard all Tools from the opponent's Active.""" + defender = ctx.defender + if defender is not None and not ctx.effects_blocked(defender): + tools = [t for t, p in ctx.tools_in_play() if p is defender] + if tools: + await ctx.discard_cards(tools) + await ctx.deal_damage() + + card = PokemonCardDef( guid="9a8dd977-ab14-559e-9afe-f9fe76442fbd", key="SWSH9", @@ -23,7 +34,7 @@ card = PokemonCardDef( game_text="Before doing damage, discard all Pok\u00e9mon Tools from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=90, - effect=unimplemented, + effect=incinerate, ), Attack( title="Heat Blast", diff --git a/spirit/game/scripts/cards/SWSH9/CharizardV_154.py b/spirit/game/scripts/cards/SWSH9/CharizardV_154.py index b05251b..bb27896 100644 --- a/spirit/game/scripts/cards/SWSH9/CharizardV_154.py +++ b/spirit/game/scripts/cards/SWSH9/CharizardV_154.py @@ -1,6 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def incinerate(ctx): + """Before doing damage, discard all Tools from the opponent's Active.""" + defender = ctx.defender + if defender is not None and not ctx.effects_blocked(defender): + tools = [t for t, p in ctx.tools_in_play() if p is defender] + if tools: + await ctx.discard_cards(tools) + await ctx.deal_damage() + + card = PokemonCardDef( guid="e4f997d7-d063-5261-8e6c-f8da0003b100", key="SWSH9", @@ -23,7 +34,7 @@ card = PokemonCardDef( game_text="Before doing damage, discard all Pok\u00e9mon Tools from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=90, - effect=unimplemented, + effect=incinerate, ), Attack( title="Heat Blast", diff --git a/spirit/game/scripts/cards/SWSH9/CharizardV_17.py b/spirit/game/scripts/cards/SWSH9/CharizardV_17.py index f5fa9cc..672cb54 100644 --- a/spirit/game/scripts/cards/SWSH9/CharizardV_17.py +++ b/spirit/game/scripts/cards/SWSH9/CharizardV_17.py @@ -1,6 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def incinerate(ctx): + """Before doing damage, discard all Tools from the opponent's Active.""" + defender = ctx.defender + if defender is not None and not ctx.effects_blocked(defender): + tools = [t for t, p in ctx.tools_in_play() if p is defender] + if tools: + await ctx.discard_cards(tools) + await ctx.deal_damage() + + card = PokemonCardDef( guid="d1160687-2e03-5777-a1d8-9f3f24e5d105", key="SWSH9", @@ -23,7 +34,7 @@ card = PokemonCardDef( game_text="Before doing damage, discard all Pok\u00e9mon Tools from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=90, - effect=unimplemented, + effect=incinerate, ), Attack( title="Heat Blast", diff --git a/spirit/game/scripts/cards/SWSH9/CherensCare_134.py b/spirit/game/scripts/cards/SWSH9/CherensCare_134.py index dec607d..f7ff337 100644 --- a/spirit/game/scripts/cards/SWSH9/CherensCare_134.py +++ b/spirit/game/scripts/cards/SWSH9/CherensCare_134.py @@ -1,5 +1,42 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import SupporterCardDef +from spirit.game.attributes import AttrID, PokemonTypes, Rarities +from spirit.game.session.effects import full_stack +from spirit.game.session.passives import effective_max_hp + + +def _colorless_damaged(board, player_id): + return [ + p for p in board.pokemon_in_play(player_id) + if PokemonTypes.COLORLESS.value in (p.get_attribute(AttrID.POKEMON_TYPES) or []) + and p.get_attribute(AttrID.HP, 0) < effective_max_hp(board, p) + ] + + +def cherens_care_condition(board, player_id): + return bool(_colorless_damaged(board, player_id)) + + +async def cherens_care(ctx): + """Put 1 of your Colorless Pokemon with damage counters and all attached + cards into your hand.""" + candidates = _colorless_damaged(ctx.board, ctx.player_id) + target = await ctx.choose_pokemon( + candidates, + "Choose 1 of your Colorless Pokémon with damage counters on it", + ) + if target is None: + return + was_active = target is ctx.my_active() + await ctx.put_in_hand(full_stack(target), reveal=False) + if was_active: + async def _promote(): + if not await ctx.session._promote_new_active(ctx.player_id): + screen_name = ctx.session.players[ctx.player_id].screen_name + await ctx.session.end_game( + ctx.opponent_id, f"{screen_name} has no Pokémon left" + ) + ctx.deferred_actions.append(_promote) + card = SupporterCardDef( guid="2aad17a1-a84c-553d-afe5-f23de5eb386c", @@ -11,5 +48,6 @@ card = SupporterCardDef( collector_number=134, set_code="SWSH9", rarity=Rarities.Uncommon, - effect=unimplemented + effect=cherens_care, + condition=cherens_care_condition, ) diff --git a/spirit/game/scripts/cards/SWSH9/CherensCare_168.py b/spirit/game/scripts/cards/SWSH9/CherensCare_168.py index a9d878e..252c7ca 100644 --- a/spirit/game/scripts/cards/SWSH9/CherensCare_168.py +++ b/spirit/game/scripts/cards/SWSH9/CherensCare_168.py @@ -1,5 +1,42 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import SupporterCardDef +from spirit.game.attributes import AttrID, PokemonTypes, Rarities +from spirit.game.session.effects import full_stack +from spirit.game.session.passives import effective_max_hp + + +def _colorless_damaged(board, player_id): + return [ + p for p in board.pokemon_in_play(player_id) + if PokemonTypes.COLORLESS.value in (p.get_attribute(AttrID.POKEMON_TYPES) or []) + and p.get_attribute(AttrID.HP, 0) < effective_max_hp(board, p) + ] + + +def cherens_care_condition(board, player_id): + return bool(_colorless_damaged(board, player_id)) + + +async def cherens_care(ctx): + """Put 1 of your Colorless Pokemon with damage counters and all attached + cards into your hand.""" + candidates = _colorless_damaged(ctx.board, ctx.player_id) + target = await ctx.choose_pokemon( + candidates, + "Choose 1 of your Colorless Pokémon with damage counters on it", + ) + if target is None: + return + was_active = target is ctx.my_active() + await ctx.put_in_hand(full_stack(target), reveal=False) + if was_active: + async def _promote(): + if not await ctx.session._promote_new_active(ctx.player_id): + screen_name = ctx.session.players[ctx.player_id].screen_name + await ctx.session.end_game( + ctx.opponent_id, f"{screen_name} has no Pokémon left" + ) + ctx.deferred_actions.append(_promote) + card = SupporterCardDef( guid="40fdf63e-1337-5e11-a5c3-833cac79c35d", @@ -11,5 +48,6 @@ card = SupporterCardDef( collector_number=168, set_code="SWSH9", rarity=Rarities.RareUltra, - effect=unimplemented + effect=cherens_care, + condition=cherens_care_condition, ) diff --git a/spirit/game/scripts/cards/SWSH9/CherensCare_177.py b/spirit/game/scripts/cards/SWSH9/CherensCare_177.py index 24ab7c9..f161377 100644 --- a/spirit/game/scripts/cards/SWSH9/CherensCare_177.py +++ b/spirit/game/scripts/cards/SWSH9/CherensCare_177.py @@ -1,5 +1,42 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import SupporterCardDef +from spirit.game.attributes import AttrID, PokemonTypes, Rarities +from spirit.game.session.effects import full_stack +from spirit.game.session.passives import effective_max_hp + + +def _colorless_damaged(board, player_id): + return [ + p for p in board.pokemon_in_play(player_id) + if PokemonTypes.COLORLESS.value in (p.get_attribute(AttrID.POKEMON_TYPES) or []) + and p.get_attribute(AttrID.HP, 0) < effective_max_hp(board, p) + ] + + +def cherens_care_condition(board, player_id): + return bool(_colorless_damaged(board, player_id)) + + +async def cherens_care(ctx): + """Put 1 of your Colorless Pokemon with damage counters and all attached + cards into your hand.""" + candidates = _colorless_damaged(ctx.board, ctx.player_id) + target = await ctx.choose_pokemon( + candidates, + "Choose 1 of your Colorless Pokémon with damage counters on it", + ) + if target is None: + return + was_active = target is ctx.my_active() + await ctx.put_in_hand(full_stack(target), reveal=False) + if was_active: + async def _promote(): + if not await ctx.session._promote_new_active(ctx.player_id): + screen_name = ctx.session.players[ctx.player_id].screen_name + await ctx.session.end_game( + ctx.opponent_id, f"{screen_name} has no Pokémon left" + ) + ctx.deferred_actions.append(_promote) + card = SupporterCardDef( guid="a4518392-129f-5b6d-aec0-1b2d59284e21", @@ -11,5 +48,6 @@ card = SupporterCardDef( collector_number=177, set_code="SWSH9", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=cherens_care, + condition=cherens_care_condition, ) diff --git a/spirit/game/scripts/cards/SWSH9/Cherubi_12.py b/spirit/game/scripts/cards/SWSH9/Cherubi_12.py index f7aff19..521f1cb 100644 --- a/spirit/game/scripts/cards/SWSH9/Cherubi_12.py +++ b/spirit/game/scripts/cards/SWSH9/Cherubi_12.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import attack_effect_shield_passive card = PokemonCardDef( guid="d2f82d99-9d8c-5515-9e55-76340c1fa395", @@ -21,7 +22,7 @@ card = PokemonCardDef( Ability( title="Lively Fruit", game_text="Prevent all effects of attacks from your opponent's Pok\u00e9mon done to this Pok\u00e9mon. (Damage is not an effect.)", - effect=unimplemented, + passive=attack_effect_shield_passive(), ), Attack( title="Leafage", diff --git a/spirit/game/scripts/cards/SWSH9/Chimchar_24.py b/spirit/game/scripts/cards/SWSH9/Chimchar_24.py index 22a8983..3959b1d 100644 --- a/spirit/game/scripts/cards/SWSH9/Chimchar_24.py +++ b/spirit/game/scripts/cards/SWSH9/Chimchar_24.py @@ -1,4 +1,5 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import self_energy_discard_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Discard an Energy from this Pok\u00e9mon.", cost={PokemonTypes.FIRE: 1}, damage=30, - effect=unimplemented, + effect=self_energy_discard_attack(count=1), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/Chimecho_63.py b/spirit/game/scripts/cards/SWSH9/Chimecho_63.py index 5f863ab..133a6e0 100644 --- a/spirit/game/scripts/cards/SWSH9/Chimecho_63.py +++ b/spirit/game/scripts/cards/SWSH9/Chimecho_63.py @@ -1,4 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.session.effects import is_special_energy +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -23,7 +25,10 @@ card = PokemonCardDef( title="Clear Tone", game_text="Search your deck for up to 2 Special Energy cards, reveal them, and put them into your hand. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_to_hand( + is_special_energy, count=2, reveal=True, + prompt="Choose up to 2 Special Energy cards.", + ), ), Attack( title="Hang Down", diff --git a/spirit/game/scripts/cards/SWSH9/Cinccino_125.py b/spirit/game/scripts/cards/SWSH9/Cinccino_125.py index 114b640..5b755ce 100644 --- a/spirit/game/scripts/cards/SWSH9/Cinccino_125.py +++ b/spirit/game/scripts/cards/SWSH9/Cinccino_125.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="c0437a94-5b80-529b-8727-141d7ddc995d", @@ -30,7 +31,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=50, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=3, per_heads=50), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH9/Claydol_59.py b/spirit/game/scripts/cards/SWSH9/Claydol_59.py index c46154a..23f4e56 100644 --- a/spirit/game/scripts/cards/SWSH9/Claydol_59.py +++ b/spirit/game/scripts/cards/SWSH9/Claydol_59.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if + + +def _same_bench_count(ctx): + return len(ctx.my_bench()) == len(ctx.opponent_bench()) + card = PokemonCardDef( guid="24134d58-7fec-5960-be75-3310b5208a0b", @@ -31,7 +37,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 2}, damage=90, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_same_bench_count, 90), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/CleansingGloves_136.py b/spirit/game/scripts/cards/SWSH9/CleansingGloves_136.py index 5c77cdd..14b1f74 100644 --- a/spirit/game/scripts/cards/SWSH9/CleansingGloves_136.py +++ b/spirit/game/scripts/cards/SWSH9/CleansingGloves_136.py @@ -1,7 +1,9 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import PokemonToolCardDef +from spirit.game.attributes import Rarities, PokemonTypes +from spirit.game.card_effects.passives_common import typed_damage_boost_tool card = PokemonToolCardDef( + passive=typed_damage_boost_tool(PokemonTypes.PSYCHIC, 30), guid="ea5c50f9-648e-55ea-984b-e412933de360", key="SWSH9", name="com.direwolfdigital.cake.data.archetypes.trainer.CleansingGloves.Name", @@ -11,5 +13,4 @@ card = PokemonToolCardDef( collector_number=136, set_code="SWSH9", rarity=Rarities.Uncommon, - effect=unimplemented ) diff --git a/spirit/game/scripts/cards/SWSH9/Clefable_54.py b/spirit/game/scripts/cards/SWSH9/Clefable_54.py index f0e4177..660cf6a 100644 --- a/spirit/game/scripts/cards/SWSH9/Clefable_54.py +++ b/spirit/game/scripts/cards/SWSH9/Clefable_54.py @@ -1,5 +1,34 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID + + +async def moonlit_miracle(ctx): + flips = await ctx.flip_coins(3, "Moonlit Miracle") + heads = sum(1 for r in flips if r) + if heads == 0: + return + candidates = ctx.my_pokemon_in_play() + if not candidates: + return + chosen = await ctx.choose_cards( + candidates, heads, minimum=0, + prompt=f"Choose up to {heads} of your Pokémon to evolve.", + ) + if not chosen: + return + for pokemon in chosen: + logic_name = pokemon.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) + if not logic_name: + continue + picks = await ctx.search_deck( + lambda c, name=logic_name: c.get_attribute(AttrID.EVOLUTION_LOGIC_FROM) == name, + count=1, minimum=0, + prompt="Choose a card that evolves from that Pokémon.", + ) + if picks: + await ctx.evolve_pokemon(pokemon, picks[0]) + await ctx.shuffle_deck() + card = PokemonCardDef( guid="8991a2ec-eaeb-5c5f-aa46-4ced762274fd", @@ -21,9 +50,9 @@ card = PokemonCardDef( abilities=[ Attack( title="Moonlit Miracle", - game_text="Flip 3 coins. Choose a number of your Pok\u00e9mon in play up to the number of heads. For each of those Pok\u00e9mon, search your deck for a card that evolves from that Pok\u00e9mon and put it onto that Pok\u00e9mon to evolve it. Then, shuffle your deck.", + game_text="Flip 3 coins. Choose a number of your Pokémon in play up to the number of heads. For each of those Pokémon, search your deck for a card that evolves from that Pokémon and put it onto that Pokémon to evolve it. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=moonlit_miracle, ), Attack( title="Magical Shot", @@ -31,4 +60,4 @@ card = PokemonCardDef( damage=90, ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH9/CollapsedStadium_137.py b/spirit/game/scripts/cards/SWSH9/CollapsedStadium_137.py index 54b3fc4..5f7982c 100644 --- a/spirit/game/scripts/cards/SWSH9/CollapsedStadium_137.py +++ b/spirit/game/scripts/cards/SWSH9/CollapsedStadium_137.py @@ -1,7 +1,9 @@ -from spirit.game.data_utils import StadiumCardDef, unimplemented +from spirit.game.card_effects.trainers import CollapsedStadiumPassive +from spirit.game.data_utils import StadiumCardDef from spirit.game.attributes import Rarities card = StadiumCardDef( + passive=CollapsedStadiumPassive(), guid="186e6382-ea02-52af-b518-e47e75df7f50", key="SWSH9", name="com.direwolfdigital.cake.data.archetypes.trainer.CollapsedStadium.Name", @@ -10,6 +12,5 @@ card = StadiumCardDef( subtypes=["Stadium"], collector_number=137, set_code="SWSH9", - rarity=Rarities.Uncommon, - effect=unimplemented + rarity=Rarities.Uncommon ) diff --git a/spirit/game/scripts/cards/SWSH9/Crawdaunt_33.py b/spirit/game/scripts/cards/SWSH9/Crawdaunt_33.py index edc644c..d656acd 100644 --- a/spirit/game/scripts/cards/SWSH9/Crawdaunt_33.py +++ b/spirit/game/scripts/cards/SWSH9/Crawdaunt_33.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import self_energy_discard_attack card = PokemonCardDef( guid="c88eab9f-b507-5e0f-b820-ca60921958cb", @@ -26,10 +27,10 @@ card = PokemonCardDef( ), Attack( title="Crab Impact", - game_text="Discard 2 Energy from this Pok\u00e9mon.", + game_text="Discard 2 Energy from this Pokémon.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=150, - effect=unimplemented, + effect=self_energy_discard_attack(count=2), ), ], -) \ No newline at end of file +) diff --git a/spirit/game/scripts/cards/SWSH9/CynthiasAmbition_138.py b/spirit/game/scripts/cards/SWSH9/CynthiasAmbition_138.py index d44a17d..ab929b6 100644 --- a/spirit/game/scripts/cards/SWSH9/CynthiasAmbition_138.py +++ b/spirit/game/scripts/cards/SWSH9/CynthiasAmbition_138.py @@ -1,6 +1,13 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities + +async def cynthias_ambition(ctx): + """Draw to 5, or to 8 if any of your Pokemon were KO'd last turn.""" + target = 8 if ctx.kos_suffered_last_turn() > 0 else 5 + await ctx.draw_until(target) + + card = SupporterCardDef( guid="64381fea-1038-5cab-88dc-f62c827b3a6b", key="SWSH9", @@ -11,5 +18,5 @@ card = SupporterCardDef( collector_number=138, set_code="SWSH9", rarity=Rarities.Uncommon, - effect=unimplemented + effect=cynthias_ambition ) diff --git a/spirit/game/scripts/cards/SWSH9/CynthiasAmbition_169.py b/spirit/game/scripts/cards/SWSH9/CynthiasAmbition_169.py index c9e6318..1095204 100644 --- a/spirit/game/scripts/cards/SWSH9/CynthiasAmbition_169.py +++ b/spirit/game/scripts/cards/SWSH9/CynthiasAmbition_169.py @@ -1,6 +1,13 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities + +async def cynthias_ambition(ctx): + """Draw to 5, or to 8 if any of your Pokemon were KO'd last turn.""" + target = 8 if ctx.kos_suffered_last_turn() > 0 else 5 + await ctx.draw_until(target) + + card = SupporterCardDef( guid="56aebdb5-d9d2-5ec4-bb56-5e13db8d1f95", key="SWSH9", @@ -11,5 +18,5 @@ card = SupporterCardDef( collector_number=169, set_code="SWSH9", rarity=Rarities.RareUltra, - effect=unimplemented + effect=cynthias_ambition ) diff --git a/spirit/game/scripts/cards/SWSH9/CynthiasAmbition_178.py b/spirit/game/scripts/cards/SWSH9/CynthiasAmbition_178.py index 1dff395..a03869f 100644 --- a/spirit/game/scripts/cards/SWSH9/CynthiasAmbition_178.py +++ b/spirit/game/scripts/cards/SWSH9/CynthiasAmbition_178.py @@ -1,6 +1,13 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities + +async def cynthias_ambition(ctx): + """Draw to 5, or to 8 if any of your Pokemon were KO'd last turn.""" + target = 8 if ctx.kos_suffered_last_turn() > 0 else 5 + await ctx.draw_until(target) + + card = SupporterCardDef( guid="e0bb2327-e325-57a8-8b71-ea5ffdaee07a", key="SWSH9", @@ -11,5 +18,5 @@ card = SupporterCardDef( collector_number=178, set_code="SWSH9", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=cynthias_ambition ) diff --git a/spirit/game/scripts/cards/SWSH9/Dedenne_67.py b/spirit/game/scripts/cards/SWSH9/Dedenne_67.py index d3d4624..06827be 100644 --- a/spirit/game/scripts/cards/SWSH9/Dedenne_67.py +++ b/spirit/game/scripts/cards/SWSH9/Dedenne_67.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import count_prizes_remaining + + +async def dede_flash(ctx): + """20, +60 more and opponent's Active becomes Confused if they have exactly 1 Prize left.""" + bonus = count_prizes_remaining("opponent")(ctx) == 1 + await ctx.deal_damage(20 + (60 if bonus else 0)) + if bonus: + await ctx.apply_special_condition(ctx.defender, SpecialConditions.CONFUSED) card = PokemonCardDef( guid="6ad2b60b-76b3-5ed8-9724-5c7252140823", @@ -24,7 +33,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=dede_flash, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/DracovishV_114.py b/spirit/game/scripts/cards/SWSH9/DracovishV_114.py index e2fc9d8..f5760e3 100644 --- a/spirit/game/scripts/cards/SWSH9/DracovishV_114.py +++ b/spirit/game/scripts/cards/SWSH9/DracovishV_114.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID, TrainerType + +_TOOL_TYPES = (TrainerType.POKEMON_TOOL.value, TrainerType.POKEMON_TOOL_F.value) + + +async def slosh_n_crash(ctx): + """Before damage: discard all Tools from the opponent's Active. If any + were discarded this way, this attack does 120 more damage.""" + defender = ctx.defender + bonus = 0 + if defender is not None and not ctx.effects_blocked(defender): + tools = [c for c in defender.children + if c.get_attribute(AttrID.TRAINER_TYPE) in _TOOL_TYPES] + if tools: + await ctx.discard_cards(tools) + bonus = 120 + await ctx.deal_damage(60 + bonus) + card = PokemonCardDef( guid="257ca341-0fbd-543d-9343-145c83692f52", @@ -23,14 +40,14 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.WATER: 1}, damage=60, damage_operator="+", - effect=unimplemented, + effect=slosh_n_crash, ), Attack( title="Dragon Strike", game_text="During your next turn, this Pok\u00e9mon can't use Dragon Strike.", cost={PokemonTypes.GRASS: 1, PokemonTypes.WATER: 2}, damage=210, - effect=unimplemented, + locks_next_turn=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/DrampaV_128.py b/spirit/game/scripts/cards/SWSH9/DrampaV_128.py index 2f42ee5..9405750 100644 --- a/spirit/game/scripts/cards/SWSH9/DrampaV_128.py +++ b/spirit/game/scripts/cards/SWSH9/DrampaV_128.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import draw_attack +from spirit.game.card_effects.attacks_common import mill_attack card = PokemonCardDef( guid="62488140-1f03-5ffc-bc97-9992d0a4570c", @@ -23,14 +25,14 @@ card = PokemonCardDef( game_text="Draw 2 cards.", cost={PokemonTypes.COLORLESS: 1}, damage=20, - effect=unimplemented, + effect=draw_attack(2), ), Attack( title="Dragon Pulse", game_text="Discard the top 2 cards of your deck.", cost={PokemonTypes.COLORLESS: 3}, damage=160, - effect=unimplemented, + effect=mill_attack(2, opponent=False), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/Druddigon_113.py b/spirit/game/scripts/cards/SWSH9/Druddigon_113.py index 78fae7e..2f09eb2 100644 --- a/spirit/game/scripts/cards/SWSH9/Druddigon_113.py +++ b/spirit/game/scripts/cards/SWSH9/Druddigon_113.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if + + +def _kos_suffered_last_turn(ctx): + return ctx.kos_suffered_last_turn() > 0 + card = PokemonCardDef( guid="a6a09ff7-316b-5f87-9210-161d286fb919", @@ -23,7 +29,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 1, PokemonTypes.WATER: 1}, damage=40, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_kos_suffered_last_turn, 120), ), Attack( title="Dragon Claw", diff --git a/spirit/game/scripts/cards/SWSH9/Dusclops_61.py b/spirit/game/scripts/cards/SWSH9/Dusclops_61.py index 2ed065c..d0b6dfb 100644 --- a/spirit/game/scripts/cards/SWSH9/Dusclops_61.py +++ b/spirit/game/scripts/cards/SWSH9/Dusclops_61.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="5eb7fb94-8de4-5302-a714-c1be5ac76b26", @@ -25,7 +26,7 @@ card = PokemonCardDef( game_text="Your opponent's Active Pok\u00e9mon is now Confused.", cost={PokemonTypes.PSYCHIC: 1}, damage=30, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/Dusknoir_62.py b/spirit/game/scripts/cards/SWSH9/Dusknoir_62.py index 590a1bd..cea5b7f 100644 --- a/spirit/game/scripts/cards/SWSH9/Dusknoir_62.py +++ b/spirit/game/scripts/cards/SWSH9/Dusknoir_62.py @@ -1,5 +1,37 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import is_special_energy + + +def _special_transfer_condition(board, player_id, pokemon): + pkmn = board.pokemon_in_play(player_id) + if len(pkmn) < 2: + return False + return any(is_special_energy(e) for p in pkmn for e in board.attached_energies(p)) + + +async def special_transfer(ctx): + pool = [ + (e, p) for p in ctx.my_pokemon_in_play() + for e in ctx.attached_energies(p) + if is_special_energy(e) + ] + if not pool: + return + picked = await ctx.choose_cards( + [e for e, _ in pool], 1, prompt="Choose a Special Energy to move" + ) + if not picked: + return + energy = picked[0] + holder = next(p for e, p in pool if e is energy) + targets = [p for p in ctx.my_pokemon_in_play() if p is not holder] + if not targets: + return + target = await ctx.choose_pokemon(targets, "Choose a Pokémon to move the Energy to") + if target is not None: + await ctx.move_energy(energy, target) + card = PokemonCardDef( guid="1ec71624-7a88-544d-9d72-106cf636d8b1", @@ -23,7 +55,9 @@ card = PokemonCardDef( Ability( title="Special Transfer", game_text="As often as you like during your turn, you may move a Special Energy from 1 of your Pok\u00e9mon to another of your Pok\u00e9mon.", - effect=unimplemented, + activation=Activations.UNLIMITED, + condition=_special_transfer_condition, + effect=special_transfer, ), Attack( title="Devour Soul", diff --git a/spirit/game/scripts/cards/SWSH9/Duskull_60.py b/spirit/game/scripts/cards/SWSH9/Duskull_60.py index 06c1cdf..6234318 100644 --- a/spirit/game/scripts/cards/SWSH9/Duskull_60.py +++ b/spirit/game/scripts/cards/SWSH9/Duskull_60.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="26fc96de-b9b8-59c5-ae8b-1ba9af5e45d6", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Confused.", cost={PokemonTypes.PSYCHIC: 1}, damage=10, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED, flip=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/Eiscue_44.py b/spirit/game/scripts/cards/SWSH9/Eiscue_44.py index 786dce7..2dbb6b0 100644 --- a/spirit/game/scripts/cards/SWSH9/Eiscue_44.py +++ b/spirit/game/scripts/cards/SWSH9/Eiscue_44.py @@ -1,6 +1,28 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, subtypes_for from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +def _count_fusion_strike_energy(ctx): + total = 0 + for p in ctx.my_pokemon_in_play(): + for e in ctx.attached_energies(p): + if "Fusion Strike" in subtypes_for(e.archetype_id): + total += 1 + return total + + +async def block_slider(ctx): + amount = 40 * _count_fusion_strike_energy(ctx) + if amount <= 0: + return + candidates = ctx.opponent_pokemon_in_play() + if not candidates: + return + target = await ctx.choose_pokemon(candidates, "Choose 1 of your opponent's Pokémon") + if target is not None: + await ctx.deal_damage(amount, target=target) + + card = PokemonCardDef( guid="3d3731ef-4935-55e2-83e8-2f466bf5d9f1", key="SWSH9", @@ -22,7 +44,7 @@ card = PokemonCardDef( title="Block Slider", game_text="This attack does 40 damage to 1 of your opponent's Pok\u00e9mon for each Fusion Strike Energy attached to all of your Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=block_slider, ), Attack( title="Icicle Missile", diff --git a/spirit/game/scripts/cards/SWSH9/Electabuzz_46.py b/spirit/game/scripts/cards/SWSH9/Electabuzz_46.py index 19d850a..2f6bc5f 100644 --- a/spirit/game/scripts/cards/SWSH9/Electabuzz_46.py +++ b/spirit/game/scripts/cards/SWSH9/Electabuzz_46.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions card = PokemonCardDef( guid="7064be96-2387-5e34-ada4-ba07749978ec", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Thunder Wave", game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.LIGHTNING: 1}, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), Attack( title="Head Bolt", diff --git a/spirit/game/scripts/cards/SWSH9/Electivire_47.py b/spirit/game/scripts/cards/SWSH9/Electivire_47.py index df7e975..9163233 100644 --- a/spirit/game/scripts/cards/SWSH9/Electivire_47.py +++ b/spirit/game/scripts/cards/SWSH9/Electivire_47.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.attacks_common import bonus_if, damage_all_opponents + + +def _magmortar_bench_damaged(ctx) -> bool: + for p in ctx.my_bench(): + if p.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) == "Magmortar" \ + and p.get_attribute(AttrID.HP, ctx.max_hp(p)) < ctx.max_hp(p): + return True + return False card = PokemonCardDef( guid="9dbb9c63-3178-59b6-8f77-653b43c1d4d2", @@ -25,13 +34,13 @@ card = PokemonCardDef( cost={PokemonTypes.LIGHTNING: 1}, damage=30, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_magmortar_bench_damaged, 90), ), Attack( title="High-Voltage Current", game_text="This attack does 50 damage to each of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=damage_all_opponents(50), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/Empoleon_37.py b/spirit/game/scripts/cards/SWSH9/Empoleon_37.py index ac27bda..913718b 100644 --- a/spirit/game/scripts/cards/SWSH9/Empoleon_37.py +++ b/spirit/game/scripts/cards/SWSH9/Empoleon_37.py @@ -1,5 +1,26 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import snipe_attack +from spirit.game.session.constants import BENCH_CAPACITY + + +def emergency_surfacing_condition(board, player_id, pokemon): + hand = board.find_player_area(player_id, "hand") + if hand and hand.children: + return False + bench = board.find_player_area(player_id, "bench") + return bench is not None and BENCH_CAPACITY - len(bench.children) >= 1 + + +async def emergency_surfacing(ctx): + """Once per turn, from the discard pile with an empty hand: you may put + this Pokémon onto your Bench. If you do, draw 3 cards.""" + if not await ctx.ask_yes_no( + "Put Empoleon onto your Bench? If you do, draw 3 cards." + ): + return + if await ctx.bench_pokemon(ctx.source): + await ctx.draw_cards(3) card = PokemonCardDef( guid="9a89e800-00ef-5265-9849-189dd799d42c", @@ -22,13 +43,16 @@ card = PokemonCardDef( Ability( title="Emergency Surfacing", game_text="Once during your turn, if this Pok\u00e9mon is in your discard pile and you have no cards in your hand, you may put this Pok\u00e9mon onto your Bench. If you do, draw 3 cards.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + usable_from="discard", + condition=emergency_surfacing_condition, + effect=emergency_surfacing, ), Attack( title="Water Arrow", game_text="This attack does 60 damage to 1 of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.WATER: 1}, - effect=unimplemented, + effect=snipe_attack(60, pool="any", count=1), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/EnteiV_22.py b/spirit/game/scripts/cards/SWSH9/EnteiV_22.py index 7866329..077e7b2 100644 --- a/spirit/game/scripts/cards/SWSH9/EnteiV_22.py +++ b/spirit/game/scripts/cards/SWSH9/EnteiV_22.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_bench +from spirit.game.card_effects.pokemon import in_active_spot + + +async def fleet_footed(ctx): + """Once per turn, in the Active Spot: you may draw a card.""" + if await ctx.ask_yes_no("Draw a card?"): + await ctx.draw_cards(1) card = PokemonCardDef( guid="71c52931-12f6-506a-86d3-8b8146535f3c", @@ -21,7 +29,9 @@ card = PokemonCardDef( Ability( title="Fleet-Footed", game_text="Once during your turn, if this Pok\u00e9mon is in the Active Spot, you may draw a card.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=in_active_spot, + effect=fleet_footed, ), Attack( title="Burning Rondo", @@ -29,7 +39,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_bench("both"), 20, base=20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/Escavalier_101.py b/spirit/game/scripts/cards/SWSH9/Escavalier_101.py index 570ef8b..7a90513 100644 --- a/spirit/game/scripts/cards/SWSH9/Escavalier_101.py +++ b/spirit/game/scripts/cards/SWSH9/Escavalier_101.py @@ -1,5 +1,11 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, is_pokemon_v from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import snipe_attack +from spirit.game.card_effects.passives_common import takes_less_passive + + +def _is_v(pokemon) -> bool: + return is_pokemon_v(pokemon.archetype_id) card = PokemonCardDef( guid="c591b00e-62d0-53bd-96df-bb6291f7740f", @@ -23,14 +29,14 @@ card = PokemonCardDef( Ability( title="Miraculous Armor", game_text="This Pok\u00e9mon takes 100 less damage from attacks from your opponent's Pok\u00e9mon V (after applying Weakness and Resistance).", - effect=unimplemented, + passive=takes_less_passive(100, attacker_pred=_is_v), ), Attack( title="Pike", game_text="This attack also does 30 damage to 1 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 2}, damage=90, - effect=unimplemented, + effect=snipe_attack(30, pool="bench", count=1, also_base=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/Exeggutor_2.py b/spirit/game/scripts/cards/SWSH9/Exeggutor_2.py index 9e7a4d0..d4c1d7b 100644 --- a/spirit/game/scripts/cards/SWSH9/Exeggutor_2.py +++ b/spirit/game/scripts/cards/SWSH9/Exeggutor_2.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import heal_attack card = PokemonCardDef( guid="e3bdb616-1122-5f6c-b141-1ee49d5b942d", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Heal 30 damage from this Pok\u00e9mon.", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 2}, damage=70, - effect=unimplemented, + effect=heal_attack(30), ), Attack( title="Seed Bomb", diff --git a/spirit/game/scripts/cards/SWSH9/Farfetchd_115.py b/spirit/game/scripts/cards/SWSH9/Farfetchd_115.py index 0c9acf6..35b494a 100644 --- a/spirit/game/scripts/cards/SWSH9/Farfetchd_115.py +++ b/spirit/game/scripts/cards/SWSH9/Farfetchd_115.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, damage_counters_on card = PokemonCardDef( guid="3af9f171-6f57-5e69-9101-c8a3989e5811", @@ -25,7 +26,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=20, damage_operator="+", - effect=unimplemented, + effect=damage_per(damage_counters_on("defender"), 10, base=20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/Floatzel_39.py b/spirit/game/scripts/cards/SWSH9/Floatzel_39.py index b1cf2c9..24e252b 100644 --- a/spirit/game/scripts/cards/SWSH9/Floatzel_39.py +++ b/spirit/game/scripts/cards/SWSH9/Floatzel_39.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import recover_from_discard +from spirit.game.session.effects import is_item_card card = PokemonCardDef( guid="6aa24680-bc8d-5639-863a-e12e0e6c6e39", @@ -23,7 +25,7 @@ card = PokemonCardDef( title="Floatify", game_text="Put up to 2 Item cards from your discard pile into your hand.", cost={PokemonTypes.WATER: 1}, - effect=unimplemented, + effect=recover_from_discard(is_item_card, count=2, to="hand"), ), Attack( title="Water Gun", diff --git a/spirit/game/scripts/cards/SWSH9/FlygonV_106.py b/spirit/game/scripts/cards/SWSH9/FlygonV_106.py index d81bf1f..d85bdac 100644 --- a/spirit/game/scripts/cards/SWSH9/FlygonV_106.py +++ b/spirit/game/scripts/cards/SWSH9/FlygonV_106.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import defender_is_vmax + + +async def draconic_impulse(ctx): + if defender_is_vmax(ctx): + await ctx.deal_damage(320) + await ctx.discard_energy_from(ctx.attacker, 3) + else: + await ctx.deal_damage(160) + card = PokemonCardDef( guid="f0b0b7aa-4f77-55e5-a5a9-db7736002ab7", @@ -28,7 +38,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=160, damage_operator="+", - effect=unimplemented, + effect=draconic_impulse, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/FlygonV_164.py b/spirit/game/scripts/cards/SWSH9/FlygonV_164.py index 054bb24..6a266dd 100644 --- a/spirit/game/scripts/cards/SWSH9/FlygonV_164.py +++ b/spirit/game/scripts/cards/SWSH9/FlygonV_164.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import defender_is_vmax + + +async def draconic_impulse(ctx): + if defender_is_vmax(ctx): + await ctx.deal_damage(320) + await ctx.discard_energy_from(ctx.attacker, 3) + else: + await ctx.deal_damage(160) + card = PokemonCardDef( guid="c82acf60-ec65-5e6e-86dc-8c4483d83f4b", @@ -28,7 +38,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1}, damage=160, damage_operator="+", - effect=unimplemented, + effect=draconic_impulse, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/Flygon_76.py b/spirit/game/scripts/cards/SWSH9/Flygon_76.py index c542a72..257f1be 100644 --- a/spirit/game/scripts/cards/SWSH9/Flygon_76.py +++ b/spirit/game/scripts/cards/SWSH9/Flygon_76.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.attacks_common import damage_per + + +def _defender_retreat_cost(ctx): + defender = ctx.defender + return int(defender.get_attribute(AttrID.RETREAT_COST) or 0) if defender else 0 + card = PokemonCardDef( guid="9a630c50-f530-5862-b4a7-27b7e95ebe12", @@ -25,7 +32,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1}, damage=50, damage_operator="x", - effect=unimplemented, + effect=damage_per(_defender_retreat_cost, 50), ), Attack( title="Blasting Wind", diff --git a/spirit/game/scripts/cards/SWSH9/FreshWaterSet_139.py b/spirit/game/scripts/cards/SWSH9/FreshWaterSet_139.py index 9834006..b5b929d 100644 --- a/spirit/game/scripts/cards/SWSH9/FreshWaterSet_139.py +++ b/spirit/game/scripts/cards/SWSH9/FreshWaterSet_139.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import ItemCardDef, unimplemented +from spirit.game.data_utils import ItemCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.support_common import heal_item, requires_damaged_pokemon card = ItemCardDef( guid="84fa460d-a5b0-555b-bb3f-08a04e4d173b", @@ -11,5 +12,6 @@ card = ItemCardDef( collector_number=139, set_code="SWSH9", rarity=Rarities.Uncommon, - effect=unimplemented + effect=heal_item(20, scope="each_own"), + condition=requires_damaged_pokemon() ) diff --git a/spirit/game/scripts/cards/SWSH9/FriendsinGalar_140.py b/spirit/game/scripts/cards/SWSH9/FriendsinGalar_140.py index 5231d61..2d7447f 100644 --- a/spirit/game/scripts/cards/SWSH9/FriendsinGalar_140.py +++ b/spirit/game/scripts/cards/SWSH9/FriendsinGalar_140.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.support_common import draw_attack card = SupporterCardDef( guid="a2b35997-7503-5e2b-848d-b967d745fec4", @@ -11,5 +12,5 @@ card = SupporterCardDef( collector_number=140, set_code="SWSH9", rarity=Rarities.Uncommon, - effect=unimplemented + effect=draw_attack(3) ) diff --git a/spirit/game/scripts/cards/SWSH9/GalarianArticunoV_181.py b/spirit/game/scripts/cards/SWSH9/GalarianArticunoV_181.py index 21c2176..2e0de86 100644 --- a/spirit/game/scripts/cards/SWSH9/GalarianArticunoV_181.py +++ b/spirit/game/scripts/cards/SWSH9/GalarianArticunoV_181.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.card_effects.support_common import requires_hand + + +async def reconstitute(ctx): + """Discard 2 cards from your hand. Once during your turn, you may draw a + card.""" + await ctx.discard_from_hand(2, prompt="Discard 2 cards to use Reconstitute") + if await ctx.ask_yes_no("Draw a card?"): + await ctx.draw_cards(1) + card = PokemonCardDef( guid="7941c00f-870d-57f7-bea8-741ff86475e6", @@ -22,14 +33,16 @@ card = PokemonCardDef( Ability( title="Reconstitute", game_text="You must discard 2 cards from your hand in order to use this Ability. Once during your turn, you may draw a card.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=requires_hand(n=2), + effect=reconstitute, ), Attack( title="Psyray", game_text="Your opponent's Active Pok\u00e9mon is now Confused.", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 2}, damage=110, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/GalarianMoltresV_183.py b/spirit/game/scripts/cards/SWSH9/GalarianMoltresV_183.py index f079031..4d75993 100644 --- a/spirit/game/scripts/cards/SWSH9/GalarianMoltresV_183.py +++ b/spirit/game/scripts/cards/SWSH9/GalarianMoltresV_183.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack +from spirit.game.card_effects.pokemon import is_energy_card +from spirit.game.card_effects.support_common import attach_from_discard, requires_discard + + +def is_darkness_energy_card(card): + return is_energy_card(card) and PokemonTypes.DARKNESS.value in (card.get_attribute(AttrID.POKEMON_TYPES) or []) + card = PokemonCardDef( guid="589a7702-ef64-5409-a244-743e7dea9559", @@ -21,14 +29,17 @@ card = PokemonCardDef( Ability( title="Direflame Wings", game_text="Once during your turn, you may attach a Darkness Energy card from your discard pile to this Pok\u00e9mon. You can't use more than 1 Direflame Wings Ability each turn.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + shared_once_per_turn="Direflame Wings", + condition=requires_discard(is_darkness_energy_card), + effect=attach_from_discard(predicate=is_darkness_energy_card, count=1, target="self"), ), Attack( title="Aura Burn", game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.DARKNESS: 2, PokemonTypes.COLORLESS: 1}, damage=190, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/GalarianZapdosV_182.py b/spirit/game/scripts/cards/SWSH9/GalarianZapdosV_182.py index 7535444..e2af6f1 100644 --- a/spirit/game/scripts/cards/SWSH9/GalarianZapdosV_182.py +++ b/spirit/game/scripts/cards/SWSH9/GalarianZapdosV_182.py @@ -1,5 +1,29 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, is_pokemon_v from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import discard_opponent_energy_attack +from spirit.game.session.passives import Passive + + +class FightingInstinctPassive(Passive): + def modify_attack_cost(self, cost, pokemon, carrier, board): + if pokemon is not carrier: + return cost + owner = carrier.owning_player_id + opponent = next((pid for pid in board.player_ids if pid != owner), None) + if opponent is None or "Colorless" not in cost: + return cost + discount = sum( + 1 for p in board.pokemon_in_play(opponent) if is_pokemon_v(p.archetype_id) + ) + if discount <= 0: + return cost + remaining = cost["Colorless"] - discount + if remaining > 0: + cost["Colorless"] = remaining + else: + del cost["Colorless"] + return cost + card = PokemonCardDef( guid="0e11a207-a236-54aa-93c4-486ec1c139d8", @@ -21,14 +45,14 @@ card = PokemonCardDef( Ability( title="Fighting Instinct", game_text="This Pok\u00e9mon's attacks cost Colorless less for each of your opponent's Pok\u00e9mon V in play.", - effect=unimplemented, + passive=FightingInstinctPassive(), ), Attack( title="Thunderous Kick", game_text="Before doing damage, discard a Special Energy from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 3}, damage=170, - effect=unimplemented, + effect=discard_opponent_energy_attack(special_only=True, after_damage=False), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/Garchomp_109.py b/spirit/game/scripts/cards/SWSH9/Garchomp_109.py index 4c707bb..1cba55f 100644 --- a/spirit/game/scripts/cards/SWSH9/Garchomp_109.py +++ b/spirit/game/scripts/cards/SWSH9/Garchomp_109.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import mill_attack +from spirit.game.card_effects.passives_common import apply_protection + + +async def sonic_slip(ctx): + if await ctx.ask_yes_no( + "Prevent all damage from and effects of attacks from your opponent's " + "Pokémon done to this Pokémon until the end of your opponent's next turn?" + ): + await apply_protection(ctx, prevent=True, effects_too=True) + card = PokemonCardDef( guid="403b03e9-6fbb-5749-91de-136851b5eb17", @@ -21,14 +32,15 @@ card = PokemonCardDef( Ability( title="Sonic Slip", game_text="When you play this Pok\u00e9mon from your hand to evolve 1 of your Pok\u00e9mon during your turn, you may prevent all damage from and effects of attacks from your opponent's Pok\u00e9mon done to this Pok\u00e9mon until the end of your opponent's next turn.", - effect=unimplemented, + trigger=Triggers.ON_EVOLVE, + effect=sonic_slip, ), Attack( title="Dragonblade", game_text="Discard the top 2 cards of your deck.", cost={PokemonTypes.WATER: 1, PokemonTypes.FIGHTING: 1}, damage=160, - effect=unimplemented, + effect=mill_attack(2, opponent=False), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/Gloria_141.py b/spirit/game/scripts/cards/SWSH9/Gloria_141.py index 20a8510..b0da78c 100644 --- a/spirit/game/scripts/cards/SWSH9/Gloria_141.py +++ b/spirit/game/scripts/cards/SWSH9/Gloria_141.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef, has_rule_box from spirit.game.attributes import Rarities +from spirit.game.card_effects.support_common import search_to_bench +from spirit.game.session.effects import is_basic_pokemon + + +def _no_rule_box_basic(card) -> bool: + return is_basic_pokemon(card) and not has_rule_box(card.archetype_id) + card = SupporterCardDef( guid="043fc87d-233f-5a1b-8e38-ab7aecc0154c", @@ -11,5 +18,8 @@ card = SupporterCardDef( collector_number=141, set_code="SWSH9", rarity=Rarities.Uncommon, - effect=unimplemented + effect=search_to_bench( + _no_rule_box_basic, count=3, + prompt="Choose up to 3 Basic Pokémon without a Rule Box to put onto your Bench.", + ), ) diff --git a/spirit/game/scripts/cards/SWSH9/Golurk_83.py b/spirit/game/scripts/cards/SWSH9/Golurk_83.py index c508570..9263bf5 100644 --- a/spirit/game/scripts/cards/SWSH9/Golurk_83.py +++ b/spirit/game/scripts/cards/SWSH9/Golurk_83.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_hand card = PokemonCardDef( guid="87e25b28-beca-54d9-b983-ef1ed488dc78", @@ -25,7 +26,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=30, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_hand("mine"), 10, base=30), ), Attack( title="Mega Punch", diff --git a/spirit/game/scripts/cards/SWSH9/GranbullV_159.py b/spirit/game/scripts/cards/SWSH9/GranbullV_159.py index 6d26c25..43b4146 100644 --- a/spirit/game/scripts/cards/SWSH9/GranbullV_159.py +++ b/spirit/game/scripts/cards/SWSH9/GranbullV_159.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import ( + damage_per, damage_counters_on, recoil_attack, +) card = PokemonCardDef( guid="374a61e3-be20-59a8-993e-dcef02dbe786", @@ -24,14 +27,14 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=30, damage_operator="+", - effect=unimplemented, + effect=damage_per(damage_counters_on("self"), 10, base=30), ), Attack( title="Bull Dash", game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.PSYCHIC: 2, PokemonTypes.COLORLESS: 1}, damage=190, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/GranbullV_57.py b/spirit/game/scripts/cards/SWSH9/GranbullV_57.py index 32411cf..f044bff 100644 --- a/spirit/game/scripts/cards/SWSH9/GranbullV_57.py +++ b/spirit/game/scripts/cards/SWSH9/GranbullV_57.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import ( + damage_per, damage_counters_on, recoil_attack, +) card = PokemonCardDef( guid="e103d602-43ea-5c22-85fe-332fce7cfcb0", @@ -24,14 +27,14 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=30, damage_operator="+", - effect=unimplemented, + effect=damage_per(damage_counters_on("self"), 10, base=30), ), Attack( title="Bull Dash", game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.PSYCHIC: 2, PokemonTypes.COLORLESS: 1}, damage=190, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/Grimer_84.py b/spirit/game/scripts/cards/SWSH9/Grimer_84.py index 7cd3049..02719eb 100644 --- a/spirit/game/scripts/cards/SWSH9/Grimer_84.py +++ b/spirit/game/scripts/cards/SWSH9/Grimer_84.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="32170af9-1865-57e1-b46a-daa1285d0ddc", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Poison Gas", game_text="Your opponent's Active Pok\u00e9mon is now Poisoned.", cost={PokemonTypes.DARKNESS: 1}, - effect=unimplemented, + effect=condition_attack(SpecialConditions.POISONED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/Grimmsnarl_94.py b/spirit/game/scripts/cards/SWSH9/Grimmsnarl_94.py index dee0fc9..77d7776 100644 --- a/spirit/game/scripts/cards/SWSH9/Grimmsnarl_94.py +++ b/spirit/game/scripts/cards/SWSH9/Grimmsnarl_94.py @@ -1,5 +1,20 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import count_energy + + +async def longhair_shot(ctx): + """30 damage to 1 of your opponent's Pokemon for each Darkness Energy attached to this Pokemon.""" + count = count_energy("self", energy_type=PokemonTypes.DARKNESS)(ctx) + if count <= 0: + return + targets = ctx.opponent_pokemon_in_play() + target = await ctx.choose_pokemon(targets, "Choose 1 of your opponent's Pokémon") + if target is None: + return + apply_mod = target is ctx.opponent_active() + await ctx.deal_damage(30 * count, target=target, apply_modifiers=apply_mod) + card = PokemonCardDef( guid="1d3a5b87-4fe8-5598-a361-0a998602c6b9", @@ -23,7 +38,7 @@ card = PokemonCardDef( title="Longhair Shot", game_text="This attack does 30 damage to 1 of your opponent's Pok\u00e9mon for each Darkness Energy attached to this Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.DARKNESS: 1}, - effect=unimplemented, + effect=longhair_shot, ), Attack( title="Darkness Fang", diff --git a/spirit/game/scripts/cards/SWSH9/Grotle_7.py b/spirit/game/scripts/cards/SWSH9/Grotle_7.py index b46150f..0af84d0 100644 --- a/spirit/game/scripts/cards/SWSH9/Grotle_7.py +++ b/spirit/game/scripts/cards/SWSH9/Grotle_7.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.session.effects import is_pokemon_card +from spirit.game.card_effects.support_common import search_to_hand + + +def _is_grass_pokemon(card): + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_pokemon_card(card) and PokemonTypes.GRASS.value in types + card = PokemonCardDef( guid="5c87b09b-c170-5200-b71d-e21ddb8d18fb", @@ -22,7 +30,11 @@ card = PokemonCardDef( Ability( title="Sun-Drenched Shell", game_text="Once during your turn, you may search your deck for a Grass Pok\u00e9mon, reveal it, and put it into your hand. Then, shuffle your deck.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + effect=search_to_hand( + _is_grass_pokemon, count=1, minimum=0, reveal=True, + prompt="Choose a Grass Pok\u00e9mon to reveal and put into your hand.", + ), ), Attack( title="Razor Leaf", diff --git a/spirit/game/scripts/cards/SWSH9/Hawlucha_127.py b/spirit/game/scripts/cards/SWSH9/Hawlucha_127.py index bd2d9d8..98b936a 100644 --- a/spirit/game/scripts/cards/SWSH9/Hawlucha_127.py +++ b/spirit/game/scripts/cards/SWSH9/Hawlucha_127.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if +from spirit.game.card_effects.support_common import attach_from_discard +from spirit.game.card_effects.trainers import is_basic_energy_card +from spirit.game.card_effects.passives_common import is_in_active_spot +from spirit.game.session.effects import is_evolution_pokemon + + +def _defender_is_evolution(ctx): + defender = ctx.opponent_active() + return defender is not None and is_evolution_pokemon(defender) + card = PokemonCardDef( guid="f37e9d8d-47bb-57b9-bb7c-3a836a6bb2d8", @@ -23,7 +34,11 @@ card = PokemonCardDef( title="Showboating Pose", game_text="Attach up to 2 basic Energy cards from your discard pile to 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=attach_from_discard( + predicate=is_basic_energy_card, count=2, minimum=0, + target=lambda p: not is_in_active_spot(p), + prompt="Choose up to 2 basic Energy cards to attach to a Benched Pok\u00e9mon", + ), ), Attack( title="Cross-Cut", @@ -31,7 +46,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 1}, damage=30, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_defender_is_evolution, 30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/Haxorus_112.py b/spirit/game/scripts/cards/SWSH9/Haxorus_112.py index 1af5755..b41e579 100644 --- a/spirit/game/scripts/cards/SWSH9/Haxorus_112.py +++ b/spirit/game/scripts/cards/SWSH9/Haxorus_112.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import recoil_attack card = PokemonCardDef( guid="24c7c2c3-e8c5-5058-bdd4-6645aa43f071", @@ -28,7 +29,7 @@ card = PokemonCardDef( game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.METAL: 1}, damage=160, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/Heatran_100.py b/spirit/game/scripts/cards/SWSH9/Heatran_100.py index 0c1c6ed..7fef09b 100644 --- a/spirit/game/scripts/cards/SWSH9/Heatran_100.py +++ b/spirit/game/scripts/cards/SWSH9/Heatran_100.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if +from spirit.game.card_effects.passives_common import protect_next_turn +from spirit.game.card_effects.pokemon import energy_provides_type + + +def _has_fire_energy(ctx): + return any(energy_provides_type(e, PokemonTypes.FIRE.value) + for e in ctx.attached_energies(ctx.attacker)) + card = PokemonCardDef( guid="8dd80462-ad54-5b1c-960d-451fc62d77dc", @@ -24,7 +33,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, this Pok\u00e9mon takes 30 less damage from attacks (after applying Weakness and Resistance).", cost={PokemonTypes.COLORLESS: 2}, damage=30, - effect=unimplemented, + effect=protect_next_turn(reduce=30), ), Attack( title="Iron Hammer", @@ -32,7 +41,7 @@ card = PokemonCardDef( cost={PokemonTypes.METAL: 2, PokemonTypes.COLORLESS: 1}, damage=80, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_has_fire_energy, 80), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/Hitmontop_72.py b/spirit/game/scripts/cards/SWSH9/Hitmontop_72.py index 1acd53e..ed7d6e5 100644 --- a/spirit/game/scripts/cards/SWSH9/Hitmontop_72.py +++ b/spirit/game/scripts/cards/SWSH9/Hitmontop_72.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import draw_attack card = PokemonCardDef( guid="c5d23b2f-02d9-56d3-b1b6-6cf2dfe04484", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Draw a card.", cost={PokemonTypes.COLORLESS: 1}, damage=20, - effect=unimplemented, + effect=draw_attack(1), ), Attack( title="Cyclone Kick", diff --git a/spirit/game/scripts/cards/SWSH9/HonchkrowV_161.py b/spirit/game/scripts/cards/SWSH9/HonchkrowV_161.py index ca91cf3..200edf7 100644 --- a/spirit/game/scripts/cards/SWSH9/HonchkrowV_161.py +++ b/spirit/game/scripts/cards/SWSH9/HonchkrowV_161.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.passives import Passive + + +class BossPocketsPassive(Passive): + def tool_capacity(self, pokemon, carrier): + return 4 if pokemon is carrier else 1 + + +async def fearsome_shadow(ctx): + """Your opponent reveals their hand.""" + await ctx.deal_damage() + await ctx.reveal_hand(of_player=ctx.opponent_id) + card = PokemonCardDef( guid="f89cd2ae-9915-5fc8-a159-0d66cbe1c5bf", @@ -22,14 +35,14 @@ card = PokemonCardDef( Ability( title="Boss Pockets", game_text="This Pok\u00e9mon may have up to 4 Pok\u00e9mon Tools attached to it. If it loses this Ability, discard Pok\u00e9mon Tools from it until only 1 remains.", - effect=unimplemented, + passive=BossPocketsPassive(), ), Attack( title="Fearsome Shadow", game_text="Your opponent reveals their hand.", cost={PokemonTypes.DARKNESS: 2, PokemonTypes.COLORLESS: 1}, damage=130, - effect=unimplemented, + effect=fearsome_shadow, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/HonchkrowV_162.py b/spirit/game/scripts/cards/SWSH9/HonchkrowV_162.py index cb94718..a92e695 100644 --- a/spirit/game/scripts/cards/SWSH9/HonchkrowV_162.py +++ b/spirit/game/scripts/cards/SWSH9/HonchkrowV_162.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.passives import Passive + + +class BossPocketsPassive(Passive): + def tool_capacity(self, pokemon, carrier): + return 4 if pokemon is carrier else 1 + + +async def fearsome_shadow(ctx): + """Your opponent reveals their hand.""" + await ctx.deal_damage() + await ctx.reveal_hand(of_player=ctx.opponent_id) + card = PokemonCardDef( guid="19a60e2c-584e-5106-bbda-42b0070e9dd3", @@ -22,14 +35,14 @@ card = PokemonCardDef( Ability( title="Boss Pockets", game_text="This Pok\u00e9mon may have up to 4 Pok\u00e9mon Tools attached to it. If it loses this Ability, discard Pok\u00e9mon Tools from it until only 1 remains.", - effect=unimplemented, + passive=BossPocketsPassive(), ), Attack( title="Fearsome Shadow", game_text="Your opponent reveals their hand.", cost={PokemonTypes.DARKNESS: 2, PokemonTypes.COLORLESS: 1}, damage=130, - effect=unimplemented, + effect=fearsome_shadow, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/HonchkrowV_88.py b/spirit/game/scripts/cards/SWSH9/HonchkrowV_88.py index 2dece64..f3be012 100644 --- a/spirit/game/scripts/cards/SWSH9/HonchkrowV_88.py +++ b/spirit/game/scripts/cards/SWSH9/HonchkrowV_88.py @@ -1,5 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.passives import Passive + + +class BossPocketsPassive(Passive): + def tool_capacity(self, pokemon, carrier): + return 4 if pokemon is carrier else 1 + + +async def fearsome_shadow(ctx): + """Your opponent reveals their hand.""" + await ctx.deal_damage() + await ctx.reveal_hand(of_player=ctx.opponent_id) + card = PokemonCardDef( guid="f1f656b0-2e01-5cbe-b7fb-724e961ce314", @@ -22,14 +35,14 @@ card = PokemonCardDef( Ability( title="Boss Pockets", game_text="This Pok\u00e9mon may have up to 4 Pok\u00e9mon Tools attached to it. If it loses this Ability, discard Pok\u00e9mon Tools from it until only 1 remains.", - effect=unimplemented, + passive=BossPocketsPassive(), ), Attack( title="Fearsome Shadow", game_text="Your opponent reveals their hand.", cost={PokemonTypes.DARKNESS: 2, PokemonTypes.COLORLESS: 1}, damage=130, - effect=unimplemented, + effect=fearsome_shadow, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/HuntingGloves_142.py b/spirit/game/scripts/cards/SWSH9/HuntingGloves_142.py index b44b088..987d1c1 100644 --- a/spirit/game/scripts/cards/SWSH9/HuntingGloves_142.py +++ b/spirit/game/scripts/cards/SWSH9/HuntingGloves_142.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import PokemonToolCardDef +from spirit.game.attributes import Rarities, PokemonTypes +from spirit.game.card_effects.passives_common import typed_damage_boost_tool card = PokemonToolCardDef( guid="e629c556-d9dd-5a75-8ee7-8e688411c43c", @@ -11,5 +12,5 @@ card = PokemonToolCardDef( collector_number=142, set_code="SWSH9", rarity=Rarities.Uncommon, - effect=unimplemented + passive=typed_damage_boost_tool(PokemonTypes.DRAGON, 30), ) diff --git a/spirit/game/scripts/cards/SWSH9/Impidimp_92.py b/spirit/game/scripts/cards/SWSH9/Impidimp_92.py index e40397c..1c02f19 100644 --- a/spirit/game/scripts/cards/SWSH9/Impidimp_92.py +++ b/spirit/game/scripts/cards/SWSH9/Impidimp_92.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_bench card = PokemonCardDef( guid="379080db-d372-5f98-9060-7288f7019854", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Call for Family", game_text="Search your deck for a Basic Pok\u00e9mon and put it onto your Bench. Then, shuffle your deck.", cost={PokemonTypes.DARKNESS: 1}, - effect=unimplemented, + effect=search_to_bench(), ), Attack( title="Bite", diff --git a/spirit/game/scripts/cards/SWSH9/Infernape_26.py b/spirit/game/scripts/cards/SWSH9/Infernape_26.py index 8df2857..098d40a 100644 --- a/spirit/game/scripts/cards/SWSH9/Infernape_26.py +++ b/spirit/game/scripts/cards/SWSH9/Infernape_26.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import is_energy_card +from spirit.game.card_effects.attacks_common import self_energy_discard_attack + + +async def infernal_vortex(ctx): + """Reveal the top 5 cards of your deck. This attack does 80 damage for + each Energy card found there. Then, discard those Energy cards and + shuffle the other cards back into your deck.""" + top = ctx.deck_top(5) + if top: + await ctx.reveal_cards(top) + energies = [c for c in top if is_energy_card(c)] + await ctx.deal_damage(80 * len(energies)) + if energies: + await ctx.discard_cards(energies) + await ctx.shuffle_deck() + card = PokemonCardDef( guid="aa9e8727-5d28-54ec-ab51-d7ab69da6955", @@ -25,14 +42,14 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 1}, damage=80, damage_operator="x", - effect=unimplemented, + effect=infernal_vortex, ), Attack( title="Burning Kick", game_text="Discard all Energy from this Pok\u00e9mon.", cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 1}, damage=160, - effect=unimplemented, + effect=self_energy_discard_attack(all_energy=True, before_damage=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/Kindler_143.py b/spirit/game/scripts/cards/SWSH9/Kindler_143.py index 6ce456c..5696235 100644 --- a/spirit/game/scripts/cards/SWSH9/Kindler_143.py +++ b/spirit/game/scripts/cards/SWSH9/Kindler_143.py @@ -1,5 +1,28 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import SupporterCardDef +from spirit.game.attributes import AttrID, PokemonTypes, Rarities +from spirit.game.card_effects.pokemon import is_energy_card +from spirit.game.card_effects.support_common import look_at_top + + +def _is_fire_energy_card(card) -> bool: + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_energy_card(card) and PokemonTypes.FIRE.value in types + + +def _kindler_condition(board, player_id): + hand = board.find_player_area(player_id, "hand") + return bool(hand) and any(_is_fire_energy_card(c) for c in hand.children) + + +async def kindler(ctx): + """Discard a Fire Energy from hand, then look at top 7 for up to 2 to hand.""" + discarded = await ctx.discard_from_hand( + 1, predicate=_is_fire_energy_card, prompt="Discard a Fire Energy card" + ) + if not discarded: + return + await look_at_top(7, take=2, rest="shuffle", minimum=0)(ctx) + card = SupporterCardDef( guid="fb1e692d-aa85-55f1-8cf8-62d0569032d3", @@ -11,5 +34,6 @@ card = SupporterCardDef( collector_number=143, set_code="SWSH9", rarity=Rarities.Uncommon, - effect=unimplemented + condition=_kindler_condition, + effect=kindler, ) diff --git a/spirit/game/scripts/cards/SWSH9/Kindler_170.py b/spirit/game/scripts/cards/SWSH9/Kindler_170.py index 5bbabc8..ee43e25 100644 --- a/spirit/game/scripts/cards/SWSH9/Kindler_170.py +++ b/spirit/game/scripts/cards/SWSH9/Kindler_170.py @@ -1,5 +1,28 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import SupporterCardDef +from spirit.game.attributes import AttrID, PokemonTypes, Rarities +from spirit.game.card_effects.pokemon import is_energy_card +from spirit.game.card_effects.support_common import look_at_top + + +def _is_fire_energy_card(card) -> bool: + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_energy_card(card) and PokemonTypes.FIRE.value in types + + +def _kindler_condition(board, player_id): + hand = board.find_player_area(player_id, "hand") + return bool(hand) and any(_is_fire_energy_card(c) for c in hand.children) + + +async def kindler(ctx): + """Discard a Fire Energy from hand, then look at top 7 for up to 2 to hand.""" + discarded = await ctx.discard_from_hand( + 1, predicate=_is_fire_energy_card, prompt="Discard a Fire Energy card" + ) + if not discarded: + return + await look_at_top(7, take=2, rest="shuffle", minimum=0)(ctx) + card = SupporterCardDef( guid="0089ebbb-0157-503a-8871-f7c25f660d8b", @@ -11,5 +34,6 @@ card = SupporterCardDef( collector_number=170, set_code="SWSH9", rarity=Rarities.RareUltra, - effect=unimplemented + condition=_kindler_condition, + effect=kindler, ) diff --git a/spirit/game/scripts/cards/SWSH9/Kindler_179.py b/spirit/game/scripts/cards/SWSH9/Kindler_179.py index b00390d..5694ed8 100644 --- a/spirit/game/scripts/cards/SWSH9/Kindler_179.py +++ b/spirit/game/scripts/cards/SWSH9/Kindler_179.py @@ -1,5 +1,28 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import SupporterCardDef +from spirit.game.attributes import AttrID, PokemonTypes, Rarities +from spirit.game.card_effects.pokemon import is_energy_card +from spirit.game.card_effects.support_common import look_at_top + + +def _is_fire_energy_card(card) -> bool: + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_energy_card(card) and PokemonTypes.FIRE.value in types + + +def _kindler_condition(board, player_id): + hand = board.find_player_area(player_id, "hand") + return bool(hand) and any(_is_fire_energy_card(c) for c in hand.children) + + +async def kindler(ctx): + """Discard a Fire Energy from hand, then look at top 7 for up to 2 to hand.""" + discarded = await ctx.discard_from_hand( + 1, predicate=_is_fire_energy_card, prompt="Discard a Fire Energy card" + ) + if not discarded: + return + await look_at_top(7, take=2, rest="shuffle", minimum=0)(ctx) + card = SupporterCardDef( guid="0a208647-afc2-5b9e-9e6c-fb290dc8eaf0", @@ -11,5 +34,6 @@ card = SupporterCardDef( collector_number=179, set_code="SWSH9", rarity=Rarities.RareRainbow, - effect=unimplemented + condition=_kindler_condition, + effect=kindler, ) diff --git a/spirit/game/scripts/cards/SWSH9/KinglerVMAX_29.py b/spirit/game/scripts/cards/SWSH9/KinglerVMAX_29.py index 3720fb4..4c0c8e2 100644 --- a/spirit/game/scripts/cards/SWSH9/KinglerVMAX_29.py +++ b/spirit/game/scripts/cards/SWSH9/KinglerVMAX_29.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.support_common import search_attach_energy +from spirit.game.card_effects.attacks_common import recoil_attack +from spirit.game.card_effects.trainers import is_energy_card + + +def _is_water_energy_card(card) -> bool: + types = card.get_attribute(AttrID.POKEMON_TYPES) or [] + return is_energy_card(card) and PokemonTypes.WATER.value in types card = PokemonCardDef( guid="81c73b9b-e186-5cd0-90fa-75dabdf97620", @@ -23,14 +31,14 @@ card = PokemonCardDef( title="Bubbles Galore", game_text="Search your deck for up to 5 Water Energy cards and attach them to your Pok\u00e9mon in any way you like. Then, shuffle your deck.", cost={PokemonTypes.WATER: 1}, - effect=unimplemented, + effect=search_attach_energy(predicate=_is_water_energy_card, count=5), ), Attack( title="G-Max Pincer", game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, damage=240, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/KinglerV_28.py b/spirit/game/scripts/cards/SWSH9/KinglerV_28.py index 67b6e9f..cced81d 100644 --- a/spirit/game/scripts/cards/SWSH9/KinglerV_28.py +++ b/spirit/game/scripts/cards/SWSH9/KinglerV_28.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_or_nothing, recoil_attack +from spirit.game.card_effects.support_common import search_attach_energy +from spirit.game.card_effects.pokemon import is_energy_card, energy_provides_type + + +def _is_water_energy(card): + return is_energy_card(card) and energy_provides_type(card, PokemonTypes.WATER) card = PokemonCardDef( guid="a6ba6ab5-9512-50b2-b521-e393e041a8eb", @@ -22,14 +29,14 @@ card = PokemonCardDef( title="Falling Bubbles", game_text="Flip a coin. If heads, search your deck for up to 5 Water Energy cards and attach them to your Pok\u00e9mon in any way you like. Then, shuffle your deck.", cost={PokemonTypes.WATER: 1}, - effect=unimplemented, + effect=flip_or_nothing(then=search_attach_energy(_is_water_energy, count=5)), ), Attack( title="Raging Pincer", game_text="This Pok\u00e9mon also does 30 damage to itself.", cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, damage=200, - effect=unimplemented, + effect=recoil_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/Klang_103.py b/spirit/game/scripts/cards/SWSH9/Klang_103.py index de0bd85..7fcfe75 100644 --- a/spirit/game/scripts/cards/SWSH9/Klang_103.py +++ b/spirit/game/scripts/cards/SWSH9/Klang_103.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.passives_common import protect_next_turn card = PokemonCardDef( guid="bb9cb5cf-a959-557f-a791-c5271eaa6742", @@ -30,7 +31,7 @@ card = PokemonCardDef( game_text="During your opponent's next turn, this Pok\u00e9mon takes 30 less damage from attacks (after applying Weakness and Resistance).", cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 2}, damage=70, - effect=unimplemented, + effect=protect_next_turn(reduce=30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/Klinklang_104.py b/spirit/game/scripts/cards/SWSH9/Klinklang_104.py index 14b2c2d..206f699 100644 --- a/spirit/game/scripts/cards/SWSH9/Klinklang_104.py +++ b/spirit/game/scripts/cards/SWSH9/Klinklang_104.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus +from spirit.game.card_effects.passives_common import takes_less_passive +from spirit.game.session.effects import is_basic_pokemon + + +def _protects_own_basics(target, carrier): + return target.owning_player_id == carrier.owning_player_id and is_basic_pokemon(target) card = PokemonCardDef( guid="8dcba8aa-fbbb-5e73-a3ad-5501d3c8f8e5", @@ -23,7 +30,7 @@ card = PokemonCardDef( Ability( title="Gear Wall", game_text="Your Basic Pok\u00e9mon take 20 less damage from attacks from your opponent's Pok\u00e9mon (after applying Weakness and Resistance).", - effect=unimplemented, + passive=takes_less_passive(20, protects=_protects_own_basics), ), Attack( title="Tumbling Attack", @@ -31,7 +38,7 @@ card = PokemonCardDef( cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 2}, damage=90, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(90), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/Lapras_31.py b/spirit/game/scripts/cards/SWSH9/Lapras_31.py index 979d767..0e7bff7 100644 --- a/spirit/game/scripts/cards/SWSH9/Lapras_31.py +++ b/spirit/game/scripts/cards/SWSH9/Lapras_31.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions + + +async def raging_freeze(ctx): + """110 damage. If any of your Pokémon were KO'd by an opponent's attack + last turn, your opponent's Active Pokémon is now Paralyzed.""" + await ctx.deal_damage() + if ctx.kos_suffered_last_turn(ctx.player_id) > 0: + await ctx.apply_special_condition(ctx.defender, SpecialConditions.PARALYZED) + card = PokemonCardDef( guid="7056dc3d-cb66-5e24-bdb9-adc33312ce30", @@ -23,7 +32,7 @@ card = PokemonCardDef( game_text="If any of your Pok\u00e9mon were Knocked Out by damage from an attack from your opponent's Pok\u00e9mon during their last turn, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1}, damage=110, - effect=unimplemented, + effect=raging_freeze, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/Liepard_91.py b/spirit/game/scripts/cards/SWSH9/Liepard_91.py index 3cd13dc..cf48753 100644 --- a/spirit/game/scripts/cards/SWSH9/Liepard_91.py +++ b/spirit/game/scripts/cards/SWSH9/Liepard_91.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import discard_then_draw, requires_hand card = PokemonCardDef( guid="76380e53-8d0e-5a8f-ada6-235d439db622", @@ -22,7 +23,9 @@ card = PokemonCardDef( Ability( title="Trade", game_text="You must discard a card from your hand in order to use this Ability. Once during your turn, you may draw 2 cards.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=requires_hand(n=1), + effect=discard_then_draw(1, 2, optional=False), ), Attack( title="Slash", diff --git a/spirit/game/scripts/cards/SWSH9/Lucario_79.py b/spirit/game/scripts/cards/SWSH9/Lucario_79.py index 327b876..d8109c1 100644 --- a/spirit/game/scripts/cards/SWSH9/Lucario_79.py +++ b/spirit/game/scripts/cards/SWSH9/Lucario_79.py @@ -1,5 +1,35 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import is_energy_card, energy_provides_type + + +def _is_fighting_energy_card(card): + return is_energy_card(card) and energy_provides_type(card, PokemonTypes.FIGHTING.value) + + +async def roaring_resolve(ctx): + """Once per turn: you may put 2 damage counters on this Pokémon. If you + do, search your deck for a Fighting Energy card and attach it here.""" + if not await ctx.ask_yes_no("Put 2 damage counters on this Pokémon?"): + return + await ctx.deal_damage(20, target=ctx.source, apply_modifiers=False, as_counters=True) + picks = await ctx.search_deck( + _is_fighting_energy_card, count=1, minimum=0, + prompt="Choose a Fighting Energy card to attach.", + ) + if picks: + await ctx.attach_energy(picks[0], ctx.source) + await ctx.shuffle_deck() + + +async def aura_sphere_volley(ctx): + """Discard all Fighting Energy from this Pokémon. This attack does 60 + more damage for each card discarded this way.""" + discarded = await ctx.discard_energy_from( + ctx.source, 99, predicate=_is_fighting_energy_card, + prompt="Discard all Fighting Energy from this Pokémon", + ) + await ctx.deal_damage(10 + 60 * len(discarded)) card = PokemonCardDef( guid="2bd1d15a-29ba-5870-9b34-3ed6be0f3294", @@ -22,7 +52,8 @@ card = PokemonCardDef( Ability( title="Roaring Resolve", game_text="Once during your turn, you may put 2 damage counters on this Pok\u00e9mon. If you do, search your deck for a Fighting Energy card and attach it to this Pok\u00e9mon. Then, shuffle your deck.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + effect=roaring_resolve, ), Attack( title="Aura Sphere Volley", @@ -30,7 +61,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 2}, damage=10, damage_operator="+", - effect=unimplemented, + effect=aura_sphere_volley, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/LumineonV_155.py b/spirit/game/scripts/cards/SWSH9/LumineonV_155.py index 77c120e..9770a31 100644 --- a/spirit/game/scripts/cards/SWSH9/LumineonV_155.py +++ b/spirit/game/scripts/cards/SWSH9/LumineonV_155.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import luminous_sign, aqua_return card = PokemonCardDef( guid="722e8bee-1cca-5b48-b46a-72580883b149", @@ -21,14 +22,15 @@ card = PokemonCardDef( Ability( title="Luminous Sign", game_text="When you play this Pok\u00e9mon from your hand onto your Bench during your turn, you may search your deck for a Supporter card, reveal it, and put it into your hand. Then, shuffle your deck.", - effect=unimplemented, + trigger=Triggers.ON_PLAY, + effect=luminous_sign, ), Attack( title="Aqua Return", game_text="Shuffle this Pok\u00e9mon and all attached cards into your deck.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=120, - effect=unimplemented, + effect=aqua_return, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/LumineonV_156.py b/spirit/game/scripts/cards/SWSH9/LumineonV_156.py index e4dceec..823b3b1 100644 --- a/spirit/game/scripts/cards/SWSH9/LumineonV_156.py +++ b/spirit/game/scripts/cards/SWSH9/LumineonV_156.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import luminous_sign, aqua_return card = PokemonCardDef( guid="60830dfb-1ff9-5796-bda8-cc7b389a9b45", @@ -21,14 +22,15 @@ card = PokemonCardDef( Ability( title="Luminous Sign", game_text="When you play this Pok\u00e9mon from your hand onto your Bench during your turn, you may search your deck for a Supporter card, reveal it, and put it into your hand. Then, shuffle your deck.", - effect=unimplemented, + trigger=Triggers.ON_PLAY, + effect=luminous_sign, ), Attack( title="Aqua Return", game_text="Shuffle this Pok\u00e9mon and all attached cards into your deck.", cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2}, damage=120, - effect=unimplemented, + effect=aqua_return, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/Luxio_50.py b/spirit/game/scripts/cards/SWSH9/Luxio_50.py index ed6a459..df1a774 100644 --- a/spirit/game/scripts/cards/SWSH9/Luxio_50.py +++ b/spirit/game/scripts/cards/SWSH9/Luxio_50.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="e7a6b5a2-f641-53ff-9f3c-f0ff1f4d8708", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.LIGHTNING: 1}, damage=30, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/Luxray_51.py b/spirit/game/scripts/cards/SWSH9/Luxray_51.py index 0c0fe27..2a68e4f 100644 --- a/spirit/game/scripts/cards/SWSH9/Luxray_51.py +++ b/spirit/game/scripts/cards/SWSH9/Luxray_51.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy, snipe_attack card = PokemonCardDef( guid="50d2a992-b8bf-568a-82a8-59d812e82fb8", @@ -25,14 +26,14 @@ card = PokemonCardDef( cost={PokemonTypes.LIGHTNING: 1}, damage=50, damage_operator="x", - effect=unimplemented, + effect=damage_per(count_energy("opponent"), 50), ), Attack( title="Flash Impact", game_text="This attack also does 30 damage to 1 of your Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.LIGHTNING: 1}, damage=110, - effect=unimplemented, + effect=snipe_attack(30, pool="bench", side="mine", also_base=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/MagmaBasin_144.py b/spirit/game/scripts/cards/SWSH9/MagmaBasin_144.py index 4e92a99..473f9cc 100644 --- a/spirit/game/scripts/cards/SWSH9/MagmaBasin_144.py +++ b/spirit/game/scripts/cards/SWSH9/MagmaBasin_144.py @@ -1,5 +1,53 @@ -from spirit.game.data_utils import StadiumCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import StadiumCardDef, Ability, Activations +from spirit.game.attributes import Rarities, AttrID, PokemonTypes +from spirit.game.card_effects.pokemon import energy_provides_type + + +def _is_fire_energy(card): + return energy_provides_type(card, PokemonTypes.FIRE.value) + + +def _is_fire_pokemon(pokemon): + types = pokemon.get_attribute(AttrID.POKEMON_TYPES) or [] + return PokemonTypes.FIRE.value in types + + +def _magma_basin_condition(board, player_id, stadium): + discard = board.find_player_area(player_id, "discard") + has_energy = bool(discard) and any(_is_fire_energy(c) for c in discard.children) + bench = board.find_player_area(player_id, "bench") + has_bench = bool(bench) and any(_is_fire_pokemon(p) for p in bench.children) + return has_energy and has_bench + + +async def _magma_basin_effect(ctx): + """Once during each player's turn, that player may attach a Fire Energy from + their discard pile to 1 of their Benched Fire Pokemon and put 2 damage + counters on it.""" + energy = [c for c in ctx.discard_pile() if _is_fire_energy(c)] + bench = [p for p in ctx.my_bench() if _is_fire_pokemon(p)] + if not energy or not bench: + return + picks = await ctx.choose_cards( + energy, 1, minimum=1, + prompt="Choose a Fire Energy card to attach.", + ) + if not picks: + return + target = await ctx.choose_pokemon(bench, "Choose 1 of your Benched Fire Pokémon") + if target is None: + return + await ctx.attach_energy(picks[0], target) + await ctx.deal_damage(20, target=target, apply_modifiers=False, as_counters=True) + + +MAGMA_BASIN_ABILITY = Ability( + title="Magma Basin", + game_text="Once during each player's turn, that player may attach a Fire Energy card from their discard pile to 1 of their Benched Fire Pokémon. If a player attached Energy to a Pokémon in this way, put 2 damage counters on that Pokémon.", + activation=Activations.ONCE_PER_TURN, + effect=_magma_basin_effect, + condition=_magma_basin_condition, +) card = StadiumCardDef( guid="1e5c7bf0-9908-5511-96b7-340441462479", @@ -11,5 +59,5 @@ card = StadiumCardDef( collector_number=144, set_code="SWSH9", rarity=Rarities.Uncommon, - effect=unimplemented + ability=MAGMA_BASIN_ABILITY, ) diff --git a/spirit/game/scripts/cards/SWSH9/MagmaBasin_185.py b/spirit/game/scripts/cards/SWSH9/MagmaBasin_185.py index 70121aa..96a5c83 100644 --- a/spirit/game/scripts/cards/SWSH9/MagmaBasin_185.py +++ b/spirit/game/scripts/cards/SWSH9/MagmaBasin_185.py @@ -1,5 +1,53 @@ -from spirit.game.data_utils import StadiumCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import StadiumCardDef, Ability, Activations +from spirit.game.attributes import Rarities, AttrID, PokemonTypes +from spirit.game.card_effects.pokemon import energy_provides_type + + +def _is_fire_energy(card): + return energy_provides_type(card, PokemonTypes.FIRE.value) + + +def _is_fire_pokemon(pokemon): + types = pokemon.get_attribute(AttrID.POKEMON_TYPES) or [] + return PokemonTypes.FIRE.value in types + + +def _magma_basin_condition(board, player_id, stadium): + discard = board.find_player_area(player_id, "discard") + has_energy = bool(discard) and any(_is_fire_energy(c) for c in discard.children) + bench = board.find_player_area(player_id, "bench") + has_bench = bool(bench) and any(_is_fire_pokemon(p) for p in bench.children) + return has_energy and has_bench + + +async def _magma_basin_effect(ctx): + """Once during each player's turn, that player may attach a Fire Energy from + their discard pile to 1 of their Benched Fire Pokemon and put 2 damage + counters on it.""" + energy = [c for c in ctx.discard_pile() if _is_fire_energy(c)] + bench = [p for p in ctx.my_bench() if _is_fire_pokemon(p)] + if not energy or not bench: + return + picks = await ctx.choose_cards( + energy, 1, minimum=1, + prompt="Choose a Fire Energy card to attach.", + ) + if not picks: + return + target = await ctx.choose_pokemon(bench, "Choose 1 of your Benched Fire Pokémon") + if target is None: + return + await ctx.attach_energy(picks[0], target) + await ctx.deal_damage(20, target=target, apply_modifiers=False, as_counters=True) + + +MAGMA_BASIN_ABILITY = Ability( + title="Magma Basin", + game_text="Once during each player's turn, that player may attach a Fire Energy card from their discard pile to 1 of their Benched Fire Pokémon. If a player attached Energy to a Pokémon in this way, put 2 damage counters on that Pokémon.", + activation=Activations.ONCE_PER_TURN, + effect=_magma_basin_effect, + condition=_magma_basin_condition, +) card = StadiumCardDef( guid="f65c94a9-a1f8-53e4-b8ff-3dd1ff150979", @@ -11,5 +59,5 @@ card = StadiumCardDef( collector_number=185, set_code="SWSH9", rarity=Rarities.RareSecret, - effect=unimplemented + ability=MAGMA_BASIN_ABILITY, ) diff --git a/spirit/game/scripts/cards/SWSH9/Magmortar_20.py b/spirit/game/scripts/cards/SWSH9/Magmortar_20.py index 81df5fe..5c44605 100644 --- a/spirit/game/scripts/cards/SWSH9/Magmortar_20.py +++ b/spirit/game/scripts/cards/SWSH9/Magmortar_20.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID +from spirit.game.card_effects.attacks_common import bonus_if + + +def _electivire_on_bench(ctx) -> bool: + return any(p.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) == "Electivire" + for p in ctx.my_bench()) + card = PokemonCardDef( guid="d4420848-619f-50c0-944b-d1edea4769ac", @@ -30,7 +37,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1}, damage=120, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_electivire_on_bench, 120), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/MarniesPride_145.py b/spirit/game/scripts/cards/SWSH9/MarniesPride_145.py index e622881..3ef2552 100644 --- a/spirit/game/scripts/cards/SWSH9/MarniesPride_145.py +++ b/spirit/game/scripts/cards/SWSH9/MarniesPride_145.py @@ -1,5 +1,34 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import is_basic_energy_card + + +def _marnies_pride_condition(board, player_id): + discard = board.find_player_area(player_id, "discard") + has_energy = bool(discard) and any(is_basic_energy_card(c) for c in discard.children) + bench = board.find_player_area(player_id, "bench") + has_bench = bool(bench) and bool(bench.children) + return has_energy and has_bench + + +async def marnies_pride(ctx): + """Attach a basic Energy card from your discard pile to 1 of your + Benched Pokemon.""" + energy = [c for c in ctx.discard_pile() if is_basic_energy_card(c)] + bench = ctx.my_bench() + if not energy or not bench: + return + picks = await ctx.choose_cards( + energy, 1, minimum=1, + prompt="Choose a basic Energy card to attach.", + ) + if not picks: + return + target = await ctx.choose_pokemon(bench, "Choose 1 of your Benched Pokémon") + if target is None: + return + await ctx.attach_energy(picks[0], target) + card = SupporterCardDef( guid="59bf1106-1947-5649-a535-e9505ad024e6", @@ -11,5 +40,6 @@ card = SupporterCardDef( collector_number=145, set_code="SWSH9", rarity=Rarities.Uncommon, - effect=unimplemented + effect=marnies_pride, + condition=_marnies_pride_condition, ) diff --git a/spirit/game/scripts/cards/SWSH9/MarniesPride_171.py b/spirit/game/scripts/cards/SWSH9/MarniesPride_171.py index 15dc95a..b1d2918 100644 --- a/spirit/game/scripts/cards/SWSH9/MarniesPride_171.py +++ b/spirit/game/scripts/cards/SWSH9/MarniesPride_171.py @@ -1,5 +1,34 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef from spirit.game.attributes import Rarities +from spirit.game.card_effects.trainers import is_basic_energy_card + + +def _marnies_pride_condition(board, player_id): + discard = board.find_player_area(player_id, "discard") + has_energy = bool(discard) and any(is_basic_energy_card(c) for c in discard.children) + bench = board.find_player_area(player_id, "bench") + has_bench = bool(bench) and bool(bench.children) + return has_energy and has_bench + + +async def marnies_pride(ctx): + """Attach a basic Energy card from your discard pile to 1 of your + Benched Pokemon.""" + energy = [c for c in ctx.discard_pile() if is_basic_energy_card(c)] + bench = ctx.my_bench() + if not energy or not bench: + return + picks = await ctx.choose_cards( + energy, 1, minimum=1, + prompt="Choose a basic Energy card to attach.", + ) + if not picks: + return + target = await ctx.choose_pokemon(bench, "Choose 1 of your Benched Pokémon") + if target is None: + return + await ctx.attach_energy(picks[0], target) + card = SupporterCardDef( guid="e38215d6-ea05-5163-85a9-56908ad8f893", @@ -11,5 +40,6 @@ card = SupporterCardDef( collector_number=171, set_code="SWSH9", rarity=Rarities.RareUltra, - effect=unimplemented + effect=marnies_pride, + condition=_marnies_pride_condition, ) diff --git a/spirit/game/scripts/cards/SWSH9/Mewtwo_56.py b/spirit/game/scripts/cards/SWSH9/Mewtwo_56.py index c574e6b..48417f4 100644 --- a/spirit/game/scripts/cards/SWSH9/Mewtwo_56.py +++ b/spirit/game/scripts/cards/SWSH9/Mewtwo_56.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import heal_attack card = PokemonCardDef( guid="79eca715-dd37-5898-ace7-ab87981d804c", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Heal 20 damage from this Pok\u00e9mon.", cost={PokemonTypes.PSYCHIC: 1}, damage=20, - effect=unimplemented, + effect=heal_attack(20, target="self"), ), Attack( title="Psyburn", diff --git a/spirit/game/scripts/cards/SWSH9/Milcery_70.py b/spirit/game/scripts/cards/SWSH9/Milcery_70.py index 69836b1..de61687 100644 --- a/spirit/game/scripts/cards/SWSH9/Milcery_70.py +++ b/spirit/game/scripts/cards/SWSH9/Milcery_70.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_hand +from spirit.game.session.effects import is_supporter_card card = PokemonCardDef( guid="434562c9-a907-5b12-8734-6327f6b24d2a", @@ -22,7 +24,10 @@ card = PokemonCardDef( title="Lead", game_text="Search your deck for a Supporter card, reveal it, and put it into your hand. Then, shuffle your deck.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=search_to_hand( + is_supporter_card, count=1, minimum=0, + prompt="Choose a Supporter card to put into your hand.", + ), ), Attack( title="Ram", diff --git a/spirit/game/scripts/cards/SWSH9/MimikyuVMAX_69.py b/spirit/game/scripts/cards/SWSH9/MimikyuVMAX_69.py index 9fb7900..7c346db 100644 --- a/spirit/game/scripts/cards/SWSH9/MimikyuVMAX_69.py +++ b/spirit/game/scripts/cards/SWSH9/MimikyuVMAX_69.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import place_counters, discard_random_from_hand + + +async def max_shadow(ctx): + """120 damage; discard a random card from the opponent's hand.""" + await ctx.deal_damage() + await discard_random_from_hand(ctx, player_id=ctx.opponent_id, count=1) card = PokemonCardDef( guid="4982ce9a-4bdd-5895-9974-d3c4895a1dcd", @@ -24,14 +31,17 @@ card = PokemonCardDef( title="Ominous Numbers", game_text="Put 4 damage counters on your opponent's Pok\u00e9mon in any way you like. If you played Acerola's Premonition from your hand during this turn, place 13 damage counters instead.", cost={PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + effect=place_counters( + lambda ctx: 13 if ctx.played_trainer_this_turn("Acerola's Premonition") else 4, + "choose_any_opponent", + ), ), Attack( title="Max Shadow", game_text="Discard a random card from your opponent's hand.", cost={PokemonTypes.PSYCHIC: 2}, damage=120, - effect=unimplemented, + effect=max_shadow, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/MimikyuV_68.py b/spirit/game/scripts/cards/SWSH9/MimikyuV_68.py index 6f69236..c147dff 100644 --- a/spirit/game/scripts/cards/SWSH9/MimikyuV_68.py +++ b/spirit/game/scripts/cards/SWSH9/MimikyuV_68.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import place_counters +from spirit.game.card_effects.passives_common import apply_protection + + +async def dummy_doll(ctx): + """On play from hand: you may prevent all damage to this Pokemon by + opposing attacks until the end of the opponent's next turn.""" + if await ctx.ask_yes_no( + "Prevent all damage done to this Pokemon by attacks from your " + "opponent's Pokemon until the end of your opponent's next turn?" + ): + await apply_protection(ctx, prevent=True) card = PokemonCardDef( guid="0fa8ad8a-cb4a-5b13-84a4-5f71d12aa6e4", @@ -22,13 +34,14 @@ card = PokemonCardDef( Ability( title="Dummy Doll", game_text="When you play this Pok\u00e9mon from your hand onto your Bench during your turn, you may prevent all damage done to this Mimikyu V by attacks from your opponent's Pok\u00e9mon until the end of your opponent's next turn.", - effect=unimplemented, + trigger=Triggers.ON_PLAY, + effect=dummy_doll, ), Attack( title="Jealous Eyes", game_text="Put 3 damage counters on your opponent's Active Pok\u00e9mon for each Prize card your opponent has taken.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=place_counters(lambda ctx: 3 * ctx.prizes_taken(ctx.opponent_id), "opponent_active"), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/Minccino_124.py b/spirit/game/scripts/cards/SWSH9/Minccino_124.py index d6870e5..ea8d963 100644 --- a/spirit/game/scripts/cards/SWSH9/Minccino_124.py +++ b/spirit/game/scripts/cards/SWSH9/Minccino_124.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import search_to_bench card = PokemonCardDef( guid="e7f42029-b829-5aa0-bee9-da1737ef197e", @@ -22,7 +23,7 @@ card = PokemonCardDef( title="Call for Family", game_text="Search your deck for up to 2 Basic Pok\u00e9mon and put them onto your Bench. Then, shuffle your deck.", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=search_to_bench(count=2), ), Attack( title="Pound", diff --git a/spirit/game/scripts/cards/SWSH9/Moltres_21.py b/spirit/game/scripts/cards/SWSH9/Moltres_21.py index aee0284..3e77d63 100644 --- a/spirit/game/scripts/cards/SWSH9/Moltres_21.py +++ b/spirit/game/scripts/cards/SWSH9/Moltres_21.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID + + +async def inferno_wings(ctx): + """20, +70 more if this Pokemon has any damage counters on it; not + affected by Weakness.""" + has_damage = ctx.source.get_attribute(AttrID.HP, 0) < ctx.max_hp(ctx.source) + amount = 20 + (70 if has_damage else 0) + await ctx.deal_damage(amount, ignore_weakness=True) card = PokemonCardDef( guid="8942b99f-e2bb-5195-bc27-5e5569161b58", @@ -24,7 +32,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=inferno_wings, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/Monferno_25.py b/spirit/game/scripts/cards/SWSH9/Monferno_25.py index 89cd7ad..97594cd 100644 --- a/spirit/game/scripts/cards/SWSH9/Monferno_25.py +++ b/spirit/game/scripts/cards/SWSH9/Monferno_25.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import self_energy_discard_attack card = PokemonCardDef( guid="fd88d2fd-aef3-5c2b-9d20-da63961f2668", @@ -29,7 +30,7 @@ card = PokemonCardDef( game_text="Discard an Energy from this Pok\u00e9mon.", cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 1}, damage=50, - effect=unimplemented, + effect=self_energy_discard_attack(count=1), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/MorpekoV_95.py b/spirit/game/scripts/cards/SWSH9/MorpekoV_95.py index b85a088..44205f6 100644 --- a/spirit/game/scripts/cards/SWSH9/MorpekoV_95.py +++ b/spirit/game/scripts/cards/SWSH9/MorpekoV_95.py @@ -1,4 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import bonus_if +from spirit.game.card_effects.support_common import switch_self_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -23,7 +25,7 @@ card = PokemonCardDef( game_text="You may switch this Pok\u00e9mon with 1 of your Benched Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 2}, damage=30, - effect=unimplemented, + effect=switch_self_attack(optional=True), ), Attack( title="Hangry Spike", @@ -31,7 +33,7 @@ card = PokemonCardDef( cost={PokemonTypes.DARKNESS: 2, PokemonTypes.COLORLESS: 1}, damage=120, damage_operator="+", - effect=unimplemented, + effect=bonus_if(lambda ctx: ctx.played_trainer_this_turn("Marnie's Pride") > 0, 120), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/Mothim_11.py b/spirit/game/scripts/cards/SWSH9/Mothim_11.py index f9cd997..5b293f3 100644 --- a/spirit/game/scripts/cards/SWSH9/Mothim_11.py +++ b/spirit/game/scripts/cards/SWSH9/Mothim_11.py @@ -1,5 +1,12 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if + + +def _evolved_this_turn(ctx) -> bool: + turn_state = ctx.session.turn_state + return turn_state.entered_play_turn.get(ctx.attacker.entity_id) == turn_state.turn_number + card = PokemonCardDef( guid="036eec95-ef8e-555c-94e4-492ddbef4175", @@ -26,7 +33,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=30, damage_operator="+", - effect=unimplemented, + effect=bonus_if(_evolved_this_turn, 90), ), Attack( title="Gust", diff --git a/spirit/game/scripts/cards/SWSH9/Muk_85.py b/spirit/game/scripts/cards/SWSH9/Muk_85.py index fe4f872..97a61fd 100644 --- a/spirit/game/scripts/cards/SWSH9/Muk_85.py +++ b/spirit/game/scripts/cards/SWSH9/Muk_85.py @@ -1,5 +1,23 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import ( + AttrID, CLIENT_SPECIAL_CONDITION_NAMES, PokemonTypes, PokemonStage, + Rarities, SpecialConditions, +) +from spirit.game.card_effects.attacks_common import condition_attack +from spirit.game.session.passives import Passive + + +class SludgeStreetPassive(Passive): + """The Retreat Cost of your opponent's Poisoned Pokémon is Colorless more.""" + + def modify_retreat_cost(self, cost, pokemon, carrier, board): + if pokemon.owning_player_id == carrier.owning_player_id: + return cost + name = CLIENT_SPECIAL_CONDITION_NAMES[SpecialConditions.POISONED] + if name in (pokemon.get_attribute(AttrID.SPECIAL_CONDITIONS) or []): + return cost + 1 + return cost + card = PokemonCardDef( guid="f3ea16e1-582d-5344-a636-9bce3bc419f3", @@ -22,14 +40,14 @@ card = PokemonCardDef( Ability( title="Sludge Street", game_text="The Retreat Cost of your opponent's Poisoned Pok\u00e9mon is Colorless more.", - effect=unimplemented, + passive=SludgeStreetPassive(), ), Attack( title="Shrieking Poison", game_text="Your opponent's Active Pok\u00e9mon is now Confused and Poisoned.", cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 2}, damage=90, - effect=unimplemented, + effect=condition_attack(SpecialConditions.CONFUSED, SpecialConditions.POISONED), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/Pachirisu_52.py b/spirit/game/scripts/cards/SWSH9/Pachirisu_52.py index ea4cfbe..88d57b6 100644 --- a/spirit/game/scripts/cards/SWSH9/Pachirisu_52.py +++ b/spirit/game/scripts/cards/SWSH9/Pachirisu_52.py @@ -1,6 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import damage_per +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +def _my_tools(ctx): + return sum( + 1 for tool, pokemon in ctx.tools_in_play() + if pokemon.owning_player_id == ctx.player_id + ) + card = PokemonCardDef( guid="d1f956bb-8776-551a-9602-be7fb5b66b86", key="SWSH9", @@ -24,7 +32,7 @@ card = PokemonCardDef( cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 1}, damage=30, damage_operator="x", - effect=unimplemented, + effect=damage_per(_my_tools, 30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/PikachuV_157.py b/spirit/game/scripts/cards/SWSH9/PikachuV_157.py index df83a1d..064e6e4 100644 --- a/spirit/game/scripts/cards/SWSH9/PikachuV_157.py +++ b/spirit/game/scripts/cards/SWSH9/PikachuV_157.py @@ -1,5 +1,25 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import energy_provides_type + + +async def lightning_blast(ctx): + lightning_energy = [ + e for e in ctx.attached_energies(ctx.attacker) + if energy_provides_type(e, PokemonTypes.LIGHTNING.value) + ] + if lightning_energy and await ctx.ask_yes_no( + "Discard all Lightning Energy from this Pokémon? " + "If you do, this attack does 120 more damage." + ): + await ctx.discard_energy_from( + ctx.attacker, 99, + predicate=lambda c: energy_provides_type(c, PokemonTypes.LIGHTNING.value), + ) + await ctx.deal_damage(220) + else: + await ctx.deal_damage(100) + card = PokemonCardDef( guid="9c07dc16-7be2-52f1-b87a-160772d76404", @@ -24,7 +44,7 @@ card = PokemonCardDef( cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 1}, damage=100, damage_operator="+", - effect=unimplemented, + effect=lightning_blast, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/Piplup_35.py b/spirit/game/scripts/cards/SWSH9/Piplup_35.py index 189b37d..f87d21d 100644 --- a/spirit/game/scripts/cards/SWSH9/Piplup_35.py +++ b/spirit/game/scripts/cards/SWSH9/Piplup_35.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.card_effects.attacks_common import condition_attack card = PokemonCardDef( guid="3f7d8d65-1bc5-5f66-9560-0e92242bb8c8", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.", cost={PokemonTypes.WATER: 1}, damage=10, - effect=unimplemented, + effect=condition_attack(SpecialConditions.PARALYZED, flip=True), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/PotHelmet_146.py b/spirit/game/scripts/cards/SWSH9/PotHelmet_146.py index 67873c4..3d513b3 100644 --- a/spirit/game/scripts/cards/SWSH9/PotHelmet_146.py +++ b/spirit/game/scripts/cards/SWSH9/PotHelmet_146.py @@ -1,15 +1,22 @@ -from spirit.game.data_utils import PokemonToolCardDef, unimplemented +from spirit.game.data_utils import PokemonToolCardDef, has_rule_box from spirit.game.attributes import Rarities +from spirit.game.card_effects.passives_common import takes_less_passive +from spirit.game.session.passives import carrier_pokemon + + +def _pot_helmet_protects(target, carrier): + return carrier_pokemon(carrier) is target and not has_rule_box(target.archetype_id) + card = PokemonToolCardDef( guid="2455cf86-658b-55d7-9b30-c57599ffa271", key="SWSH9", name="com.direwolfdigital.cake.data.archetypes.trainer.PotHelmet.Name", display_name="Pot Helmet", - searchable_by=["Pot Helmet", "Item", "Pok\u00c3\u00a9mon Tool"], - subtypes=["Item", "Pok\u00e9mon Tool"], + searchable_by=["Pot Helmet", "Item", "Pokémon Tool"], + subtypes=["Item", "Pokémon Tool"], collector_number=146, set_code="SWSH9", rarity=Rarities.Uncommon, - effect=unimplemented + passive=takes_less_passive(30, protects=_pot_helmet_protects), ) diff --git a/spirit/game/scripts/cards/SWSH9/Probopass_99.py b/spirit/game/scripts/cards/SWSH9/Probopass_99.py index 980c014..51d2c9b 100644 --- a/spirit/game/scripts/cards/SWSH9/Probopass_99.py +++ b/spirit/game/scripts/cards/SWSH9/Probopass_99.py @@ -1,4 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.card_effects.attacks_common import recoil_attack +from spirit.game.card_effects.support_common import gust_attack +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities card = PokemonCardDef( @@ -24,14 +26,14 @@ card = PokemonCardDef( title="Magnetic Tension", game_text="Switch 1 of your opponent's Benched Pok\u00e9mon with their Active Pok\u00e9mon. This attack does 40 damage to the new Active Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 2}, - effect=unimplemented, + effect=gust_attack(damage_to_new_active=40), ), Attack( title="Iron Tackle", game_text="This Pok\u00e9mon also does 20 damage to itself.", cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 2}, damage=120, - effect=unimplemented, + effect=recoil_attack(20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/Purrloin_90.py b/spirit/game/scripts/cards/SWSH9/Purrloin_90.py index 321b065..e03f499 100644 --- a/spirit/game/scripts/cards/SWSH9/Purrloin_90.py +++ b/spirit/game/scripts/cards/SWSH9/Purrloin_90.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_or_nothing card = PokemonCardDef( guid="a0be6373-d079-54f0-80f0-88895d9cac7d", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="Flip a coin. If tails, this attack does nothing.", cost={PokemonTypes.COLORLESS: 2}, damage=30, - effect=unimplemented, + effect=flip_or_nothing(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/RaichuV_158.py b/spirit/game/scripts/cards/SWSH9/RaichuV_158.py index 468a75f..242546d 100644 --- a/spirit/game/scripts/cards/SWSH9/RaichuV_158.py +++ b/spirit/game/scripts/cards/SWSH9/RaichuV_158.py @@ -1,5 +1,26 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import is_lightning_energy, energy_provides_type +from spirit.game.card_effects.support_common import search_attach_energy + + +async def dynamic_spark(ctx): + """You may discard any amount of Lightning Energy from this Pokemon; + 60 damage for each card discarded this way.""" + attached = [ + e for e in ctx.attached_energies(ctx.attacker) + if energy_provides_type(e, PokemonTypes.LIGHTNING.value) + ] + picks = [] + if attached: + picks = await ctx.choose_cards( + attached, len(attached), minimum=0, + prompt="Discard any amount of Lightning Energy from this Pokémon.", + ) + if picks: + await ctx.discard_cards(picks) + await ctx.deal_damage(60 * len(picks)) + card = PokemonCardDef( guid="d2cd69b4-9c28-5ef4-9bf2-4b0f12c2cf89", @@ -22,7 +43,8 @@ card = PokemonCardDef( title="Fast Charge", game_text="If you go first, you can use this attack during your first turn. Search your deck for a Lightning Energy card and attach it to this Pok\u00e9mon. Then, shuffle your deck.", cost={PokemonTypes.LIGHTNING: 1}, - effect=unimplemented, + usable_first_turn=True, + effect=search_attach_energy(predicate=is_lightning_energy, count=1, to_self=True), ), Attack( title="Dynamic Spark", @@ -30,7 +52,7 @@ card = PokemonCardDef( cost={PokemonTypes.LIGHTNING: 2}, damage=60, damage_operator="x", - effect=unimplemented, + effect=dynamic_spark, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/RaichuV_45.py b/spirit/game/scripts/cards/SWSH9/RaichuV_45.py index 418052a..615d3b2 100644 --- a/spirit/game/scripts/cards/SWSH9/RaichuV_45.py +++ b/spirit/game/scripts/cards/SWSH9/RaichuV_45.py @@ -1,5 +1,26 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.pokemon import is_lightning_energy, energy_provides_type +from spirit.game.card_effects.support_common import search_attach_energy + + +async def dynamic_spark(ctx): + """You may discard any amount of Lightning Energy from this Pokemon; + 60 damage for each card discarded this way.""" + attached = [ + e for e in ctx.attached_energies(ctx.attacker) + if energy_provides_type(e, PokemonTypes.LIGHTNING.value) + ] + picks = [] + if attached: + picks = await ctx.choose_cards( + attached, len(attached), minimum=0, + prompt="Discard any amount of Lightning Energy from this Pokémon.", + ) + if picks: + await ctx.discard_cards(picks) + await ctx.deal_damage(60 * len(picks)) + card = PokemonCardDef( guid="363bf9c7-0376-53d1-913a-74d464f30b0f", @@ -22,7 +43,8 @@ card = PokemonCardDef( title="Fast Charge", game_text="If you go first, you can use this attack during your first turn. Search your deck for a Lightning Energy card and attach it to this Pok\u00e9mon. Then, shuffle your deck.", cost={PokemonTypes.LIGHTNING: 1}, - effect=unimplemented, + usable_first_turn=True, + effect=search_attach_energy(predicate=is_lightning_energy, count=1, to_self=True), ), Attack( title="Dynamic Spark", @@ -30,7 +52,7 @@ card = PokemonCardDef( cost={PokemonTypes.LIGHTNING: 2}, damage=60, damage_operator="x", - effect=unimplemented, + effect=dynamic_spark, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/RaikouV_48.py b/spirit/game/scripts/cards/SWSH9/RaikouV_48.py index 6e7a8d2..df48bb7 100644 --- a/spirit/game/scripts/cards/SWSH9/RaikouV_48.py +++ b/spirit/game/scripts/cards/SWSH9/RaikouV_48.py @@ -1,5 +1,14 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_bench +from spirit.game.card_effects.pokemon import in_active_spot + + +async def fleet_footed(ctx): + """Once per turn, in the Active Spot: you may draw a card.""" + if await ctx.ask_yes_no("Draw a card?"): + await ctx.draw_cards(1) + card = PokemonCardDef( guid="3bde83ea-247d-58ca-9c68-7e7796806e9d", @@ -21,7 +30,9 @@ card = PokemonCardDef( Ability( title="Fleet-Footed", game_text="Once during your turn, if this Pok\u00e9mon is in the Active Spot, you may draw a card.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + condition=in_active_spot, + effect=fleet_footed, ), Attack( title="Lightning Rondo", @@ -29,7 +40,7 @@ card = PokemonCardDef( cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 1}, damage=20, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_bench("both"), 20, base=20), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/RoseannesBackup_148.py b/spirit/game/scripts/cards/SWSH9/RoseannesBackup_148.py index a4f2fba..58540b5 100644 --- a/spirit/game/scripts/cards/SWSH9/RoseannesBackup_148.py +++ b/spirit/game/scripts/cards/SWSH9/RoseannesBackup_148.py @@ -1,5 +1,50 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import SupporterCardDef +from spirit.game.attributes import Rarities, AttrID, TrainerType +from spirit.game.session.effects import is_pokemon_card +from spirit.game.card_effects.pokemon import is_energy_card + +_TOOL_TYPES = (TrainerType.POKEMON_TOOL.value, TrainerType.POKEMON_TOOL_F.value) + + +def _is_tool_card(card): + return card.get_attribute(AttrID.TRAINER_TYPE) in _TOOL_TYPES + + +def _is_stadium_card(card): + return card.get_attribute(AttrID.TRAINER_TYPE) == TrainerType.STADIUM.value + + +def _roseannes_backup_condition(board, player_id): + discard = board.find_player_area(player_id, "discard") + if not discard: + return False + return any( + is_pokemon_card(c) or _is_tool_card(c) or _is_stadium_card(c) or is_energy_card(c) + for c in discard.children + ) + + +async def _shuffle_category(ctx, predicate, label): + cards = [c for c in ctx.discard_pile() if predicate(c)] + if not cards: + return + if not await ctx.ask_yes_no(f"Shuffle a {label} from your discard pile into your deck?"): + return + picks = await ctx.choose_cards( + cards, 1, minimum=1, prompt=f"Choose a {label} to shuffle into your deck.", + ) + if picks: + await ctx.shuffle_into_deck(picks) + + +async def roseannes_backup(ctx): + """Choose 1 or more: shuffle a Pokemon / Pokemon Tool / Stadium / + Energy card from your discard pile into your deck.""" + await _shuffle_category(ctx, is_pokemon_card, "Pokémon") + await _shuffle_category(ctx, _is_tool_card, "Pokémon Tool card") + await _shuffle_category(ctx, _is_stadium_card, "Stadium card") + await _shuffle_category(ctx, is_energy_card, "Energy card") + card = SupporterCardDef( guid="44ca3f1e-7140-5e41-8ec1-2b365b29b46b", @@ -11,5 +56,6 @@ card = SupporterCardDef( collector_number=148, set_code="SWSH9", rarity=Rarities.Uncommon, - effect=unimplemented + effect=roseannes_backup, + condition=_roseannes_backup_condition, ) diff --git a/spirit/game/scripts/cards/SWSH9/RoseannesBackup_172.py b/spirit/game/scripts/cards/SWSH9/RoseannesBackup_172.py index 49e5745..c189446 100644 --- a/spirit/game/scripts/cards/SWSH9/RoseannesBackup_172.py +++ b/spirit/game/scripts/cards/SWSH9/RoseannesBackup_172.py @@ -1,5 +1,50 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import SupporterCardDef +from spirit.game.attributes import Rarities, AttrID, TrainerType +from spirit.game.session.effects import is_pokemon_card +from spirit.game.card_effects.pokemon import is_energy_card + +_TOOL_TYPES = (TrainerType.POKEMON_TOOL.value, TrainerType.POKEMON_TOOL_F.value) + + +def _is_tool_card(card): + return card.get_attribute(AttrID.TRAINER_TYPE) in _TOOL_TYPES + + +def _is_stadium_card(card): + return card.get_attribute(AttrID.TRAINER_TYPE) == TrainerType.STADIUM.value + + +def _roseannes_backup_condition(board, player_id): + discard = board.find_player_area(player_id, "discard") + if not discard: + return False + return any( + is_pokemon_card(c) or _is_tool_card(c) or _is_stadium_card(c) or is_energy_card(c) + for c in discard.children + ) + + +async def _shuffle_category(ctx, predicate, label): + cards = [c for c in ctx.discard_pile() if predicate(c)] + if not cards: + return + if not await ctx.ask_yes_no(f"Shuffle a {label} from your discard pile into your deck?"): + return + picks = await ctx.choose_cards( + cards, 1, minimum=1, prompt=f"Choose a {label} to shuffle into your deck.", + ) + if picks: + await ctx.shuffle_into_deck(picks) + + +async def roseannes_backup(ctx): + """Choose 1 or more: shuffle a Pokemon / Pokemon Tool / Stadium / + Energy card from your discard pile into your deck.""" + await _shuffle_category(ctx, is_pokemon_card, "Pokémon") + await _shuffle_category(ctx, _is_tool_card, "Pokémon Tool card") + await _shuffle_category(ctx, _is_stadium_card, "Stadium card") + await _shuffle_category(ctx, is_energy_card, "Energy card") + card = SupporterCardDef( guid="54d5fdb1-5b09-5556-be6e-5a5e2a448d3a", @@ -11,5 +56,6 @@ card = SupporterCardDef( collector_number=172, set_code="SWSH9", rarity=Rarities.RareUltra, - effect=unimplemented + effect=roseannes_backup, + condition=_roseannes_backup_condition, ) diff --git a/spirit/game/scripts/cards/SWSH9/RoseannesBackup_180.py b/spirit/game/scripts/cards/SWSH9/RoseannesBackup_180.py index 077b3ad..acc3f4c 100644 --- a/spirit/game/scripts/cards/SWSH9/RoseannesBackup_180.py +++ b/spirit/game/scripts/cards/SWSH9/RoseannesBackup_180.py @@ -1,5 +1,50 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented -from spirit.game.attributes import Rarities +from spirit.game.data_utils import SupporterCardDef +from spirit.game.attributes import Rarities, AttrID, TrainerType +from spirit.game.session.effects import is_pokemon_card +from spirit.game.card_effects.pokemon import is_energy_card + +_TOOL_TYPES = (TrainerType.POKEMON_TOOL.value, TrainerType.POKEMON_TOOL_F.value) + + +def _is_tool_card(card): + return card.get_attribute(AttrID.TRAINER_TYPE) in _TOOL_TYPES + + +def _is_stadium_card(card): + return card.get_attribute(AttrID.TRAINER_TYPE) == TrainerType.STADIUM.value + + +def _roseannes_backup_condition(board, player_id): + discard = board.find_player_area(player_id, "discard") + if not discard: + return False + return any( + is_pokemon_card(c) or _is_tool_card(c) or _is_stadium_card(c) or is_energy_card(c) + for c in discard.children + ) + + +async def _shuffle_category(ctx, predicate, label): + cards = [c for c in ctx.discard_pile() if predicate(c)] + if not cards: + return + if not await ctx.ask_yes_no(f"Shuffle a {label} from your discard pile into your deck?"): + return + picks = await ctx.choose_cards( + cards, 1, minimum=1, prompt=f"Choose a {label} to shuffle into your deck.", + ) + if picks: + await ctx.shuffle_into_deck(picks) + + +async def roseannes_backup(ctx): + """Choose 1 or more: shuffle a Pokemon / Pokemon Tool / Stadium / + Energy card from your discard pile into your deck.""" + await _shuffle_category(ctx, is_pokemon_card, "Pokémon") + await _shuffle_category(ctx, _is_tool_card, "Pokémon Tool card") + await _shuffle_category(ctx, _is_stadium_card, "Stadium card") + await _shuffle_category(ctx, is_energy_card, "Energy card") + card = SupporterCardDef( guid="061426fc-96b5-5487-9499-074a39205694", @@ -11,5 +56,6 @@ card = SupporterCardDef( collector_number=180, set_code="SWSH9", rarity=Rarities.RareRainbow, - effect=unimplemented + effect=roseannes_backup, + condition=_roseannes_backup_condition, ) diff --git a/spirit/game/scripts/cards/SWSH9/Sawk_81.py b/spirit/game/scripts/cards/SWSH9/Sawk_81.py index f492061..05a894e 100644 --- a/spirit/game/scripts/cards/SWSH9/Sawk_81.py +++ b/spirit/game/scripts/cards/SWSH9/Sawk_81.py @@ -1,6 +1,18 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def sweep_the_leg(ctx): + await ctx.deal_damage() + heads = (await ctx.flip_coins(1, "Sweep the Leg"))[0] + if not heads: + return + target = ctx.opponent_active() + if target is None or ctx.effects_blocked(target): + return + await ctx.discard_energy_from( + target, 1, prompt="Choose Energy to discard from the Defending Pokémon") + card = PokemonCardDef( guid="3158c52d-3775-5335-8fe0-7db69a999810", key="SWSH9", @@ -23,7 +35,7 @@ card = PokemonCardDef( game_text="Flip a coin. If heads, discard an Energy from your opponent's Active Pok\u00e9mon.", cost={PokemonTypes.FIGHTING: 1}, damage=30, - effect=unimplemented, + effect=sweep_the_leg, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/ShayminVSTAR_14.py b/spirit/game/scripts/cards/SWSH9/ShayminVSTAR_14.py index 42c8198..d84f6c4 100644 --- a/spirit/game/scripts/cards/SWSH9/ShayminVSTAR_14.py +++ b/spirit/game/scripts/cards/SWSH9/ShayminVSTAR_14.py @@ -1,5 +1,26 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_prizes_taken + + +def _star_bloom_condition(board, player_id, pokemon): + bench = board.find_player_area(player_id, "bench") + return bool(bench) and any( + PokemonTypes.GRASS.value in (p.get_attribute(AttrID.POKEMON_TYPES) or []) + for p in bench.children + ) + + +async def star_bloom(ctx): + targets = [p for p in ctx.my_bench() + if PokemonTypes.GRASS.value in (p.get_attribute(AttrID.POKEMON_TYPES) or [])] + if not targets: + return + if not await ctx.ask_yes_no("Heal 120 damage from each of your Benched Grass Pokémon?"): + return + for pokemon in targets: + await ctx.heal(120, pokemon) + card = PokemonCardDef( guid="373f2035-2e68-50fd-be12-085897056af2", @@ -22,7 +43,10 @@ card = PokemonCardDef( Ability( title="Star Bloom", game_text="During your turn, you may heal 120 damage from each of your Benched Grass Pok\u00e9mon. (You can't use more than 1 VSTAR Power in a game.)", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + vstar=True, + effect=star_bloom, + condition=_star_bloom_condition, ), Attack( title="Revenge Blast", @@ -30,7 +54,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=120, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_prizes_taken("opponent"), 40, base=120), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/ShayminVSTAR_173.py b/spirit/game/scripts/cards/SWSH9/ShayminVSTAR_173.py index c32743c..3016ae8 100644 --- a/spirit/game/scripts/cards/SWSH9/ShayminVSTAR_173.py +++ b/spirit/game/scripts/cards/SWSH9/ShayminVSTAR_173.py @@ -1,5 +1,26 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_prizes_taken + + +def _star_bloom_condition(board, player_id, pokemon): + bench = board.find_player_area(player_id, "bench") + return bool(bench) and any( + PokemonTypes.GRASS.value in (p.get_attribute(AttrID.POKEMON_TYPES) or []) + for p in bench.children + ) + + +async def star_bloom(ctx): + targets = [p for p in ctx.my_bench() + if PokemonTypes.GRASS.value in (p.get_attribute(AttrID.POKEMON_TYPES) or [])] + if not targets: + return + if not await ctx.ask_yes_no("Heal 120 damage from each of your Benched Grass Pokémon?"): + return + for pokemon in targets: + await ctx.heal(120, pokemon) + card = PokemonCardDef( guid="0687700d-989d-53dc-9dda-644b5798ea5f", @@ -22,7 +43,10 @@ card = PokemonCardDef( Ability( title="Star Bloom", game_text="During your turn, you may heal 120 damage from each of your Benched Grass Pok\u00e9mon. (You can't use more than 1 VSTAR Power in a game.)", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + vstar=True, + effect=star_bloom, + condition=_star_bloom_condition, ), Attack( title="Revenge Blast", @@ -30,7 +54,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=120, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_prizes_taken("opponent"), 40, base=120), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/ShayminV_13.py b/spirit/game/scripts/cards/SWSH9/ShayminV_13.py index 0b2cc84..d6324d8 100644 --- a/spirit/game/scripts/cards/SWSH9/ShayminV_13.py +++ b/spirit/game/scripts/cards/SWSH9/ShayminV_13.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_prizes_taken card = PokemonCardDef( guid="82a177b4-b2b8-59ca-b3ba-d214a7d3f0d9", @@ -29,7 +30,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=60, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_prizes_taken("opponent"), 40, base=60), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/ShayminV_152.py b/spirit/game/scripts/cards/SWSH9/ShayminV_152.py index 6ef1550..1a2db9e 100644 --- a/spirit/game/scripts/cards/SWSH9/ShayminV_152.py +++ b/spirit/game/scripts/cards/SWSH9/ShayminV_152.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_prizes_taken card = PokemonCardDef( guid="45c3992e-7feb-57e2-a57b-f9df18e8196c", @@ -29,7 +30,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=60, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_prizes_taken("opponent"), 40, base=60), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/Sigilyph_66.py b/spirit/game/scripts/cards/SWSH9/Sigilyph_66.py index ca3c705..33d92fc 100644 --- a/spirit/game/scripts/cards/SWSH9/Sigilyph_66.py +++ b/spirit/game/scripts/cards/SWSH9/Sigilyph_66.py @@ -1,5 +1,23 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy +from spirit.game.card_effects.support_common import distribute_energy +from spirit.game.card_effects.trainers import is_basic_energy_card + + +async def tri_recharge(ctx): + heads = await ctx.flip_coins(3, "Tri Recharge") + count = sum(1 for h in heads if h) + bench = ctx.my_bench() + energy = [c for c in ctx.discard_pile() if is_basic_energy_card(c)] + if count <= 0 or not bench or not energy: + return + picks = await ctx.choose_cards( + energy, count, minimum=1, + prompt=f"Choose up to {count} basic Energy to attach to your Benched Pokémon.", + ) + if picks: + await distribute_energy(ctx, picks, bench) card = PokemonCardDef( guid="2e3d360d-1824-5683-a6dd-f783b5d47288", @@ -23,7 +41,7 @@ card = PokemonCardDef( title="Tri Recharge", game_text="Flip 3 coins. Attach a number of basic Energy cards up to the number of heads from your discard pile to your Benched Pok\u00e9mon in any way you like.", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=tri_recharge, ), Attack( title="Psychic", @@ -31,7 +49,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1}, damage=10, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("defender"), 30, base=10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/SimisearV_27.py b/spirit/game/scripts/cards/SWSH9/SimisearV_27.py index 7a41c5c..383b144 100644 --- a/spirit/game/scripts/cards/SWSH9/SimisearV_27.py +++ b/spirit/game/scripts/cards/SWSH9/SimisearV_27.py @@ -1,5 +1,22 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy +from spirit.game.card_effects.support_common import distribute_energy +from spirit.game.card_effects.trainers import is_basic_energy_card + + +async def bursting_power(ctx): + await ctx.deal_damage() + energies = [c for c in ctx.hand() if is_basic_energy_card(c)] + if not energies: + return + picks = await ctx.choose_cards( + energies, 2, minimum=0, + prompt="Choose up to 2 basic Energy cards to attach to your Pokémon", + ) + if picks: + await distribute_energy(ctx, picks, ctx.my_pokemon_in_play()) + card = PokemonCardDef( guid="a63c3a0b-0c27-59e0-a731-4a896318de3b", @@ -23,7 +40,7 @@ card = PokemonCardDef( game_text="You may attach up to 2 basic Energy cards from your hand to your Pok\u00e9mon in any way you like.", cost={PokemonTypes.FIRE: 1}, damage=20, - effect=unimplemented, + effect=bursting_power, ), Attack( title="Flare Juggling", @@ -31,7 +48,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 2}, damage=90, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("defender"), 30, base=90), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/Spiritomb_89.py b/spirit/game/scripts/cards/SWSH9/Spiritomb_89.py index 0850575..b7c2b19 100644 --- a/spirit/game/scripts/cards/SWSH9/Spiritomb_89.py +++ b/spirit/game/scripts/cards/SWSH9/Spiritomb_89.py @@ -1,5 +1,21 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import place_counters +from spirit.game.session.passives import Passive + + +class _TickingTerrorPassive(Passive): + def modify_weakness(self, calc, carrier): + if calc.target is carrier: + calc.weak_types = [PokemonTypes.DARKNESS.value] + + +async def ticking_terror(ctx): + """Until the end of your next turn, the Defending Pokemon's Weakness is Darkness.""" + target = ctx.defender + if target is not None: + ctx.add_passive_through_own_next_turn(target, _TickingTerrorPassive()) + card = PokemonCardDef( guid="c7096418-5364-56fb-b00a-566cfbc18b48", @@ -22,13 +38,13 @@ card = PokemonCardDef( title="Ticking Terror", game_text="Until the end of your next turn, the Defending Pok\u00e9mon's Weakness is now Darkness. (The amount of Weakness doesn't change.)", cost={PokemonTypes.COLORLESS: 1}, - effect=unimplemented, + effect=ticking_terror, ), Attack( title="Cursed Drop", game_text="Put 2 damage counters on your opponent's Pok\u00e9mon in any way you like.", cost={PokemonTypes.DARKNESS: 1}, - effect=unimplemented, + effect=place_counters(2, "choose_any_opponent"), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/Staraptor_119.py b/spirit/game/scripts/cards/SWSH9/Staraptor_119.py index 2d873e0..04ac200 100644 --- a/spirit/game/scripts/cards/SWSH9/Staraptor_119.py +++ b/spirit/game/scripts/cards/SWSH9/Staraptor_119.py @@ -1,5 +1,25 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.session.effects import full_stack +from spirit.game.card_effects.attacks_common import self_energy_discard_attack + + +async def strong_breeze(ctx): + """Your opponent shuffles their Active Pokemon and all attached cards into their deck.""" + target = ctx.opponent_active() + if target is None or ctx.effects_blocked(target): + return + await ctx.shuffle_into_deck(full_stack(target), ctx.opponent_id) + + async def _promote(): + if not await ctx.session._promote_new_active(ctx.opponent_id): + screen_name = ctx.session.players[ctx.opponent_id].screen_name + await ctx.session.end_game( + ctx.player_id, f"{screen_name} has no Pokémon left") + ctx.deferred_actions.append(_promote) + + +spinning_bird = self_energy_discard_attack(count=2) card = PokemonCardDef( guid="973a434c-a358-53bb-9edb-b6578dc8aeec", @@ -24,14 +44,14 @@ card = PokemonCardDef( title="Strong Breeze", game_text="Your opponent shuffles their Active Pok\u00e9mon and all attached cards into their deck.", cost={PokemonTypes.COLORLESS: 3}, - effect=unimplemented, + effect=strong_breeze, ), Attack( title="Spinning Bird", game_text="Discard 2 Energy from this Pok\u00e9mon.", cost={PokemonTypes.COLORLESS: 4}, damage=180, - effect=unimplemented, + effect=spinning_bird, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/Starly_117.py b/spirit/game/scripts/cards/SWSH9/Starly_117.py index 7b82595..ed43c42 100644 --- a/spirit/game/scripts/cards/SWSH9/Starly_117.py +++ b/spirit/game/scripts/cards/SWSH9/Starly_117.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_or_nothing card = PokemonCardDef( guid="ad219808-1683-53c4-95a1-9bd5ac7a7eaf", @@ -24,7 +25,7 @@ card = PokemonCardDef( game_text="Flip a coin. If tails, this attack does nothing.", cost={PokemonTypes.COLORLESS: 1}, damage=30, - effect=unimplemented, + effect=flip_or_nothing(), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/Starmie_55.py b/spirit/game/scripts/cards/SWSH9/Starmie_55.py index aeec0aa..88cc7c6 100644 --- a/spirit/game/scripts/cards/SWSH9/Starmie_55.py +++ b/spirit/game/scripts/cards/SWSH9/Starmie_55.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_energy card = PokemonCardDef( guid="bb1f0e43-0213-589f-942a-75d44a7d0cd1", @@ -26,7 +27,7 @@ card = PokemonCardDef( cost={PokemonTypes.PSYCHIC: 1}, damage=10, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_energy("defender"), 30, base=10), ), Attack( title="Power Gem", diff --git a/spirit/game/scripts/cards/SWSH9/Staryu_30.py b/spirit/game/scripts/cards/SWSH9/Staryu_30.py index 227e0c0..505a1d2 100644 --- a/spirit/game/scripts/cards/SWSH9/Staryu_30.py +++ b/spirit/game/scripts/cards/SWSH9/Staryu_30.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_damage card = PokemonCardDef( guid="e04ffc93-58d0-5526-87d1-65d46e2b1f46", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 1}, damage=10, damage_operator="x", - effect=unimplemented, + effect=flip_damage(coins=2, per_heads=10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/TeamYellsCheer_149.py b/spirit/game/scripts/cards/SWSH9/TeamYellsCheer_149.py index 2a1c71f..188c2d8 100644 --- a/spirit/game/scripts/cards/SWSH9/TeamYellsCheer_149.py +++ b/spirit/game/scripts/cards/SWSH9/TeamYellsCheer_149.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import SupporterCardDef, unimplemented +from spirit.game.data_utils import SupporterCardDef, def_for from spirit.game.attributes import Rarities +from spirit.game.session.effects import is_pokemon_card, is_supporter_card +from spirit.game.card_effects.support_common import recover_from_discard, requires_discard + + +def _team_yells_cheer_predicate(c): + if not (is_pokemon_card(c) or is_supporter_card(c)): + return False + definition = def_for(c.archetype_id) + return not (definition and definition.display_name == "Team Yell's Cheer") + card = SupporterCardDef( guid="7b2b7973-efcb-5a6f-b416-99a988a1e7e4", @@ -11,5 +21,9 @@ card = SupporterCardDef( collector_number=149, set_code="SWSH9", rarity=Rarities.Uncommon, - effect=unimplemented + effect=recover_from_discard( + _team_yells_cheer_predicate, count=3, to="deck_shuffle", + prompt="Choose up to 3 Pokémon and/or Supporter cards to shuffle into your deck", + ), + condition=requires_discard(_team_yells_cheer_predicate), ) diff --git a/spirit/game/scripts/cards/SWSH9/Torkoal_23.py b/spirit/game/scripts/cards/SWSH9/Torkoal_23.py index 4658914..f1fd8f0 100644 --- a/spirit/game/scripts/cards/SWSH9/Torkoal_23.py +++ b/spirit/game/scripts/cards/SWSH9/Torkoal_23.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus +from spirit.game.card_effects.passives_common import protect_next_turn card = PokemonCardDef( guid="a84b57ab-4e8a-58d9-a51b-a976fb09da8e", @@ -24,14 +26,14 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=30, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(30), ), Attack( title="Guard Press", game_text="During your opponent's next turn, this Pok\u00e9mon takes 30 less damage from attacks (after applying Weakness and Resistance).", cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 2}, damage=90, - effect=unimplemented, + effect=protect_next_turn(reduce=30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/Tornadus_126.py b/spirit/game/scripts/cards/SWSH9/Tornadus_126.py index 6c8067c..2ba6b40 100644 --- a/spirit/game/scripts/cards/SWSH9/Tornadus_126.py +++ b/spirit/game/scripts/cards/SWSH9/Tornadus_126.py @@ -1,5 +1,17 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.support_common import opponent_switches + + +async def sudden_cyclone(ctx): + """On play from hand: you may have your opponent switch their Active + Pokémon with 1 of their Benched Pokémon.""" + if ctx.opponent_bench() and await ctx.ask_yes_no( + "Have your opponent switch their Active Pokémon with 1 of their " + "Benched Pokémon?" + ): + await opponent_switches(ctx) + card = PokemonCardDef( guid="f5efc250-82e1-5a20-9ce9-d8544857ef02", @@ -22,7 +34,8 @@ card = PokemonCardDef( Ability( title="Sudden Cyclone", game_text="When you play this Pok\u00e9mon from your hand onto your Bench, you may have your opponent switch their Active Pok\u00e9mon with 1 of their Benched Pok\u00e9mon.", - effect=unimplemented, + trigger=Triggers.ON_PLAY, + effect=sudden_cyclone, ), Attack( title="Blasting Wind", diff --git a/spirit/game/scripts/cards/SWSH9/Torterra_8.py b/spirit/game/scripts/cards/SWSH9/Torterra_8.py index 6a76bd5..50225ee 100644 --- a/spirit/game/scripts/cards/SWSH9/Torterra_8.py +++ b/spirit/game/scripts/cards/SWSH9/Torterra_8.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_in_play +from spirit.game.session.effects import is_evolution_pokemon card = PokemonCardDef( guid="93b2cb2f-e92f-58c4-9f0d-0b49688e19db", @@ -25,7 +27,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1}, damage=50, damage_operator="x", - effect=unimplemented, + effect=damage_per(count_in_play("mine", is_evolution_pokemon), 50), ), Attack( title="Hammer In", diff --git a/spirit/game/scripts/cards/SWSH9/Trapinch_74.py b/spirit/game/scripts/cards/SWSH9/Trapinch_74.py index 29677f9..36cd069 100644 --- a/spirit/game/scripts/cards/SWSH9/Trapinch_74.py +++ b/spirit/game/scripts/cards/SWSH9/Trapinch_74.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import flip_bonus card = PokemonCardDef( guid="36dd30e3-724e-5320-b264-ed1e753f26ea", @@ -24,7 +25,7 @@ card = PokemonCardDef( cost={PokemonTypes.FIGHTING: 1}, damage=10, damage_operator="+", - effect=unimplemented, + effect=flip_bonus(10), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/Tropius_5.py b/spirit/game/scripts/cards/SWSH9/Tropius_5.py index 09a6d0e..9a2484b 100644 --- a/spirit/game/scripts/cards/SWSH9/Tropius_5.py +++ b/spirit/game/scripts/cards/SWSH9/Tropius_5.py @@ -1,5 +1,32 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers +from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions +from spirit.game.session.passives import Passive +from spirit.game.models.board import BoardState +from spirit.game.card_effects.pokemon import energy_provides_type + + +def _has_grass_energy(pokemon) -> bool: + return any( + energy_provides_type(e, PokemonTypes.GRASS.value) + for e in BoardState.attached_energies(pokemon) + ) + + +class CurativeBowerPassive(Passive): + def blocks_special_conditions(self, target, condition, carrier): + if condition != SpecialConditions.CONFUSED: + return False + if target is None or target.owning_player_id != carrier.owning_player_id: + return False + return _has_grass_energy(target) + + +async def curative_bower_cure(ctx): + """Cures Confusion on any of your Pokemon that already have Grass Energy attached.""" + for pokemon in ctx.my_pokemon_in_play(): + if _has_grass_energy(pokemon): + await ctx.cure_condition(pokemon, SpecialConditions.CONFUSED) + card = PokemonCardDef( guid="d2fa901f-fe1a-5efe-86d2-b4bb082f952f", @@ -21,7 +48,9 @@ card = PokemonCardDef( Ability( title="Curative Bower", game_text="All of your Pok\u00e9mon that have Grass Energy attached can't be Confused, and if they are already Confused, they recover from that Special Condition.", - effect=unimplemented, + passive=CurativeBowerPassive(), + trigger=(Triggers.ON_PLAY, Triggers.ON_ENERGY_ATTACHED), + effect=curative_bower_cure, ), Attack( title="Slicing Blade", diff --git a/spirit/game/scripts/cards/SWSH9/Weavile_87.py b/spirit/game/scripts/cards/SWSH9/Weavile_87.py index aa2f9df..ebb28ea 100644 --- a/spirit/game/scripts/cards/SWSH9/Weavile_87.py +++ b/spirit/game/scripts/cards/SWSH9/Weavile_87.py @@ -1,6 +1,25 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities + +async def ransack(ctx): + """Flip 2. If any heads, opponent reveals hand; for each heads, choose a + card there and put it on the bottom of their deck, in any order.""" + coins = await ctx.flip_coins(2, "Ransack") + heads = coins.count(True) + if heads == 0: + return + hand = ctx.hand(ctx.opponent_id) + if not hand: + return + picks = await ctx.choose_cards( + hand, heads, ordered=True, + prompt="Choose cards from your opponent's hand to put on the bottom of their deck, in any order.", + ) + for card in picks: + await ctx.put_on_bottom_of_deck(card) + + card = PokemonCardDef( guid="9d55755b-a5ee-5e26-b743-07730c67d4cb", key="SWSH9", @@ -23,7 +42,7 @@ card = PokemonCardDef( title="Ransack", game_text="Flip 2 coins. If either of them is heads, your opponent reveals their hand. For each heads, choose a card you find there and put it on the bottom of your opponent's deck in any order.", cost={PokemonTypes.DARKNESS: 1}, - effect=unimplemented, + effect=ransack, ), Attack( title="Slash", diff --git a/spirit/game/scripts/cards/SWSH9/WhimsicottVSTAR_175.py b/spirit/game/scripts/cards/SWSH9/WhimsicottVSTAR_175.py index e3226eb..01828c6 100644 --- a/spirit/game/scripts/cards/SWSH9/WhimsicottVSTAR_175.py +++ b/spirit/game/scripts/cards/SWSH9/WhimsicottVSTAR_175.py @@ -1,5 +1,35 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities, TrainerType +from spirit.game.card_effects.attacks_common import count_energy +from spirit.game.session.effects import is_special_energy + + +def _tool_or_special_energy(card): + return card.get_attribute(AttrID.TRAINER_TYPE) in ( + TrainerType.POKEMON_TOOL.value, TrainerType.POKEMON_TOOL_F.value + ) or is_special_energy(card) + + +async def trick_wind(ctx): + """160. During your opponent's next turn, they can't play any Pokémon Tool or Special Energy cards from their hand.""" + await ctx.deal_damage() + ctx.lock_plays(ctx.opponent_id, _tool_or_special_energy) + + +async def fluffball_star(ctx): + """VSTAR Power: 60 damage to 1 of your opponent's Pokémon for each Energy attached to this Pokémon. No W/R on Benched targets.""" + targets = ctx.opponent_pokemon_in_play() + if not targets: + return + target = await ctx.choose_pokemon(targets, "Choose 1 of your opponent's Pokémon") + if target is None: + return + amount = 60 * count_energy("self")(ctx) + if amount <= 0: + return + await ctx.deal_damage( + amount, target=target, apply_modifiers=(target is ctx.opponent_active()) + ) card = PokemonCardDef( guid="914cb40a-e940-5fda-b442-321cee34bdb3", @@ -24,13 +54,14 @@ card = PokemonCardDef( game_text="During your opponent's next turn, they can't play any Pok\u00e9mon Tool or Special Energy cards from their hand.", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 2}, damage=160, - effect=unimplemented, + effect=trick_wind, ), Attack( title="Fluffball Star", game_text="This attack does 60 damage to 1 of your opponent's Pok\u00e9mon for each Energy attached to this Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.) (You can't use more than 1 VSTAR Power in a game.)", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=fluffball_star, + vstar=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/WhimsicottVSTAR_65.py b/spirit/game/scripts/cards/SWSH9/WhimsicottVSTAR_65.py index f5406ae..3612f81 100644 --- a/spirit/game/scripts/cards/SWSH9/WhimsicottVSTAR_65.py +++ b/spirit/game/scripts/cards/SWSH9/WhimsicottVSTAR_65.py @@ -1,5 +1,35 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented -from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.data_utils import PokemonCardDef, Attack, Ability +from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities, TrainerType +from spirit.game.card_effects.attacks_common import count_energy +from spirit.game.session.effects import is_special_energy + + +def _tool_or_special_energy(card): + return card.get_attribute(AttrID.TRAINER_TYPE) in ( + TrainerType.POKEMON_TOOL.value, TrainerType.POKEMON_TOOL_F.value + ) or is_special_energy(card) + + +async def trick_wind(ctx): + """160. During your opponent's next turn, they can't play any Pokémon Tool or Special Energy cards from their hand.""" + await ctx.deal_damage() + ctx.lock_plays(ctx.opponent_id, _tool_or_special_energy) + + +async def fluffball_star(ctx): + """VSTAR Power: 60 damage to 1 of your opponent's Pokémon for each Energy attached to this Pokémon. No W/R on Benched targets.""" + targets = ctx.opponent_pokemon_in_play() + if not targets: + return + target = await ctx.choose_pokemon(targets, "Choose 1 of your opponent's Pokémon") + if target is None: + return + amount = 60 * count_energy("self")(ctx) + if amount <= 0: + return + await ctx.deal_damage( + amount, target=target, apply_modifiers=(target is ctx.opponent_active()) + ) card = PokemonCardDef( guid="54d210b3-ba37-589b-9110-3682a957fe06", @@ -24,13 +54,14 @@ card = PokemonCardDef( game_text="During your opponent's next turn, they can't play any Pok\u00e9mon Tool or Special Energy cards from their hand.", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 2}, damage=160, - effect=unimplemented, + effect=trick_wind, ), Attack( title="Fluffball Star", game_text="This attack does 60 damage to 1 of your opponent's Pok\u00e9mon for each Energy attached to this Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.) (You can't use more than 1 VSTAR Power in a game.)", cost={PokemonTypes.PSYCHIC: 1}, - effect=unimplemented, + effect=fluffball_star, + vstar=True, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/WhimsicottV_160.py b/spirit/game/scripts/cards/SWSH9/WhimsicottV_160.py index 4769f58..5a7a33f 100644 --- a/spirit/game/scripts/cards/SWSH9/WhimsicottV_160.py +++ b/spirit/game/scripts/cards/SWSH9/WhimsicottV_160.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import lock_defender_attacks +from spirit.game.card_effects.passives_common import protect_next_turn +from spirit.game.session.effects import is_basic_pokemon + + +async def fluff_gets_in_the_way(ctx): + """20. If the Defending Pokémon is a Basic Pokémon, it can't attack during your opponent's next turn.""" + await ctx.deal_damage() + defender = ctx.defender + if defender is not None and is_basic_pokemon(defender): + lock_defender_attacks(ctx) card = PokemonCardDef( guid="8862b0c1-8db9-51fe-a01a-74d8c058ea3c", @@ -23,14 +34,14 @@ card = PokemonCardDef( game_text="If the Defending Pok\u00e9mon is a Basic Pok\u00e9mon, it can't attack during your opponent's next turn.", cost={PokemonTypes.PSYCHIC: 1}, damage=20, - effect=unimplemented, + effect=fluff_gets_in_the_way, ), Attack( title="Cotton Guard", game_text="During your opponent's next turn, this Pok\u00e9mon takes 30 less damage from attacks (after applying Weakness and Resistance).", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 2}, damage=90, - effect=unimplemented, + effect=protect_next_turn(reduce=30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/WhimsicottV_64.py b/spirit/game/scripts/cards/SWSH9/WhimsicottV_64.py index af34fc8..8fd6185 100644 --- a/spirit/game/scripts/cards/SWSH9/WhimsicottV_64.py +++ b/spirit/game/scripts/cards/SWSH9/WhimsicottV_64.py @@ -1,5 +1,16 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import lock_defender_attacks +from spirit.game.card_effects.passives_common import protect_next_turn +from spirit.game.session.effects import is_basic_pokemon + + +async def fluff_gets_in_the_way(ctx): + """20. If the Defending Pokémon is a Basic Pokémon, it can't attack during your opponent's next turn.""" + await ctx.deal_damage() + defender = ctx.defender + if defender is not None and is_basic_pokemon(defender): + lock_defender_attacks(ctx) card = PokemonCardDef( guid="0b1665e3-c8dc-5143-8894-199d40a96cfa", @@ -23,14 +34,14 @@ card = PokemonCardDef( game_text="If the Defending Pok\u00e9mon is a Basic Pok\u00e9mon, it can't attack during your opponent's next turn.", cost={PokemonTypes.PSYCHIC: 1}, damage=20, - effect=unimplemented, + effect=fluff_gets_in_the_way, ), Attack( title="Cotton Guard", game_text="During your opponent's next turn, this Pok\u00e9mon takes 30 less damage from attacks (after applying Weakness and Resistance).", cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 2}, damage=90, - effect=unimplemented, + effect=protect_next_turn(reduce=30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/Wormadam_10.py b/spirit/game/scripts/cards/SWSH9/Wormadam_10.py index 5692b3e..b442ee1 100644 --- a/spirit/game/scripts/cards/SWSH9/Wormadam_10.py +++ b/spirit/game/scripts/cards/SWSH9/Wormadam_10.py @@ -1,5 +1,8 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_discard +from spirit.game.card_effects.support_common import heal_attack +from spirit.game.session.effects import is_pokemon_card card = PokemonCardDef( guid="286d616a-a345-52d0-9d5c-58c2749e98a9", @@ -25,14 +28,14 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=30, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_discard("mine", is_pokemon_card), 10, base=30), ), Attack( title="Leaf Drain", game_text="Heal 30 damage from this Pok\u00e9mon.", cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 2}, damage=80, - effect=unimplemented, + effect=heal_attack(30), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/Wormadam_77.py b/spirit/game/scripts/cards/SWSH9/Wormadam_77.py index 2a7f225..7d4b170 100644 --- a/spirit/game/scripts/cards/SWSH9/Wormadam_77.py +++ b/spirit/game/scripts/cards/SWSH9/Wormadam_77.py @@ -1,5 +1,15 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_discard +from spirit.game.session.effects import is_pokemon_card + + +async def bind_down(ctx): + """80. During your opponent's next turn, the Defending Pokémon can't retreat.""" + await ctx.deal_damage() + defender = ctx.defender + if defender is not None and not ctx.effects_blocked(defender): + ctx.lock_retreat(defender) card = PokemonCardDef( guid="c878ca6a-06b1-5e0b-9c7a-d2b31cea772b", @@ -25,14 +35,14 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=30, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_discard("mine", is_pokemon_card), 10, base=30), ), Attack( title="Bind Down", game_text="During your opponent's next turn, the Defending Pok\u00e9mon can't retreat.", cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 2}, damage=80, - effect=unimplemented, + effect=bind_down, ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/Wormadam_98.py b/spirit/game/scripts/cards/SWSH9/Wormadam_98.py index 2735d3a..3638f1b 100644 --- a/spirit/game/scripts/cards/SWSH9/Wormadam_98.py +++ b/spirit/game/scripts/cards/SWSH9/Wormadam_98.py @@ -1,5 +1,7 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_discard +from spirit.game.session.effects import is_pokemon_card card = PokemonCardDef( guid="9426930f-8239-5c78-899f-a1190da687c9", @@ -26,7 +28,7 @@ card = PokemonCardDef( cost={PokemonTypes.COLORLESS: 2}, damage=30, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_discard("mine", is_pokemon_card), 10, base=30), ), Attack( title="Scrap Drop", diff --git a/spirit/game/scripts/cards/SWSH9/ZamazentaV_105.py b/spirit/game/scripts/cards/SWSH9/ZamazentaV_105.py index 088ead9..9555992 100644 --- a/spirit/game/scripts/cards/SWSH9/ZamazentaV_105.py +++ b/spirit/game/scripts/cards/SWSH9/ZamazentaV_105.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_prizes_taken + + +async def regal_stance(ctx): + """Once per turn: discard your hand and draw 5 cards. Ends your turn.""" + await ctx.discard_cards(ctx.hand()) + await ctx.draw_cards(5) + card = PokemonCardDef( guid="815b2a05-57ca-5775-a8f1-710da40f4e40", @@ -22,7 +30,9 @@ card = PokemonCardDef( Ability( title="Regal Stance", game_text="Once during your turn, you may discard your hand and draw 5 cards. If you use this Ability, your turn ends.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + ends_turn=True, + effect=regal_stance, ), Attack( title="Revenge Blast", @@ -30,7 +40,7 @@ card = PokemonCardDef( cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 2}, damage=120, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_prizes_taken("opponent"), 30, base=120), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/ZamazentaV_163.py b/spirit/game/scripts/cards/SWSH9/ZamazentaV_163.py index 85a8ff2..6fa3eae 100644 --- a/spirit/game/scripts/cards/SWSH9/ZamazentaV_163.py +++ b/spirit/game/scripts/cards/SWSH9/ZamazentaV_163.py @@ -1,5 +1,13 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import damage_per, count_prizes_taken + + +async def regal_stance(ctx): + """Once per turn: discard your hand and draw 5 cards. Ends your turn.""" + await ctx.discard_cards(ctx.hand()) + await ctx.draw_cards(5) + card = PokemonCardDef( guid="dc3330a5-6e5e-53b7-9ff7-5aab9078adcc", @@ -22,7 +30,9 @@ card = PokemonCardDef( Ability( title="Regal Stance", game_text="Once during your turn, you may discard your hand and draw 5 cards. If you use this Ability, your turn ends.", - effect=unimplemented, + activation=Activations.ONCE_PER_TURN, + ends_turn=True, + effect=regal_stance, ), Attack( title="Revenge Blast", @@ -30,7 +40,7 @@ card = PokemonCardDef( cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 2}, damage=120, damage_operator="+", - effect=unimplemented, + effect=damage_per(count_prizes_taken("opponent"), 30, base=120), ), ], ) \ No newline at end of file diff --git a/spirit/game/scripts/cards/SWSH9/ZarudeV_16.py b/spirit/game/scripts/cards/SWSH9/ZarudeV_16.py index 4213c4a..a5091f0 100644 --- a/spirit/game/scripts/cards/SWSH9/ZarudeV_16.py +++ b/spirit/game/scripts/cards/SWSH9/ZarudeV_16.py @@ -1,5 +1,6 @@ -from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented +from spirit.game.data_utils import PokemonCardDef, Attack, Ability from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities +from spirit.game.card_effects.attacks_common import bonus_if, snipe_attack, defender_is_v card = PokemonCardDef( guid="13b89f67-e10e-5a0c-b5a0-0ffa88ab20f7", @@ -23,7 +24,7 @@ card = PokemonCardDef( game_text="This attack also does 30 damage to 1 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)", cost={PokemonTypes.GRASS: 1}, damage=30, - effect=unimplemented, + effect=snipe_attack(30, pool="bench", count=1, also_base=True), ), Attack( title="Jungle Rage", @@ -31,7 +32,7 @@ card = PokemonCardDef( cost={PokemonTypes.GRASS: 2, PokemonTypes.COLORLESS: 1}, damage=120, damage_operator="+", - effect=unimplemented, + effect=bonus_if(defender_is_v, 120), ), ], ) \ No newline at end of file diff --git a/spirit/game/session/constants.py b/spirit/game/session/constants.py index c6abc8c..155c43d 100644 --- a/spirit/game/session/constants.py +++ b/spirit/game/session/constants.py @@ -179,6 +179,9 @@ PROMPT_TAKE_PRIZE = "Choose a Prize card to take" PROMPT_CHOOSE_NEW_ACTIVE = "Choose a Pokémon to be your new Active Pokémon" PROMPT_REVEAL_BASIC_FROM_PRIZE = "You may reveal a Basic Pokémon from your Prize cards." +# Bench-shrink ruling (Collapsed Stadium): the owner picks the excess to discard. +PROMPT_DISCARD_BENCH = "Choose a Benched Pokémon to discard" + # Pokemon Checkup (sleep flip) texts. TEXT_SLEEP_CHECK = "Asleep Check" TEXT_WOKE_UP = "Woke up!" @@ -193,3 +196,13 @@ TEXT_STILL_BURNED = "Still Burned!" TEXT_CONFUSION_CHECK = "Confusion Check" TEXT_CONFUSION_PROCEEDS = "Attack proceeds!" TEXT_CONFUSION_HURT = "Hurt itself in confusion!" + +# Smokescreen-family attack-attempt flip texts. +TEXT_ATTACK_FLIP_CHECK = "Attack Check" +TEXT_ATTACK_FLIP_PROCEEDS = "Attack proceeds!" +TEXT_ATTACK_FLIP_FAILS = "The attack didn't happen!" + +# Energy-attach tax flip texts (Slimy Room). +TEXT_ATTACH_TAX_CHECK = "Energy Attach Check" +TEXT_ATTACH_TAX_OK = "Attached!" +TEXT_ATTACH_TAX_DISCARD = "Discarded instead!" diff --git a/spirit/game/session/effects.py b/spirit/game/session/effects.py index e94e197..2d57ebc 100644 --- a/spirit/game/session/effects.py +++ b/spirit/game/session/effects.py @@ -39,6 +39,9 @@ from .passives import ( TempPassive, ability_effects_blocked, ability_locked, + active_passives, + active_to_bench_counters, + attack_coin_reroll_offered, attack_effects_blocked, carrier_pokemon, compute_damage, @@ -46,7 +49,11 @@ from .passives import ( discard_blocked, effective_bench_capacity, effective_max_hp, + effective_pokemon_types, + energy_removal_blocked, healing_blocked, + supporter_effect_replacement, + trainer_effects_blocked, ) # CakeAttackEffect's damageType is a string array of client type names. @@ -106,13 +113,31 @@ class EffectContext: # Set by the effect (or auto-set from Ability.ends_turn) to end the # acting player's turn once this effect resolves (Rotom Bike). self.ends_turn: bool = False + # Triggered "you may" effects set this when the window never opened: + # skips even the announce-only PokeAbility bracket. + self.suppress_announce: bool = False + # Attack-flow only: the resolved attack does NOT end the turn. + self.attack_keeps_turn: bool = False + # Snapshot of ctx.knockouts taken just before resolve_knockouts clears + # it (post-attack hooks need the KO evidence). + self.knockouts_resolved: List[PokemonEntity] = [] # Attack titles already resolving in this attack (copy-loop guard). self._copy_chain: List[str] = [] + # Wire-encoded results (0=heads) of coins this effect itself flipped; + # defensive interceptor flips excluded (Blunder Policy's tails check). + self.coin_results: List[int] = [] + self._in_interceptor = False self._messages: List[Tuple[Optional[str], Dict[str, Any], Optional[str]]] = [] # Set by resolve_knockouts before firing an ON_KNOCKED_OUT trigger: # True iff the causing damage was an opposing Pokemon's attack. self.ko_from_attack: bool = False self.ko_attacker: Optional[PokemonEntity] = None + # ON_KNOCKED_OUT only: whether this Pokemon sat in the Active spot at + # the moment it was Knocked Out (Radiant Jirachi). + self.was_active_at_ko: bool = False + # Set by resolve_trainer_effect: primitives against a shielded OTHER + # player no-op (Dew Guard, via _trainer_blocked). + self.is_trainer_effect: bool = False # ON_ALLY_KNOCKED_OUT: the KO'd ally (still on board, energies attached). self.ko_pokemon: Optional[PokemonEntity] = None # target entity_id -> (dealt, pre_hit_hp); first attack hit wins. @@ -126,6 +151,12 @@ class EffectContext: self.attaching_player_id: Optional[str] = None self.attached_energy: Optional[CardEntity] = None self.energy_receiver: Optional[PokemonEntity] = None + # ON_ALLY_EVOLVED trigger inputs (set via ctx_setup). + self.evolved_pokemon: Optional[CardEntity] = None + self.evolved_from: Optional[PokemonEntity] = None + # ON_POKEMON_BENCHED trigger inputs (set via ctx_setup). + self.benching_player_id: Optional[str] = None + self.benched_pokemon: Optional[PokemonEntity] = None # ------------------------------------------------------------------ # Game state accessors @@ -232,6 +263,61 @@ class EffectContext: return ability_effects_blocked(self.board, target) return False + def _trainer_blocked(self, player_or_entity) -> bool: + """Dew Guard shield: in a trainer context, True when the primitive's + direct object belongs to a shielded player OTHER than the acting one. + Guards: deal_damage/counters, apply_special_condition, switch_active, + draw_cards/draw_until, discard_from_hand, hand_to_bottom_of_deck, + shuffle_into_deck, discard_cards/move_to_lost_zone (per card).""" + if not self.is_trainer_effect: + return False + pid = player_or_entity if isinstance(player_or_entity, str) \ + else getattr(player_or_entity, "owning_player_id", None) + if pid is None or pid == self.player_id: + return False + entity = None if isinstance(player_or_entity, str) else player_or_entity + if trainer_effects_blocked(self.board, pid, self.source, entity): + logging.info( + f"[Effects {self.game_id}] Trainer effect on {pid} blocked " + f"by a trainer-effect shield." + ) + return True + return False + + def _energy_removal_blocked(self, card) -> bool: + """Brazen Tail shield: in a trainer context, True when a passive keeps + this attached Energy from moving to hand/deck/discard.""" + if not self.is_trainer_effect: + return False + if energy_removal_blocked(self.board, self.player_id, card): + logging.info( + f"[Effects {self.game_id}] Energy removal of {card.entity_id} " + f"blocked by a passive." + ) + return True + return False + + async def _run_damage_interceptors(self, calc, target: PokemonEntity) -> int: + """Awaitable damage stage (Guts/Infiltrator): runs AFTER compute_damage + and BEFORE the HP write; each interceptor may rewrite calc.amount. + Interceptors riding the target are skipped under ignore_target_effects + (a flip-prevention IS an effect on the opponent's Active -- Max + Miracle ruling).""" + self._in_interceptor = True + try: + for passive, carrier in active_passives(self.board): + interceptor = getattr(passive, "damage_interceptor", None) + if interceptor is None: + continue + if calc.ignore_target_effects and carrier_pokemon(carrier) is target: + continue + new_amount = await interceptor(self, calc, target, carrier) + if new_amount is not None: + calc.amount = max(0, int(new_amount)) + finally: + self._in_interceptor = False + return calc.amount + # ------------------------------------------------------------------ # Damage / HP primitives # ------------------------------------------------------------------ @@ -263,6 +349,8 @@ class EffectContext: if target is None: logging.warning(f"[Effects {self.game_id}] deal_damage with no target; skipped.") return 0 + if self._trainer_blocked(target): + return 0 base = amount if amount is not None else getattr(self.ability, "damage", 0) if base <= 0: return 0 @@ -270,6 +358,11 @@ class EffectContext: apply_modifiers = target is self.opponent_active() if is_attack is None: is_attack = self.is_attack_effect() + # Turn-scoped Max Miracle flag on the attacker (Phoebe). + if not ignore_target_effects and self.attacker is not None \ + and self.attacker.entity_id in \ + self.session.turn_state.ignore_target_effects_entities: + ignore_target_effects = True if as_counters: # Damage counters are RAW -- not "damage from an attack", so they @@ -295,6 +388,8 @@ class EffectContext: ) return 0 dealt = calc.amount + if dealt > 0: + dealt = await self._run_damage_interceptors(calc, target) current = target.get_attribute(AttrID.HP, 0) remaining = max(0, current - dealt) @@ -313,7 +408,7 @@ class EffectContext: self.visual_targets.append(target.entity_id) else: title = self.ability.title if self.ability else "" - attacker_types = self.attacker.get_attribute(AttrID.POKEMON_TYPES) or [] + attacker_types = effective_pokemon_types(self.board, self.attacker) type_name = CLIENT_TYPE_NAMES.get(attacker_types[0], "Colorless") \ if attacker_types else "Colorless" self._queue(self.session._build_msg( @@ -406,6 +501,8 @@ class EffectContext: """ if target is None: return False + if self._trainer_blocked(target): + return False if self.effects_blocked(target): logging.info( f"[Effects {self.game_id}] {condition.name} on {target.entity_id} " @@ -528,6 +625,42 @@ class EffectContext: through the opponent's next turn); manual attach offers exclude it.""" self.session.turn_state.restrict_attachments(target.entity_id, through_turn) + def require_attack_flip(self, target: Optional[PokemonEntity], + through_turn: Optional[int] = None, + title: Optional[str] = None) -> None: + """Smokescreen: "if the Defending Pokemon tries to attack ... flip a + coin. If tails, that attack doesn't happen" (default: through the + opponent's next turn; cleared when the target leaves the Active).""" + if target is None: + return + self.session.turn_state.set_attack_flip_check( + target.entity_id, through_turn, + title if title is not None else (self.ability.title if self.ability else ""), + ) + + def ignore_own_target_effects(self, entity: PokemonEntity) -> None: + """"During this turn, 's attacks aren't affected by effects on + the opponent's Active" (Phoebe); cleared at begin_turn.""" + self.session.turn_state.ignore_target_effects_entities.add(entity.entity_id) + + def take_extra_turn(self) -> None: + """"Take another turn after this one. (Skip Pokemon Checkup.)" + (Star Chronos, Yoga Loop); consumed by run_turn_loop.""" + self.session.extra_turn_pending = True + + def schedule_at_checkup(self, turns_ahead: int, effect, + guard: Optional[Callable] = None) -> None: + """Schedules async `effect(session)` at the START of the Pokemon + Checkup `turns_ahead` turns from now (0 = this turn's checkup, 1 = the + checkup ending the opponent's next turn -- Word of Ruin's delayed KO); + `guard(board)` returning False at fire time drops it silently.""" + self.session.scheduled_effects.append({ + "fire_at_checkup_of_turn": + self.session.turn_state.turn_number + turns_ahead, + "guard": guard, + "effect": effect, + }) + # ------------------------------------------------------------------ # Turn-history accessors (two turns kept, rotated at begin_turn) # ------------------------------------------------------------------ @@ -630,11 +763,19 @@ class EffectContext: self.session.turn_state.lock_attack(self.attacker.entity_id, ability.ability_id) return True - async def _queue_coin_results(self, results: List[int], title: str): + async def _queue_coin_results(self, results: List[int], title: str, + source: Optional[BoardEntity] = None, + immediate: bool = False): """Sends ONE MultipleCoinFlipWithContextEffect for a pre-rolled run (0 = heads); queued inline in attack/ability context, sent as its own - PokeAbility bracket + pacing pause in trainer context.""" + PokeAbility bracket + pacing pause in trainer context. `source` + overrides the flip's visual anchor (defensive flips ride the carrier); + `immediate` forces the send-now path (Glimwood re-flip: the player + must SEE the run before deciding).""" heads = results.count(0) + if not self._in_interceptor: + self.coin_results.extend(results) + source = source if source is not None else self.source self.session.stat_add(self.player_id, "headsflipped", heads) self.session.stat_add(self.player_id, "tailsflipped", len(results) - heads) msg = self.session._build_msg( @@ -642,13 +783,13 @@ class EffectContext: { "gameID": self.game_id, "resultLst": results, - "title": {"id": title or _display_name(self.source)}, + "title": {"id": title or _display_name(source)}, "gameText": {"id": f"{heads} heads"}, - "source": self.source.entity_id, - "targets": [self.source.entity_id], + "source": source.entity_id, + "targets": [source.entity_id], }, ) - if self.ability is not None: + if self.ability is not None and not immediate: # Attack/PokeAbility brackets play the coin flip natively inline. self._queue(msg) else: @@ -660,22 +801,64 @@ class EffectContext: ) await self.session.choreo_pause(2.5) - async def flip_coins(self, count: int, title: str = "") -> List[bool]: + async def _maybe_reroll_attack_coins( + self, results: List[int], title: str, + source: Optional[BoardEntity], reroll: Callable[[], List[int]], + ) -> Optional[List[int]]: + """Glimwood Tangle: once during their turn, after flipping coins for + an attack, the player may ignore the results and flip again. Returns + the final (already-displayed) run, or None when no re-flip was offered + (the caller queues the flip normally). `source`-anchored runs are + defensive/carrier flips, never the player's own attack coins.""" + if source is not None or not self.is_attack_effect(): + return None + ts = self.session.turn_state + if ts.attack_coin_reroll_used or self.player_id != ts.active_player_id: + return None + if not attack_coin_reroll_offered(self.board, self.player_id): + return None + await self._queue_coin_results(results, title, source, immediate=True) + if not await self.ask_yes_no( + "Ignore all results of those coin flips and begin flipping again?"): + return results + ts.attack_coin_reroll_used = True + # "Ignore all results of those coin flips": the ignored run must not + # feed post-attack followups (Blunder Policy's tails check). + del self.coin_results[len(self.coin_results) - len(results):] + new_results = reroll() + await self._queue_coin_results(new_results, title, source, immediate=True) + return new_results + + async def flip_coins(self, count: int, title: str = "", + source: Optional[BoardEntity] = None) -> List[bool]: """Flips `count` coins for a card effect ("Flip 2 coins..."); returns - the results, True = heads.""" + the results, True = heads. `source` re-anchors the flip visual.""" if count <= 0: return [] results = [random.choice([0, 1]) for _ in range(count)] - await self._queue_coin_results(results, title) + final = await self._maybe_reroll_attack_coins( + results, title, source, + lambda: [random.choice([0, 1]) for _ in range(count)]) + if final is None: + await self._queue_coin_results(results, title, source) + else: + results = final return [r == 0 for r in results] async def flip_until_tails(self, title: str = "") -> int: """"Flip a coin until you get tails": one coin screen shows the whole run; returns the heads count.""" - results = [random.choice([0, 1])] - while results[-1] == 0: - results.append(random.choice([0, 1])) - await self._queue_coin_results(results, title) + def _run() -> List[int]: + run = [random.choice([0, 1])] + while run[-1] == 0: + run.append(random.choice([0, 1])) + return run + results = _run() + final = await self._maybe_reroll_attack_coins(results, title, None, _run) + if final is None: + await self._queue_coin_results(results, title) + else: + results = final return results.count(0) async def place_damage_counters( @@ -937,6 +1120,8 @@ class EffectContext: ) -> List[CardEntity]: """Chooser over the player's hand, then discards the picks.""" pid = player_id or self.player_id + if self._trainer_blocked(pid): + return [] excluded = set(id(c) for c in (exclude or [])) cards = [c for c in self.hand(pid) if id(c) not in excluded and (predicate is None or predicate(c))] @@ -953,6 +1138,8 @@ class EffectContext: async def draw_cards(self, count: int, player_id: Optional[str] = None) -> int: """Draws cards for a player (default: the effect's owner); returns how many.""" pid = player_id or self.player_id + if self._trainer_blocked(pid): + return 0 moved = self.board.draw_cards(pid, count) if moved: self.session.stat_add(pid, "cardsdrawn", len(moved)) @@ -993,6 +1180,8 @@ class EffectContext: hand = self.board.find_player_area(owner, "hand") if not hand: continue + if self._energy_removal_blocked(card): + continue self._note_visual_source(card) position = len(hand.children) if not self.board.move_card(card.entity_id, hand.entity_id): @@ -1127,6 +1316,8 @@ class EffectContext: pile = self.board.find_player_area(owner, area_name) if not pile: continue + if self._trainer_blocked(card): + continue # Opponent-caused discards can be shielded (Bibarel PGO/Greedent); # own discards (costs) always resolve. if area_name == "discard" and owner != self.player_id \ @@ -1136,6 +1327,8 @@ class EffectContext: f"blocked by a passive." ) continue + if area_name == "discard" and self._energy_removal_blocked(card): + continue holder = self._tool_holder_before_move(card) source = getattr(card, "parent", None) # deck/prizes are face-down to the owner too, so a card from there @@ -1162,6 +1355,15 @@ class EffectContext: self._queue(move, bracket=GameSequence.GROUPED_MOVE.value) if holder is not None: await self.session.refresh_granted_abilities(holder) + if area_name == "discard" and owner != self.player_id \ + and source is not None \ + and source.get_attribute(AttrID.NAME) == "hand": + # Opponent-forced hand discard (Amoonguss "Surprise Spores"): + # fire deferred so the trigger's brackets follow this flush. + async def _fire_hand_discard(card=card, owner=owner): + await self.session._fire_triggered_abilities( + owner, card, Triggers.ON_DISCARDED_FROM_HAND) + self.deferred_actions.append(_fire_hand_discard) async def look_at_prizes_take_basic(self) -> bool: """Hisuian Heavy Ball: look at your face-down Prizes; you may reveal a @@ -1252,6 +1454,8 @@ class EffectContext: if not deck: return for card in cards: + if self._trainer_blocked(card) or self._energy_removal_blocked(card): + continue holder = self._tool_holder_before_move(card) position = len(deck.children) if self.board.move_card(card.entity_id, deck.entity_id): @@ -1278,6 +1482,8 @@ class EffectContext: PlaceOnBottom lifts the deck; V.s NREs without a PlaceOnBottom. """ pid = player_id or self.player_id + if self._trainer_blocked(pid): + return 0 hand = self.board.find_player_area(pid, "hand") deck = self.board.find_player_area(pid, "deck") if not hand or not deck or not hand.children: @@ -1337,7 +1543,7 @@ class EffectContext: """Puts a card on top of its owner's deck.""" owner = card.owning_player_id or self.player_id deck = self.board.find_player_area(owner, "deck") - if not deck: + if not deck or self._energy_removal_blocked(card): return False position = len(deck.children) if not self.board.move_card(card.entity_id, deck.entity_id): @@ -1352,7 +1558,7 @@ class EffectContext: """Puts a card on the bottom of its owner's deck (position 0).""" owner = card.owning_player_id or self.player_id deck = self.board.find_player_area(owner, "deck") - if not deck: + if not deck or self._energy_removal_blocked(card): return False if not self.board.move_card(card.entity_id, deck.entity_id, 0): return False @@ -1400,6 +1606,58 @@ class EffectContext: owner, evolution_card, target, from_zone_intro=from_hidden ) + async def devolve_pokemon(self, pokemon: PokemonEntity, steps: int = 1, + destination: str = "hand") -> List[CardEntity]: + """Devolves an evolved Pokemon `steps` times (highest stage first); + removed evolution cards go to the owner's `destination` ('hand' or + 'deck'). Damage carries down; a final stage left at 0 HP queues on + ctx.knockouts (the caller's flow resolves it). Flushes queued + choreography first so the Devolve brackets land in order.""" + removed: List[CardEntity] = [] + if pokemon is None: + return removed + await self.flush_choreography() + top = pokemon + for i in range(steps): + result = await self.session.perform_devolution( + top, destination_name=destination, + clamp_nonlethal=(i < steps - 1), + ) + if result is None: + break + top, removed_card = result + removed.append(removed_card) + # The removed card left play to hand/deck: any KO queued against + # it (Rewind Beam's 180 first) follows the remaining stage instead. + if removed_card in self.knockouts: + self.knockouts.remove(removed_card) + if removed and top.get_attribute(AttrID.HP, 0) <= 0 \ + and top not in self.knockouts: + self.knockouts.append(top) + return removed + + async def identity_swap(self, outgoing: PokemonEntity, incoming: CardEntity, + destination: str = "discard", + transfer: bool = True) -> bool: + """Switches an in-play Pokemon with a hand/discard card; see + GameSession.perform_identity_swap. transfer=True carries attachments/ + damage/conditions/turn stamps onto the new Pokemon; a transferred + damage total covering its HP queues a knockout. Flushes queued + choreography first so the swap brackets land in order.""" + if outgoing is None or incoming is None: + return False + await self.flush_choreography() + result = await self.session.perform_identity_swap( + outgoing, incoming, destination_name=destination, transfer=transfer) + if result is None: + return False + if outgoing in self.knockouts: + self.knockouts.remove(outgoing) + if incoming.get_attribute(AttrID.HP, 0) <= 0 \ + and incoming not in self.knockouts: + self.knockouts.append(incoming) + return True + async def attach_energy(self, energy: CardEntity, pokemon: PokemonEntity, counts_as_attachment: bool = False) -> bool: """Attaches an energy card from any zone underneath a Pokemon @@ -1452,6 +1710,7 @@ class EffectContext: """Keeps damage-taken constant when an attachment's max-HP bonus arrives/leaves mid-effect; queues the HP update, enqueues a KO at 0.""" max_after = effective_max_hp(self.board, pokemon) + self.session._effective_max_seen[pokemon.entity_id] = max_after delta = max_after - max_before if delta == 0: return @@ -1500,6 +1759,10 @@ class EffectContext: async def switch_active(self, player_id: str, new_active: PokemonEntity) -> bool: """Swaps a player's Active with the given benched Pokemon (gust or self-switch). Special Conditions on the leaving Active are cured.""" + # Guard on the gusted bench Pokemon: entity-scoped shields (Princess's + # Curtain) protect the chosen bencher, not the whole side. + if self._trainer_blocked(new_active): + return False board = self.board active_area = board.find_player_area(player_id, "activePokemonArea") bench_area = board.find_player_area(player_id, "bench") @@ -1550,6 +1813,13 @@ class EffectContext: self.session._entity_moved_msg(old_active.entity_id, bench_area.entity_id, slot), bracket=bracket, ) + # Spikemuth: an Active moving to its owner's Bench during THEIR turn + # takes counters (a gust during the opponent's turn does not). + if player_id == self.session.turn_state.active_player_id: + counters = active_to_bench_counters(board, old_active) + if counters > 0: + await self.deal_damage( + counters * 10, target=old_active, as_counters=True) # ON_MOVE_TO_ACTIVE fires after the swap choreography flushes. self.deferred_actions.append( lambda p=new_active: self.session.fire_move_to_active_triggers(p)) @@ -1562,19 +1832,54 @@ class EffectContext: await self.session._flush_effect_runs(self) self._messages.clear() - async def take_prizes(self, count: int, player_id: Optional[str] = None) -> None: + async def take_prizes(self, count: int, player_id: Optional[str] = None, + minimum: Optional[int] = None, + check_win: bool = True) -> List[CardEntity]: """Takes prize cards outside the KO flow (Slowbro PGO): flushes the queued choreography first so the prize fan never interleaves, then - runs the standard pick + WithOpenPrizeCards flow and the win check.""" + runs the standard pick + WithOpenPrizeCards flow and the win check. + minimum int: + """Puts hand cards face down into the player's empty Prize slots + (Peonia): queues the moves, AttributesReset re-hides (the owner knows + the faces) and the pile's gap refresh; returns how many were placed.""" + pid = player_id or self.player_id + session = self.session + prize_area = self.board.find_player_area(pid, "prizePile") + if prize_area is None or not cards: + return 0 + dealt = self.board.prizes_dealt.get(pid, 0) + occupied = {c.board_slot if c.board_slot is not None else i + for i, c in enumerate(prize_area.children)} + slots = [s for s in range(dealt) if s not in occupied] + placed = 0 + for card in cards: + slot = slots.pop(0) if slots else dealt + placed + if not self.board.move_card(card.entity_id, prize_area.entity_id, slot): + continue + self._queue(session._entity_moved_msg( + card.entity_id, prize_area.entity_id, slot), + bracket=GameSequence.GROUPED_MOVE.value) + self._queue(session._attributes_reset_msg(card.entity_id), + bracket=GameSequence.GROUPED_MOVE.value) + placed += 1 + if placed: + self._queue(session._refresh_prize_gaps(pid, prize_area), + bracket=GameSequence.GROUPED_MOVE.value) + return placed async def win_game(self, reason: str = "") -> None: """Declares the effect's owner the winner (Unown V; raises GameOver).""" @@ -1767,9 +2072,29 @@ async def resolve_attack(session, player_id: str, attacker: PokemonEntity, # ON_DAMAGED_BY_ATTACK fires after the attack choreography but BEFORE the # knockout stacks move ("even if this Pokemon is Knocked Out"). await _fire_damaged_by_attack_triggers(session, ctx) + ctx.knockouts_resolved = list(ctx.knockouts) await session.resolve_knockouts(ctx) for hook in ctx.deferred_actions: await hook() + # A KO'd/removed carrier may have shrunk a bench (Eternatus VMAX leaving). + await session.enforce_bench_capacity() + await _run_attack_followups(session, ctx) + return ctx + + +async def _run_attack_followups(session, ctx: EffectContext): + """End-of-turn attack riders (Blunder Policy): runs each active passive's + attack_followup with the flushed ctx; new messages flush as own brackets.""" + followups = [(p, c) for p, c in active_passives(session.board_state) + if getattr(p, "attack_followup", None) is not None] + if not followups: + return + # The attack bracket already sent ctx's queue; start a fresh one. + ctx._messages.clear() + for passive, carrier in followups: + await passive.attack_followup(ctx, carrier) + await session._flush_effect_runs(ctx) + ctx._messages.clear() async def _fire_damaged_by_attack_triggers(session, ctx: EffectContext): @@ -1894,10 +2219,13 @@ async def _send_ability_brackets(session, ctx: EffectContext, if not ctx._messages: # Declined/no-op effect: announce only (popin + gamelog, no orb). - for pid, viewer in session.players.items(): - await session.send_game_sequence( - [viewer], GameSequence.POKE_ABILITY, [head] + extra + [tail] - ) + if not ctx.suppress_announce: + for pid, viewer in session.players.items(): + await session.send_game_sequence( + [viewer], GameSequence.POKE_ABILITY, [head] + extra + [tail] + ) + # A message-less effect can still grant a bench-capacity passive. + await session.enforce_bench_capacity() return # The Attack executor dereferences the playmat's attack-source [0]. @@ -1936,6 +2264,7 @@ async def _send_ability_brackets(session, ctx: EffectContext, await session.resolve_knockouts(ctx, _ko_depth=_ko_depth) for hook in ctx.deferred_actions: await hook() + await session.enforce_bench_capacity() async def resolve_trainer_effect(session, player_id: str, card) -> Optional[EffectContext]: @@ -1946,6 +2275,16 @@ async def resolve_trainer_effect(session, player_id: str, card) -> Optional[Effe player here, before any choreography is sent; the caller flushes ctx.bracket_runs_for into brackets afterwards. """ + # Shifty Substitution: a passive may replace a Supporter's effect wholesale + # (consulted BEFORE the effect registry -- unscripted cards replace too). + if card.get_attribute(AttrID.TRAINER_TYPE) == TrainerType.SUPPORTER.value: + replacement = supporter_effect_replacement( + session.board_state, card, player_id) + if replacement is not None: + ctx = EffectContext(session, player_id, card, None) + ctx.is_trainer_effect = True + await replacement(ctx) + return ctx effect = TRAINER_EFFECTS_BY_GUID.get((card.archetype_id or "").lower()) if effect is None or effect is unimplemented: logging.warning( @@ -1957,6 +2296,7 @@ async def resolve_trainer_effect(session, player_id: str, card) -> Optional[Effe ) return None ctx = EffectContext(session, player_id, card, None) + ctx.is_trainer_effect = True await effect(ctx) return ctx @@ -2039,18 +2379,21 @@ async def _send_attack_bracket(session, ctx: AttackContext, action_id: str, titl [attacker_viewer], GameSequence.DISMISS_ABILITY_SELECT, [] ) for pid, viewer in session.players.items(): - # Draw runs flush as their own top-level Draw brackets after the - # attack (m.c's flip fan only plays for brackets named "Draw"); - # everything else rides inside the Attack bracket as before. + # Only untagged messages (CakeAttackEffect, HP mods) ride inside the + # Attack bracket; tagged runs flush as their own top-level brackets + # AFTER it, in queue order -- M.N never WrapSequences, so an inlined + # deck->Pokemon attach ran raw (energies teleported) and inlined + # Retreat/condition runs lost their executors' choreography. inline: List[Dict[str, Any]] = [] - draw_runs: List[List[Dict[str, Any]]] = [] + post_runs: List[Tuple[str, List[Dict[str, Any]]]] = [] for name, msgs in ctx.bracket_runs_for(pid, GameSequence.ATTACK.value): - if name == GameSequence.DRAW.value: - draw_runs.append(msgs) - else: + if name == GameSequence.ATTACK.value: inline.extend(msgs) + else: + post_runs.append((name, msgs)) await session.send_game_sequence( [viewer], GameSequence.ATTACK, [head] + extra + inline + [tail], ) - for msgs in draw_runs: - await session.send_game_sequence([viewer], GameSequence.DRAW, msgs) + for name, msgs in post_runs: + if msgs: + await session.send_game_sequence([viewer], name, msgs) diff --git a/spirit/game/session/game_session.py b/spirit/game/session/game_session.py index 47eb6ab..14be27f 100644 --- a/spirit/game/session/game_session.py +++ b/spirit/game/session/game_session.py @@ -46,6 +46,7 @@ from .constants import ( PROMPT_TAKE_PRIZE, PROMPT_CHOOSE_NEW_ACTIVE, PROMPT_REVEAL_BASIC_FROM_PRIZE, + PROMPT_DISCARD_BENCH, TEXT_COIN_TOSS_RESULT, TEXT_HEADS_RESULT, TEXT_TAILS_RESULT, @@ -59,6 +60,12 @@ from .constants import ( TEXT_CONFUSION_CHECK, TEXT_CONFUSION_PROCEEDS, TEXT_CONFUSION_HURT, + TEXT_ATTACK_FLIP_CHECK, + TEXT_ATTACK_FLIP_PROCEEDS, + TEXT_ATTACK_FLIP_FAILS, + TEXT_ATTACH_TAX_CHECK, + TEXT_ATTACH_TAX_OK, + TEXT_ATTACH_TAX_DISCARD, ) from spirit.network.message_names import OutboundMsg from spirit.game.attributes import ( @@ -87,9 +94,11 @@ from .effects import ( resolve_triggered_ability, ) from .passives import ( - ability_locked, active_passives, burn_recovery_blocked, - effective_bench_capacity, effective_max_hp, effective_retreat_cost, - tool_slots_free, + ability_locked, active_passives, active_to_bench_counters, + burn_recovery_blocked, effective_bench_capacity, effective_max_hp, + effective_retreat_cost, energy_attach_taxer, evolve_heal_amount, + granted_extra_attacks, retreat_energy_destination, tool_slots_free, + tool_suppressed, ) from .legal_actions import ( ACTION_ATTACH_TOOL, @@ -118,6 +127,9 @@ EOG_STAT_KEYS = ( # Recursion cap on ON_KNOCKED_OUT triggers cascading into further knockouts. _MAX_KO_TRIGGER_DEPTH = 4 +# Cap on bench-capacity enforcement passes (a discard can itself shrink capacity). +_MAX_BENCH_ENFORCE_PASSES = 4 + # Ceiling on an AIPlayer prompt wait (simulated answers land in ~1.5s); a # prompt no simulation answers must never hang the gameplay task. AI_PROMPT_GRACE_SECONDS = 15.0 @@ -240,6 +252,18 @@ class GameSession: # entity_id -> turn number Paralyzed was applied (cures after the # checkup following the OWNER's next turn, not the same turn). self.paralyzed_since: Dict[str, int] = {} + # Reentrancy guard for enforce_bench_capacity (a discard toggles passives). + self._enforcing_bench: bool = False + # Cross-turn scheduled effects (ctx.schedule_at_checkup): entries of + # {fire_at_checkup_of_turn, guard, effect}, run at checkup start. + self.scheduled_effects: List[Dict[str, Any]] = [] + # "Take another turn after this one. (Skip Pokemon Checkup.)" -- + # granted mid-turn to the active player (Star Chronos, Yoga Loop). + self.extra_turn_pending: bool = False + # entity_id -> last effective max HP broadcast for it; lets + # resync_effective_max_hp keep damage constant when a suppression + # passive (Tool Jammer) flips a bonus on/off without a stack change. + self._effective_max_seen: Dict[str, int] = {} # Per-player EOG summary counters (playmat.endgame.stat.* suffixes). self.game_stats: Dict[str, Dict[str, int]] = { pid: {} for pid in pairing["players"].keys() @@ -901,6 +925,8 @@ class GameSession: state.attack_locks.pop(key, None) state.retreat_locks.pop(entity_id, None) state.attach_restrictions.pop(entity_id, None) + state.attack_flip_checks.pop(entity_id, None) + state.ignore_target_effects_entities.discard(entity_id) for entity_map in (state.damage_taken, state.damage_taken_last_turn, state.became_active_turn): entity_map.pop(entity_id, None) @@ -925,6 +951,7 @@ class GameSession: AttrID.HP.value, pokemon.get_attribute(AttrID.HP, 0) ) pokemon.set_attribute(AttrID.HP, printed_max) + self._effective_max_seen.pop(pokemon.entity_id, None) def reset_ability_usage(self, pokemon) -> None: """A Pokemon leaving PLAY becomes a fresh card: its once-per-turn @@ -1506,27 +1533,33 @@ class GameSession: if not ctx.knockouts: return # Snapshot ON_KNOCKED_OUT triggers BEFORE any stack moves: ability_locked - # reads board position, so the carrier must still be top-level in-play. - ko_triggers: List[Tuple[PokemonEntity, str, Ability]] = [] + # reads board position, so the carrier must still be top-level in-play; + # was_active is captured here too (Radiant Jirachi's Active-spot gate). + ko_triggers: List[Tuple[PokemonEntity, str, Ability, bool]] = [] for pokemon in ctx.knockouts: owner_id = pokemon.owning_player_id if owner_id is None or ability_locked(self.board_state, pokemon): continue + was_active = self.board_state.active_pokemon(owner_id) is pokemon for entry in pokemon.get_attribute(AttrID.PIE_ABILITIES) or []: if not isinstance(entry, dict): continue ability_id = entry.get("abilityID") ability = ABILITIES_BY_ID.get(ability_id) if ability_id else None if ability is not None and ability.has_trigger(Triggers.ON_KNOCKED_OUT): - ko_triggers.append((pokemon, owner_id, ability)) + ko_triggers.append((pokemon, owner_id, ability, was_active)) # Special-energy leave-play hooks (Gift Energy's draw) snapshotted with # the KO'd carrier before any stack moves; run only for attack KOs. - is_attack_ko = ctx.is_attack_effect() + # "By DAMAGE from an attack" needs a real hit in ctx.attack_damage -- + # ctx.knock_out() direct KOs and as_counters placement never qualify. + def _damage_ko(pokemon) -> bool: + return ctx.is_attack_effect() \ + and pokemon.entity_id in ctx.attack_damage energy_ko_hooks: List[Tuple[str, Any]] = [] for pokemon in ctx.knockouts: owner_id = pokemon.owning_player_id - if owner_id is None or not is_attack_ko \ + if owner_id is None or not _damage_ko(pokemon) \ or ctx.attacker.owning_player_id == owner_id: continue for energy in self.board_state.attached_energies(pokemon): @@ -1553,7 +1586,8 @@ class GameSession: "hand", ) prize_plans.append((self._opponent_id(owner_id), max(0, count), mode)) - ko_from_attack = is_attack_ko and ctx.attacker.owning_player_id != owner_id + ko_from_attack = _damage_ko(pokemon) \ + and ctx.attacker.owning_player_id != owner_id for ally in self.board_state.pokemon_in_play(owner_id): if ally is pokemon or ally in ctx.knockouts: continue @@ -1619,20 +1653,24 @@ class GameSession: viz_msg = self._clear_entity_visualizations_msg(member) if viz_msg is not None: viz_msgs.append(viz_msg) - # Restore HP to the printed max so the discarded card carries no - # damage counters (the client keeps rendering attr 200490 there). - printed_max = pokemon.attribute_originals.get( - AttrID.HP.value, pokemon.get_attribute(AttrID.HP, 0) - ) - pokemon.set_attribute(AttrID.HP, printed_max) - if moves: - # The attr rides the Knockout bracket (N.k runs non-move - # commands generically); a move-less bracket would NRE it. - moves.append(self._build_msg( + # Restore HP to the printed max so the discarded cards carry no + # damage counters (the client keeps rendering attr 200490 there); + # tucked pre-evolutions may hold a stale pre-evolve HP the client + # still renders, so every stack Pokemon resets, not just the top. + hp_resets = [] + for member in stack: + if not isinstance(member, PokemonEntity): + continue + printed_max = member.attribute_originals.get( + AttrID.HP.value, member.get_attribute(AttrID.HP, 0) + ) + member.set_attribute(AttrID.HP, printed_max) + self._effective_max_seen.pop(member.entity_id, None) + hp_resets.append(self._build_msg( OutboundMsg.ATTRIBUTE_MODIFIED.value, { "gameID": self.game_id, - "entityID": pokemon.entity_id, + "entityID": member.entity_id, "attribute": { "name": AttrID.HP.value, "value": printed_max, @@ -1641,11 +1679,15 @@ class GameSession: }, }, )) + if moves: + # The attrs ride the Knockout bracket (N.k runs non-move + # commands generically); a move-less bracket would NRE it. + moves.extend(hp_resets) moves.extend(viz_msgs) await self.send_game_sequence( list(self.players.values()), GameSequence.KNOCKOUT, moves ) - if is_attack_ko and ctx.attacker.owning_player_id != owner_id: + if _damage_ko(pokemon) and ctx.attacker.owning_player_id != owner_id: self.turn_state.kos_by_attack.setdefault(owner_id, []).append({ "archetype_id": pokemon.archetype_id, "subtypes": list(subtypes_for(pokemon.archetype_id)), @@ -1679,14 +1721,16 @@ class GameSession: f"depth exceeded ({_ko_depth}); skipping further triggers." ) else: - is_attack_ctx = ctx.is_attack_effect() - for pokemon, owner_id, ability in ko_triggers: - ko_from_attack = is_attack_ctx and ctx.attacker.owning_player_id != owner_id + for pokemon, owner_id, ability, was_active in ko_triggers: + ko_from_attack = _damage_ko(pokemon) \ + and ctx.attacker.owning_player_id != owner_id ko_attacker = ctx.attacker if ko_from_attack else None - def _setup(c, _from_attack=ko_from_attack, _attacker=ko_attacker): + def _setup(c, _from_attack=ko_from_attack, _attacker=ko_attacker, + _was_active=was_active): c.ko_from_attack = _from_attack c.ko_attacker = _attacker + c.was_active_at_ko = _was_active trigger_ctx = await resolve_triggered_ability( self, owner_id, pokemon, ability, ctx_setup=_setup, @@ -1722,24 +1766,214 @@ class GameSession: if trigger_ctx.knockouts: await self.resolve_knockouts(trigger_ctx, _ko_depth=_ko_depth + 1) + async def enforce_bench_capacity(self): + """Bench-shrink ruling (Collapsed Stadium): every over-capacity player + picks their excess Benched Pokemon and discards the stacks -- NOT a + Knock Out (no prizes, no ON_KNOCKED_OUT). Loops until stable since a + discard can itself toggle capacity passives.""" + if self._enforcing_bench: + return + self._enforcing_bench = True + try: + for _ in range(_MAX_BENCH_ENFORCE_PASSES): + if not await self._enforce_bench_capacity_once(): + await self.sync_bench_size() + await self.resync_effective_max_hp() + return + logging.error( + f"[Session {self.game_id}] Bench capacity never stabilized " + f"after {_MAX_BENCH_ENFORCE_PASSES} passes; giving up." + ) + await self.sync_bench_size() + await self.resync_effective_max_hp() + finally: + self._enforcing_bench = False + + async def resync_effective_max_hp(self): + """Re-syncs in-play Pokemon HP against the live effective max: a + suppression passive flipping with board position (Tool Jammer entering + or leaving the Active spot) changes a bonus without any stack change, + so the attach-site delta shifts never see it. Damage taken stays + constant; a bonus turning off can make existing damage lethal.""" + lethal = [] + for player_id in self.players: + for pokemon in list(self.board_state.pokemon_in_play(player_id)): + new_max = effective_max_hp(self.board_state, pokemon) + old_max = self._effective_max_seen.get(pokemon.entity_id) + self._effective_max_seen[pokemon.entity_id] = new_max + if old_max is None or old_max == new_max: + continue + current = max( + 0, pokemon.get_attribute(AttrID.HP, 0) + (new_max - old_max) + ) + pokemon.set_attribute(AttrID.HP, current) + envelope = self._sequence_envelope( + EMPTY_SEQUENCE_ID, self._hp_attribute_msg(pokemon) + ) + await self.broadcast_packet( + OutboundMsg.SEQUENCE_MESSAGE.value, envelope + ) + if current <= 0: + lethal.append(pokemon) + for pokemon in lethal: + if pokemon.parent is not None: + await self._resolve_raw_knockout(pokemon) + + async def sync_bench_size(self): + """Plays the bench-resize choreography when a capacity passive + (Collapsed Stadium entering/leaving, Eternatus VMAX) changed a + player's bench size since the last broadcast attr 201920 value.""" + viewers = list(self.players.values()) + for player_id in self.players: + bench = self.board_state.find_player_area(player_id, "bench") + if bench is None: + continue + capacity = effective_bench_capacity(self.board_state, player_id) + if bench.get_attribute(AttrID.AREA_SLOTS) == capacity: + continue + # Compact stamps to 0..n-1 (slot order): a survivor stamped beyond + # the shrunk width would render off the mat. Same-bench EntityMoveds + # re-stamp A.m via m.I -- never attr 10920, whose override would + # outlive the resize and hijack every later stamp. + moves = [] + benched = sorted( + (c for c in bench.children if isinstance(c, PokemonEntity)), + key=self.board_state.bench_slot_of, + ) + for new_slot, entity in enumerate(benched): + if self.board_state.bench_slot_of(entity) == new_slot: + continue + self.board_state.move_card(entity.entity_id, bench.entity_id) + moves.append(self._entity_moved_msg( + entity.entity_id, bench.entity_id, new_slot + )) + bench.set_attribute(AttrID.AREA_SLOTS, capacity) + occupied = {self.board_state.bench_slot_of(c) for c in bench.children} + empty = [s for s in range(capacity) if s not in occupied] + bench.set_attribute(AttrID.AREA_EMPTY_SLOTS, empty) + if moves: + await self.send_game_sequence( + viewers, GameSequence.GROUPED_MOVE, moves + ) + # M.A keys on the 201860 AttributeModified (IL-verified) and plays + # S.o re-layout flights for every bench child; the new width comes + # from attr 201920 (U.E.get_Slots -> BenchLayout.layoutSize). + await self.send_game_sequence( + viewers, GameSequence.BENCH_SIZE_MODIFIED, + [self._area_attr_msg(bench, AttrID.AREA_SLOTS, capacity), + self._area_attr_msg(bench, AttrID.AREA_EMPTY_SLOTS, empty)], + ) + logging.info( + f"[Session {self.game_id}] Bench of " + f"{self.players[player_id].screen_name} resized to {capacity}." + ) + + def _area_attr_msg(self, area, attr: AttrID, value) -> Dict[str, Any]: + """AttributeModified for a play-area attribute (bench slots/gaps).""" + return self._build_msg( + OutboundMsg.ATTRIBUTE_MODIFIED.value, + { + "gameID": self.game_id, + "entityID": area.entity_id, + "attribute": { + "name": attr.value, + "value": value, + "originalValue": value, + "modValue": None, + }, + }, + ) + + async def _enforce_bench_capacity_once(self) -> bool: + """One enforcement pass, active player first; True iff it discarded.""" + order = self._turn_order() + active_id = self.turn_state.active_player_id + if active_id in self.players: + order = [active_id] + [p for p in order if p != active_id] + discarded = False + for player_id in order: + bench = self.board_state.find_player_area(player_id, "bench") + if bench is None: + continue + overflow = len(bench.children) - \ + effective_bench_capacity(self.board_state, player_id) + if overflow <= 0: + continue + candidates = [c for c in bench.children if isinstance(c, PokemonEntity)] + overflow = min(overflow, len(candidates)) + if overflow <= 0: + continue + picked_ids = await self.prompt_entity_picker( + player_id, candidates[0].entity_id, candidates, overflow, + prompt=PROMPT_DISCARD_BENCH, + ) + for entity_id in picked_ids[:overflow]: + picked = self.board_state.get_entity(entity_id) + if isinstance(picked, PokemonEntity) and picked.parent is bench: + await self._discard_bench_stack(player_id, picked) + discarded = True + return discarded + + async def _discard_bench_stack(self, player_id: str, pokemon): + """Discards a benched stack (Pokemon + attachments) for the bench-shrink + ruling: everything to the owner's discard in one GroupedMove bracket.""" + discard = self.board_state.find_player_area(player_id, "discard") + if not discard: + return + stack = [pokemon] + _stack_descendants(pokemon) + moves = [] + for entity in stack: + position = len(discard.children) + if self.board_state.move_card(entity.entity_id, discard.entity_id): + moves.append(self._entity_moved_msg( + entity.entity_id, discard.entity_id, position + )) + for member in stack: + if isinstance(member, PokemonEntity): + self.clear_pokemon_effects(member) + self.reset_pokemon_damage(member) + self.reset_ability_usage(member) + viz_msg = self._clear_entity_visualizations_msg(member) + if viz_msg is not None: + moves.append(viz_msg) + if not moves: + return + # The client keeps rendering attr 200490 on discarded cards -- reset + # every stack Pokemon (tucked pre-evolutions included), not just the top. + for member in stack: + if isinstance(member, PokemonEntity): + moves.append(self._hp_attribute_msg(member)) + await self.send_game_sequence( + list(self.players.values()), GameSequence.GROUPED_MOVE, moves + ) + logging.info( + f"[Session {self.game_id}] {pokemon.entity_id} " + f"({self.players[player_id].screen_name}) discarded to the bench " + f"capacity ruling." + ) + async def _take_prizes(self, player_id: str, count: int, - destination: str = "hand"): + destination: str = "hand", + minimum: Optional[int] = None) -> List[Any]: """The player picks `count` face-down prizes and takes them to hand; a non-hand `destination` (Billowing Smoke's discard, Barbaracle's - lostZone) reroutes the picked prizes there after the reveal.""" + lostZone) reroutes the picked prizes there after the reveal. + minimum Dict[str, Any]: """Marks taken prize positions in the pile's AREA_EMPTY_SLOTS so the @@ -1823,18 +2065,29 @@ class GameSession: ) async def _prompt_prize_pick(self, player_id: str, prize_ids: List[str], - count: int) -> List[str]: - """Prize-pile pick via the r.B fan-out UI; falls back to the top ones.""" + count: int, prompt: Optional[str] = None, + minimum: Optional[int] = None, + prize_area=None) -> List[str]: + """Prize-pile pick via the r.B fan-out UI; falls back to the top ones. + + minimum 0, # r.B gates the ENTIRE pile->slots fly-in on this flag; False # SetContents the fan cold (cards teleport into the pick grid). "presentPrizesAllowed": True, @@ -1845,10 +2098,10 @@ class GameSession: offer = { "gameID": self.game_id, "counter": counter, - "prompt": {"id": PROMPT_TAKE_PRIZE}, + "prompt": {"id": prompt_id}, "offerLength": 60000, "startingTimestamp": int(time.time() * 1000), - "forced": True, + "forced": needed > 0, "targetType": SelectionKind.PRIZE_CARD.value, "ignoreFirst": True, "selectionParams": {}, @@ -1873,7 +2126,7 @@ class GameSession: t for t in (response.get("entityList") or []) if t in prize_ids ) - if len(picked) >= count: + if len(picked) >= needed: return picked[:count] return prize_ids[:count] @@ -2265,6 +2518,20 @@ class GameSession: BETWEEN_TURNS triggered abilities for every in-play Pokemon.""" active_id = active_id if active_id is not None else self.turn_state.active_player_id turn_number = self.turn_state.turn_number + # Scheduled cross-turn effects (Word of Ruin timers) fire FIRST, before + # conditions; due entries are removed whether their guard passes or not. + due = [s for s in self.scheduled_effects + if s["fire_at_checkup_of_turn"] <= turn_number] + if due: + due_ids = {id(s) for s in due} + self.scheduled_effects = [ + s for s in self.scheduled_effects if id(s) not in due_ids + ] + for entry in due: + guard = entry.get("guard") + if guard is not None and not guard(self.board_state): + continue + await entry["effect"](self) poisoned = CLIENT_SPECIAL_CONDITION_NAMES[SpecialConditions.POISONED] burned = CLIENT_SPECIAL_CONDITION_NAMES[SpecialConditions.BURNED] asleep = CLIENT_SPECIAL_CONDITION_NAMES[SpecialConditions.ASLEEP] @@ -2304,6 +2571,8 @@ class GameSession: await self._fire_triggered_abilities( player_id, pokemon, Triggers.BETWEEN_TURNS ) + # Checkup KOs/triggers may have toggled bench-capacity passives. + await self.enforce_bench_capacity() async def _checkup_poison(self, active): """Poison tick: 10 * poison_counters (default 1) raw damage.""" @@ -2655,11 +2924,17 @@ class GameSession: order = self._turn_order() if not order: return + active_id = order[self.turn_state.turn_number % len(order)] for _ in range(MAX_TURNS): - active_id = order[self.turn_state.turn_number % len(order)] await self._begin_turn(active_id) await self._run_player_turn(active_id) + await self._fire_end_of_turn_triggers(active_id) + if self.extra_turn_pending: + # Star Chronos / Yoga Loop: same player again, no checkup. + self.extra_turn_pending = False + continue await self._run_pokemon_checkup(active_id) + active_id = self._opponent_id(active_id) logging.error( f"[Session {self.game_id}] MAX_TURNS ({MAX_TURNS}) reached; " f"stopping the turn loop." @@ -2707,6 +2982,12 @@ class GameSession: GameSequence.DRAW, (intros + [nested_moves]) if pid == active_id else [nested_moves], ) + # Turn-draw provenance window (Lombre "Top Entry"): the card + # visually lands in hand first -- accepted approximation of + # "before you put it into your hand". + for d in drawn: + await self._fire_triggered_abilities( + active_id, d["card"], Triggers.ON_TURN_DRAWN) async def _run_player_turn(self, active_id: str): """Offers recomputed legal actions until the player ends their turn.""" @@ -2717,6 +2998,7 @@ class GameSession: return for _ in range(MAX_ACTIONS_PER_TURN): + await self._refresh_dynamic_attacks(active_id) target_map = compute_legal_actions( self.board_state, self.turn_state, active_id, self.game_id ) @@ -2884,6 +3166,10 @@ class GameSession: if not self.board_state.move_card(card.entity_id, bench_area.entity_id): return self.turn_state.mark_entered_play(card.entity_id) + if card.get_attribute(AttrID.TRAINER_TYPE) is not None: + # A fossil bench play is an Item play rules-wise. + self._record_trainer_played(card) + self.stat_add(player_id, "trainersplayed") logging.info( f"[Session {self.game_id}] {self.players[player_id].screen_name} " f"benched {card.entity_id}." @@ -2894,19 +3180,33 @@ class GameSession: [self._entity_moved_msg(card.entity_id, bench_area.entity_id, position)], [card], ) + await self.fire_pokemon_benched_triggers(player_id, card) await self._fire_triggered_abilities(player_id, card, Triggers.ON_PLAY) async def _fire_triggered_abilities(self, player_id: str, card, trigger: str, ctx_setup=None): - """Runs a Pokemon's abilities matching `trigger` (on-play, on-evolve, - on-knocked-out, between-turns); resolve_knockouts already flushed any - knockouts the effect caused, so the extra call here is empty-safe.""" + """Runs a card's abilities matching `trigger` (on-play, on-evolve, + on-knocked-out, between-turns, turn-drawn, taken-as-prize); scans the + entity's PIE_ABILITIES plus the definition's declared abilities + (trainers carry no PIE slot -- Dream Ball's prize hook). + resolve_knockouts already flushed any knockouts the effect caused, + so the extra call here is empty-safe.""" + seen = set() + abilities = [] for entry in card.get_attribute(AttrID.PIE_ABILITIES) or []: if not isinstance(entry, dict): continue ability_id = entry.get("abilityID") ability = ABILITIES_BY_ID.get(ability_id) if ability_id else None - if ability is not None and ability.has_trigger(trigger): + if ability is not None and ability_id not in seen: + seen.add(ability_id) + abilities.append(ability) + for ability in getattr(def_for(card.archetype_id), "abilities", None) or []: + if ability.ability_id not in seen: + seen.add(ability.ability_id) + abilities.append(ability) + for ability in abilities: + if ability.has_trigger(trigger): # "1 per turn" abilities shared by name across copies (Dark Asset). if ability.shared_once_per_turn \ and ability.shared_once_per_turn in self.turn_state.used_named_abilities: @@ -2934,6 +3234,48 @@ class GameSession: for pokemon in list(self.board_state.pokemon_in_play(pid)): await self._fire_triggered_abilities( pid, pokemon, Triggers.ON_ENERGY_ATTACHED, ctx_setup=_setup) + await self._fire_stadium_triggers( + attaching_player_id, Triggers.ON_ENERGY_ATTACHED, _setup) + + async def _fire_ally_evolved_triggers(self, player_id: str, evolution_card, + pre_evolution): + """ON_ALLY_EVOLVED for the owner's OTHER in-play Pokemon after a + hand-played evolution (Eevee's Resonant Evolution); the trigger ctx + carries evolved_pokemon / evolved_from.""" + def _setup(c): + c.evolved_pokemon = evolution_card + c.evolved_from = pre_evolution + for ally in list(self.board_state.pokemon_in_play(player_id)): + if ally is evolution_card: + continue + await self._fire_triggered_abilities( + player_id, ally, Triggers.ON_ALLY_EVOLVED, ctx_setup=_setup) + + async def _fire_end_of_turn_triggers(self, active_id: str): + """END_OF_TURN for the turn player's in-play Pokemon, after their turn + ends and before the checkup (Radiant Venusaur's Sunny Bloom).""" + for pokemon in list(self.board_state.pokemon_in_play(active_id)): + await self._fire_triggered_abilities( + active_id, pokemon, Triggers.END_OF_TURN) + + async def _fire_stadium_triggers(self, acting_player_id: str, trigger: str, + ctx_setup=None): + """Declared-ability triggers on the in-play Stadium (Old Cemetery); + ctx.player_id is the ACTING player so counters on that player's own + Pokemon are never shielded as opposing effects.""" + area = self.board_state.find_global_area("activeStadium") + for stadium in list(area.children if area else []): + await self._fire_triggered_abilities( + acting_player_id, stadium, trigger, ctx_setup=ctx_setup) + + async def fire_pokemon_benched_triggers(self, benching_player_id: str, pokemon): + """ON_POKEMON_BENCHED for a manual from-hand bench play only (Gapejaw + Bog does not observe effect-driven benching from the deck).""" + def _setup(c): + c.benching_player_id = benching_player_id + c.benched_pokemon = pokemon + await self._fire_stadium_triggers( + benching_player_id, Triggers.ON_POKEMON_BENCHED, _setup) async def fire_move_to_active_triggers(self, pokemon): """ON_MOVE_TO_ACTIVE (Cinderace Libero), at most once per entity per @@ -2971,6 +3313,14 @@ class GameSession: ) return + # Slimy Room tax: the flip runs before anything else; tails discards + # the energy instead of attaching -- per the card text this does NOT + # use up the turn's energy attachment. + taxer = energy_attach_taxer(self.board_state, player_id, card, target) + if taxer is not None and \ + not await self._resolve_attach_tax_flip(player_id, card, taxer): + return + # Attach cost (e.g. Aurora's discard) resolves first; declining the # cost cancels the attach and the action re-offers. cost_ctx = await resolve_energy_attach_cost(self, player_id, card, target) @@ -3011,10 +3361,56 @@ class GameSession: # A manual attach from hand is what ON_ENERGY_ATTACHED observes. await self.fire_energy_attached_triggers(player_id, card, target) + async def _resolve_attach_tax_flip(self, player_id: str, energy, taxer) -> bool: + """Slimy Room flip on a manual energy attach: heads (True) proceeds; + tails discards the energy from hand (revealed) and returns False.""" + flip = random.choice([0, 1]) + heads = flip == 0 + self.stat_add(player_id, "headsflipped", 1 if heads else 0) + self.stat_add(player_id, "tailsflipped", 0 if heads else 1) + name = taxer.get_attribute(AttrID.NAME) + title = name.get("id", "") if isinstance(name, dict) else (name or "") + await self.send_game_sequence( + list(self.players.values()), GameSequence.POKE_ABILITY, + [self._build_msg( + OutboundMsg.MULTIPLE_COIN_FLIP_WITH_CONTEXT_EFFECT.value, + { + "gameID": self.game_id, + "resultLst": [flip], + "title": {"id": title or TEXT_ATTACH_TAX_CHECK}, + "gameText": {"id": TEXT_ATTACH_TAX_OK if heads + else TEXT_ATTACH_TAX_DISCARD}, + "source": taxer.entity_id, + "targets": [taxer.entity_id], + }, + )], + ) + await self.choreo_pause(2.5) + if heads: + return True + discard = self.board_state.find_player_area(player_id, "discard") + if discard is not None: + position = len(discard.children) + if self.board_state.move_card(energy.entity_id, discard.entity_id): + # Entering a public pile reveals the card to the opponent. + opponent = self.players.get(self._opponent_id(player_id)) + if opponent is not None: + await self.send_game_sequence( + [opponent], GameSequence.SERIAL_SEQUENCE, + [self._entity_introduced_msg(energy)], + ) + await self.send_game_sequence( + list(self.players.values()), GameSequence.GROUPED_MOVE, + [self._entity_moved_msg( + energy.entity_id, discard.entity_id, position)], + ) + return False + async def _refresh_max_hp(self, pokemon, max_before: int): """Re-broadcasts HP after a stack change shifted a max-HP bonus (e.g. Heat Fire Energy's +20): damage taken stays constant.""" max_after = effective_max_hp(self.board_state, pokemon) + self._effective_max_seen[pokemon.entity_id] = max_after delta = max_after - max_before if delta == 0: return @@ -3065,14 +3461,36 @@ class GameSession: await self.broadcast_packet(OutboundMsg.SEQUENCE_MESSAGE.value, envelope) def _pie_ability_entries(self, pokemon: PokemonEntity) -> List[Dict[str, Any]]: - """`pokemon`'s canonical PIE_ABILITIES: printed entries plus every - currently attached tool's granted_abilities (Forest Seal Stone).""" + """`pokemon`'s canonical PIE_ABILITIES: printed entries, every attached + tool's granted_abilities (Forest Seal Stone), and passive-granted + dynamic attacks (Ditto, Memory Capsule) deduped by title+text.""" definition = def_for(pokemon.archetype_id) entries = [a.to_dict() for a in (getattr(definition, "abilities", None) or [])] for child in pokemon.children: tool_def = def_for(child.archetype_id) - for granted in (getattr(tool_def, "granted_abilities", None) or []): + grants = getattr(tool_def, "granted_abilities", None) or [] + # A jammed tool has "no effect": its grants drop out of the attr + # (re-synced by _refresh_dynamic_attacks before every offer). + if grants and tool_suppressed(self.board_state, child): + continue + for granted in grants: entries.append(granted.to_dict()) + # Dedupe on the full printed identity: same-named attacks with a + # different cost/damage are genuinely distinct grants and both offer. + def _identity(e: Dict[str, Any]): + return ( + (e.get("title") or {}).get("id"), + (e.get("gameText") or {}).get("id"), + tuple(sorted((e.get("cost") or {}).items())), + e.get("damage"), e.get("amountOperator"), + ) + seen = {_identity(e) for e in entries} + for attack in granted_extra_attacks(self.board_state, pokemon): + row = attack.to_dict() + key = _identity(row) + if key not in seen: + seen.add(key) + entries.append(row) return entries async def refresh_granted_abilities(self, pokemon: PokemonEntity): @@ -3082,6 +3500,18 @@ class GameSession: pokemon, AttrID.PIE_ABILITIES, self._pie_ability_entries(pokemon) ) + async def _refresh_dynamic_attacks(self, player_id: str): + """Syncs the Active's PIE_ABILITIES before each offer so dynamic + attack grants (discard/stack contents changed) stay panel-resolvable.""" + active = self.board_state.active_pokemon(player_id) + if active is None: + return + entries = self._pie_ability_entries(active) + if entries != (active.get_attribute(AttrID.PIE_ABILITIES) or []): + await self._broadcast_entity_attribute( + active, AttrID.PIE_ABILITIES, entries + ) + async def add_turn_stat_visualization( self, pokemon: PokemonEntity, arrow: str, display_type: str, source_name: str, card_text: Optional[str] = None, @@ -3375,12 +3805,15 @@ class GameSession: player_id, GameSequence.EVOLVE, data_effects + moves, [card] ) - # Push the transferred damage so the evolution renders its HP tracker. + # Push the transferred damage so the evolution renders its HP tracker, + # and the tucked pre-evolution's reset so its stale damage never + # re-renders (e.g. in the discard pile after a later knockout). if damage_taken: - envelope = self._sequence_envelope( - EMPTY_SEQUENCE_ID, self._hp_attribute_msg(card) - ) - await self.broadcast_packet(OutboundMsg.SEQUENCE_MESSAGE.value, envelope) + for entity in (card, target): + envelope = self._sequence_envelope( + EMPTY_SEQUENCE_ID, self._hp_attribute_msg(entity) + ) + await self.broadcast_packet(OutboundMsg.SEQUENCE_MESSAGE.value, envelope) # Freshly entered play: cannot evolve again this turn. self.turn_state.mark_entered_play(card.entity_id) @@ -3397,9 +3830,243 @@ class GameSession: self._condition_attr_msg(target)], ) + # Wyndon Stadium: heal a Pokemon just evolved from hand (deck-sourced + # evolutions ride from_zone_intro and are not "played from hand"). + if not from_zone_intro: + heal = evolve_heal_amount(self.board_state, card, target, player_id) + if heal > 0: + heal_ctx = EffectContext(self, player_id, card, None) + if await heal_ctx.heal(heal, target=card): + await self._flush_effect_runs(heal_ctx) + await self._fire_triggered_abilities(player_id, card, Triggers.ON_EVOLVE) + # Hand-played evolutions also fire the owner's OTHER Pokemon (Eevee's + # Resonant Evolution); deck-sourced ones aren't "played from hand". + if not from_zone_intro: + await self._fire_ally_evolved_triggers(player_id, card, target) + # Tucking the pre-evolution can switch off a capacity-raising ability. + await self.enforce_bench_capacity() return True + async def perform_devolution(self, pokemon, destination_name: str = "hand", + clamp_nonlethal: bool = False): + """One devolution step: the top evolution card leaves the stack and + the previous stage takes its place, carrying the damage. + + Devolve bracket (IL-verified, executor d.o): data effects From= / Into=, ALL moves pre-handled (the + executor plays the spin + DevolutionLeft/Right flights itself and + auto-flies Into toward the KO pile when its HP tracker hits 0 -- so a + non-final step clamps the sent HP above lethal via clamp_nonlethal). + Returns (promoted_stage, removed_card) or None. + """ + area = pokemon.parent + owner_id = pokemon.owning_player_id + if area is None or owner_id is None: + return None + evolves_from = pokemon.get_attribute(AttrID.EVOLUTION_LOGIC_FROM) + prev = next( + (c for c in pokemon.children + if isinstance(c, PokemonEntity) + and c.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) == evolves_from), + None, + ) + dest = self.board_state.find_player_area(owner_id, destination_name) + if prev is None or dest is None: + return None + damage_taken = max( + 0, effective_max_hp(self.board_state, pokemon) + - pokemon.get_attribute(AttrID.HP, 0) + ) + slot = self.board_state.bench_slot_of(pokemon) + + moves = [] + # The previous stage takes the board spot... + self.board_state.move_card(prev.entity_id, area.entity_id) + moves.append(self._entity_moved_msg(prev.entity_id, area.entity_id, slot)) + # ...inherits the attachments and any older stages... + for other in [c for c in list(pokemon.children)]: + position = len(prev.children) + self.board_state.attach_card(other.entity_id, prev.entity_id) + moves.append(self._entity_moved_msg(other.entity_id, prev.entity_id, position)) + # ...and the removed evolution card goes to the hand/deck. + position = len(dest.children) + self.board_state.move_card(pokemon.entity_id, dest.entity_id) + moves.append(self._entity_moved_msg(pokemon.entity_id, dest.entity_id, position)) + + # Devolving removes Special Conditions (not damage); the removed card + # leaves play as a fresh card. + self.clear_pokemon_effects(pokemon) + self.reset_pokemon_damage(pokemon) + self.reset_ability_usage(pokemon) + prev_max = effective_max_hp(self.board_state, prev) + prev_hp = max(0, prev_max - damage_taken) + prev.set_attribute(AttrID.HP, prev_hp) + # Clamp only the SENT value on non-final steps: the executor flies + # Into toward the KO pile the moment its HP tracker reads 0. + sent_hp = max(10, prev_hp) if clamp_nonlethal else prev_hp + + data_effects = [ + self._entity_id_data_effect_msg("From", pokemon.entity_id), + self._entity_id_data_effect_msg("Into", prev.entity_id), + ] + attrs = [ + self._build_msg( + OutboundMsg.ATTRIBUTE_MODIFIED.value, + { + "gameID": self.game_id, + "entityID": prev.entity_id, + "attribute": { + "name": AttrID.HP.value, + "value": sent_hp, + "originalValue": prev_max, + "modValue": None, + }, + }, + ), + self._hp_attribute_msg(pokemon), + ] + await self.send_game_sequence( + list(self.players.values()), GameSequence.DEVOLVE, + data_effects + moves + attrs, + ) + await self.refresh_granted_abilities(prev) + logging.info( + f"[Session {self.game_id}] {pokemon.entity_id} devolved into " + f"{prev.entity_id} ({destination_name})." + ) + return prev, pokemon + + def transfer_pokemon_state(self, old, new) -> List[str]: + """Rekeys every per-entity effect (Special Conditions, locks, turn + stamps, temporary passives) from `old` onto `new` for identity swaps + ("any other effects remain on the new Pokemon"). Once-per-turn ability + usage stays behind: the swapped-in card is a different Pokemon. + Returns the transferred conditions array.""" + conditions = list(old.get_attribute(AttrID.SPECIAL_CONDITIONS) or []) + new.set_attribute(AttrID.SPECIAL_CONDITIONS, conditions) + old.set_attribute(AttrID.SPECIAL_CONDITIONS, []) + old_id, new_id = old.entity_id, new.entity_id + ts = self.turn_state + # The incoming entity may carry stale keys from an earlier stint in + # play: its old entries are dropped even when `old` has none to move. + for entity_map in (self.sleep_checkup_coins, self.poison_counters, + self.paralyzed_since, ts.entered_play_turn, + ts.became_active_turn, ts.retreat_locks, + ts.attach_restrictions, ts.attack_flip_checks, + ts.damage_taken, ts.damage_taken_last_turn): + entity_map.pop(new_id, None) + if old_id in entity_map: + entity_map[new_id] = entity_map.pop(old_id) + for entity_set in (ts.retreated_entities, ts.healed_entities, + ts.ignore_target_effects_entities, + ts.on_move_to_active_fired): + entity_set.discard(new_id) + if old_id in entity_set: + entity_set.discard(old_id) + entity_set.add(new_id) + for key in [k for k in ts.attack_locks if k[0] == old_id]: + ts.attack_locks[(new_id, key[1])] = ts.attack_locks.pop(key) + for temp in self.board_state.temporary_passives: + if temp.carrier_entity_id == old_id: + temp.carrier_entity_id = new_id + return conditions + + async def perform_identity_swap(self, outgoing, incoming, + destination_name: str = "discard", + transfer: bool = True): + """Switches an in-play Pokemon with a card from the owner's hand or + discard: `incoming` takes the exact board spot (Active or the same + bench slot) and `outgoing` goes to `destination_name`. + + transfer=True carries attachments, damage, Special Conditions and + turn stamps onto the new Pokemon (Stance Change / V Transformation / + Thorton); transfer=False sends the attachments to the destination + pile with the outgoing card and the new Pokemon enters fresh + (Phantom Transformation). Returns `incoming`, or None on failure. + """ + area = outgoing.parent + owner_id = outgoing.owning_player_id + if area is None or owner_id is None or incoming is None \ + or incoming is outgoing: + return None + source_area = incoming._containing_area_name() + dest = self.board_state.find_player_area(owner_id, destination_name) + if dest is None or source_area is None: + return None + slot = self.board_state.bench_slot_of(outgoing) + damage_taken = max( + 0, effective_max_hp(self.board_state, outgoing) + - outgoing.get_attribute(AttrID.HP, 0) + ) if transfer else 0 + from_hidden = incoming.is_hidden_from(self._opponent_id(owner_id)) + + # The new Pokemon takes the vacated spot... + self.board_state.move_card(incoming.entity_id, area.entity_id, slot) + incoming_move = self._entity_moved_msg(incoming.entity_id, area.entity_id, slot) + moves = [] + # ...the attachments follow it (or go to the pile with the old card)... + for other in list(outgoing.children): + if transfer: + position = len(incoming.children) + self.board_state.attach_card(other.entity_id, incoming.entity_id) + moves.append(self._entity_moved_msg( + other.entity_id, incoming.entity_id, position)) + else: + position = len(dest.children) + self.board_state.move_card(other.entity_id, dest.entity_id) + moves.append(self._entity_moved_msg( + other.entity_id, dest.entity_id, position)) + # ...and the old card leaves play. + position = len(dest.children) + self.board_state.move_card(outgoing.entity_id, dest.entity_id) + moves.append(self._entity_moved_msg(outgoing.entity_id, dest.entity_id, position)) + + conditions: List[str] = [] + if transfer: + conditions = self.transfer_pokemon_state(outgoing, incoming) + new_max = effective_max_hp(self.board_state, incoming) + incoming.set_attribute(AttrID.HP, max(0, new_max - damage_taken)) + else: + self.clear_pokemon_effects(outgoing) + self.turn_state.mark_entered_play(incoming.entity_id) + self.reset_pokemon_damage(outgoing) + self.reset_ability_usage(outgoing) + + attrs = [self._hp_attribute_msg(incoming), self._hp_attribute_msg(outgoing)] + players = list(self.players.values()) + # Wrap-FX rule: the moving card's intro rides a preceding SerialSequence. + await self.send_game_sequence( + players, GameSequence.SERIAL_SEQUENCE, + [self._entity_introduced_msg(incoming)], + ) + if from_hidden: + # k.z's reveal delegation presents the card large before it lands; + # the pair must ride alone or other moves steal the presentation. + await self.send_game_sequence( + players, GameSequence.GROUPED_MOVE, + [self._reveal_card_msg(incoming.entity_id, True), incoming_move], + ) + await self.send_game_sequence( + players, GameSequence.GROUPED_MOVE, moves + attrs) + else: + await self.send_game_sequence( + players, GameSequence.GROUPED_MOVE, + [incoming_move] + moves + attrs) + if conditions: + # Executor ctor (M.u) requires the "Target" data effect first. + await self.send_game_sequence( + players, GameSequence.ADD_SPECIAL_CONDITION, + [self._entity_id_data_effect_msg("Target", incoming.entity_id), + self._condition_attr_msg(incoming)], + ) + await self.refresh_granted_abilities(incoming) + logging.info( + f"[Session {self.game_id}] {outgoing.entity_id} identity-swapped " + f"with {incoming.entity_id} (out to {destination_name})." + ) + return incoming + async def _execute_play_trainer(self, player_id, card) -> bool: """Plays an Item/Supporter: revealed onto activeTrainer, effect resolves, then discarded. Returns True when the effect ended the turn (Rotom Bike).""" @@ -3463,6 +4130,8 @@ class GameSession: # the choreography flushes. for hook in ctx.deferred_actions: await hook() + # The effect may have removed a Stadium / toggled capacity passives. + await self.enforce_bench_capacity() return ctx is not None and ctx.ends_turn def _record_trainer_played(self, card): @@ -3502,6 +4171,8 @@ class GameSession: f"played stadium {card.entity_id} (effect pending effects API)." ) await self._send_play_sequence(player_id, GameSequence.STADIUM_PRESENT, moves, [card]) + # A capacity-reducing Stadium (Collapsed Stadium) shrinks benches now. + await self.enforce_bench_capacity() async def _execute_retreat(self, player_id, card, entry, target_ids): """Retreats the Active: pays the cost to discard, swaps with a benched Pokemon.""" @@ -3548,9 +4219,18 @@ class GameSession: self._entity_id_data_effect_msg("NewActive", new_active.entity_id), ] for eid in discard_ids: - position = len(discard_area.children) - if self.board_state.move_card(eid, discard_area.entity_id): - messages.append(self._entity_moved_msg(eid, discard_area.entity_id, position)) + # A passive may redirect a retreat-cost energy (Skaters' Park + # returns basic Energy to "hand") instead of the discard pile. + energy = self.board_state.get_entity(eid) + dest_area = discard_area + dest_name = retreat_energy_destination(self.board_state, card, energy) \ + if energy is not None else None + if dest_name: + dest_area = self.board_state.find_player_area( + player_id, dest_name) or discard_area + position = len(dest_area.children) + if self.board_state.move_card(eid, dest_area.entity_id): + messages.append(self._entity_moved_msg(eid, dest_area.entity_id, position)) # The old active takes the new active's rendered SLOT (client stamp), # captured before the active move overwrites new_active's stamp with 0. slot = self.board_state.bench_slot_of(new_active) @@ -3582,7 +4262,22 @@ class GameSession: self.turn_state.retreated_entities.add(card.entity_id) self.turn_state.became_active_turn[new_active.entity_id] = \ self.turn_state.turn_number + await self._apply_active_to_bench_counters(player_id, card) await self.fire_move_to_active_triggers(new_active) + # An Active-spot change can flip suppression passives (Tool Jammer). + await self.resync_effective_max_hp() + + async def _apply_active_to_bench_counters(self, player_id, pokemon): + """Spikemuth: counters on an Active that moved to its owner's Bench + during their turn (the retreat path; effects ride switch_active).""" + counters = active_to_bench_counters(self.board_state, pokemon) + if counters <= 0 or pokemon.parent is None: + return + ctx = EffectContext(self, player_id, pokemon, None) + await ctx.deal_damage(counters * 10, target=pokemon, as_counters=True) + await self._flush_effect_runs(ctx) + if ctx.knockouts: + await self.resolve_knockouts(ctx) async def _execute_attack(self, player_id, card, entry) -> bool: """Resolves an attack through the effect engine; attacking ends the turn.""" @@ -3604,13 +4299,20 @@ class GameSession: if not await self._resolve_confusion_flip(player_id, card): return True # tails: the attack fizzles, but the turn still ends + # Smokescreen-family check, also before any bookkeeping: a cancelled + # attack never happened for VSTAR usage or attack locks either. + flip_title = self.turn_state.attack_flip_check(card.entity_id) + if flip_title is not None: + if not await self._resolve_attack_attempt_flip(player_id, card, flip_title): + return True # tails: the attack does not happen; the turn ends + if ability is not None: if getattr(ability, "locks_next_turn", False): self.turn_state.lock_attack(card.entity_id, action_id) if ability.vstar: self.turn_state.vstar_used.add(player_id) - await resolve_attack(self, player_id, card, ability, action_id) + ctx = await resolve_attack(self, player_id, card, ability, action_id) # Effects like Aqua Return can remove the attacker itself from play. for pid in list(self.players.keys()): if self.board_state.active_pokemon(pid) is None \ @@ -3619,8 +4321,51 @@ class GameSession: self._opponent_id(pid), f"{self.players[pid].screen_name} has no Pokémon left", ) + # An attack normally ends the turn; the effect (Additional Order-style + # ctx flag) or a passive (Fluffy Barrage, evaluated after promotions + # so "after your opponent chooses a new Active" holds) can keep it. + keeps = ctx is not None and ( + ctx.attack_keeps_turn or any( + passive.attack_keeps_turn(card, ability, ctx, carrier) + for passive, carrier in active_passives(self.board_state) + ) + ) + if keeps: + logging.info( + f"[Session {self.game_id}] Attack kept the turn going for " + f"{self.players[player_id].screen_name}." + ) + return False return True + async def _resolve_attack_attempt_flip(self, player_id: str, attacker, + title: str) -> bool: + """Smokescreen-family flip before attacking: tails and the attack does + not happen (no damage, no lock/VSTAR bookkeeping; the turn still + ends). PokeAbility bracket: M.s runs the L.x flip with no condition + pop-in (FlipToWakeUp would stamp the Asleep pop-in).""" + flip = random.choice([0, 1]) + heads = flip == 0 + self.stat_add(player_id, "headsflipped", 1 if heads else 0) + self.stat_add(player_id, "tailsflipped", 0 if heads else 1) + await self.send_game_sequence( + list(self.players.values()), GameSequence.POKE_ABILITY, + [self._build_msg( + OutboundMsg.MULTIPLE_COIN_FLIP_WITH_CONTEXT_EFFECT.value, + { + "gameID": self.game_id, + "resultLst": [flip], + "title": {"id": title or TEXT_ATTACK_FLIP_CHECK}, + "gameText": {"id": TEXT_ATTACK_FLIP_PROCEEDS if heads + else TEXT_ATTACK_FLIP_FAILS}, + "source": attacker.entity_id, + "targets": [attacker.entity_id], + }, + )], + ) + await self.choreo_pause(2.5) + return heads + async def _resolve_confusion_flip(self, player_id: str, attacker) -> bool: """Confused Pokemon flip before attacking: heads proceeds into the normal attack; tails hurts the attacker for 30 raw damage instead.""" diff --git a/spirit/game/session/legal_actions.py b/spirit/game/session/legal_actions.py index a9c94b8..f96181f 100644 --- a/spirit/game/session/legal_actions.py +++ b/spirit/game/session/legal_actions.py @@ -32,6 +32,7 @@ from .constants import ( ) from .passives import ( ability_locked, + can_attack_despite_conditions, can_evolve_early, effective_attack_cost, effective_bench_capacity, @@ -142,10 +143,26 @@ class TurnState: attach_restrictions: Dict[str, int] = field(default_factory=dict) # Entities whose ON_MOVE_TO_ACTIVE trigger already fired this turn. on_move_to_active_fired: Set[str] = field(default_factory=set) + # Whether the turn player used their once-per-turn attack-coin re-flip + # (Glimwood Tangle); only actually re-flipping consumes it. + attack_coin_reroll_used: bool = False + # entity_id -> (through_turn, flip_title): a Smokescreen-family check -- + # this entity must flip a coin to attack, tails cancels the attack. + attack_flip_checks: Dict[str, Tuple[int, str]] = field(default_factory=dict) + # Entities whose attacks ignore effects on the opponent's Active THIS turn + # (Phoebe); cleared every begin_turn. + ignore_target_effects_entities: Set[str] = field(default_factory=set) + # player_id -> attack titles that player declared on THEIR previous turn + # ("If 1 of your Pokemon used Yoga Loop during your last turn..."). + attack_titles_prev_turn_by_player: Dict[str, List[str]] = field(default_factory=dict) def begin_turn(self, player_id: str, board: Optional[Any] = None): """Advances to the next turn, resets the once-per-turn flags, rotates the two-turn history, and prunes expired turn-scoped effects.""" + if self.active_player_id: + self.attack_titles_prev_turn_by_player[self.active_player_id] = [ + title for _, _, title in self.attacks_used + ] self.turn_number += 1 self.active_player_id = player_id self.supporter_played = False @@ -175,6 +192,7 @@ class TurnState: self.turn_draw_entity_ids_last_turn = self.turn_draw_entity_ids self.turn_draw_entity_ids = set() self.on_move_to_active_fired = set() + self.attack_coin_reroll_used = False self.play_locks = { pid: kept for pid, locks in self.play_locks.items() if (kept := [(p, exp) for p, exp in locks @@ -184,6 +202,11 @@ class TurnState: eid: exp for eid, exp in self.attach_restrictions.items() if exp >= self.turn_number } + self.attack_flip_checks = { + eid: entry for eid, entry in self.attack_flip_checks.items() + if entry[0] >= self.turn_number + } + self.ignore_target_effects_entities = set() if board is not None: board.temporary_passives = [ tp for tp in (getattr(board, "temporary_passives", None) or []) @@ -233,6 +256,22 @@ class TurnState: def attach_restricted(self, entity_id: str) -> bool: return self.turn_number <= self.attach_restrictions.get(entity_id, -1) + def set_attack_flip_check(self, entity_id: str, through_turn: Optional[int] = None, + title: str = ""): + """Requires a coin flip before `entity_id` attacks (tails = the attack + doesn't happen); default lifetime: through the opponent's next turn.""" + self.attack_flip_checks[entity_id] = ( + self.turn_number + 1 if through_turn is None else through_turn, + title, + ) + + def attack_flip_check(self, entity_id: str) -> Optional[str]: + """The flip title when `entity_id` must flip to attack this turn, else None.""" + entry = self.attack_flip_checks.get(entity_id) + if entry is not None and self.turn_number <= entry[0]: + return entry[1] + return None + def may_evolve_target(self, entity_id: str) -> bool: """A Pokemon may evolve only if it has been in play since a previous turn, and never during either player's first turn (turns 1 and 2).""" @@ -379,6 +418,11 @@ def compute_legal_actions( if stage == PokemonStage.BASIC.value: if getattr(def_for(card.archetype_id), "unplayable_from_hand", False): continue # Shedinja: enters play only via an effect + # Fossils stay Item cards in hand: Item locks gate the bench play. + if card.get_attribute(AttrID.TRAINER_TYPE) is not None \ + and (state.play_locked(player_id, card) + or trainer_play_blocked(board, player_id, card)): + continue if bench_has_space: # The bench area is the drop target; without it the drag # dead-ends at the ActionsNode and nothing highlights. @@ -435,7 +479,8 @@ def compute_legal_actions( continue definition = def_for(card.archetype_id) condition = getattr(definition, "condition", None) - if condition is not None and not condition(board, player_id): + if condition is not None \ + and not trainer_condition_met(condition, board, player_id, card): continue if trainer_type == TrainerType.ITEM.value: entries.append(_target_map_entry( @@ -471,10 +516,18 @@ def compute_legal_actions( entries.extend(_ability_entries(board, state, player_id, game_id, in_play)) entries.extend(_out_of_zone_ability_entries(board, state, player_id, game_id)) entries.extend(_stadium_ability_entries(board, state, player_id, game_id)) - if not _active_immobilized(board, player_id): - # The turn-1 attack gate lives in _attack_entries (usable_first_turn). - entries.extend(_attack_entries(board, state, player_id, game_id)) + # Asleep/Paralyzed gates attacks per-attack (Attack(usable_despite_conditions)). + immobilized = _active_immobilized(board, player_id) + entries.extend(_attack_entries(board, state, player_id, game_id, immobilized)) + if not immobilized: entries.extend(_retreat_entry(board, state, player_id, game_id)) + # A turn kept alive past an attack (Fluffy Barrage's second strike) stays + # in the attack phase: only attacking (or End Turn) remains legal. + if state.attacks_used: + entries = [ + e for e in entries + if e["selectableAction"]["description"] == ACTION_USE_ATTACK + ] return entries @@ -642,6 +695,15 @@ def _retreat_entry( )] +def trainer_condition_met(condition, board: BoardState, player_id: str, card) -> bool: + """condition(board, player_id[, card]) -- 3-arg variants get the specific + hand copy (Nugget's turn-draw provenance is per-card).""" + code = getattr(condition, "__code__", None) + if code is not None and code.co_argcount >= 3: + return bool(condition(board, player_id, card)) + return bool(condition(board, player_id)) + + def _same_stadium_in_play(board: BoardState, card: TrainerEntity) -> bool: """A Stadium is unplayable if one with the same archetype is in play.""" stadium_area = board.find_global_area("activeStadium") @@ -652,12 +714,16 @@ def _same_stadium_in_play(board: BoardState, card: TrainerEntity) -> bool: def _attack_entries( - board: BoardState, state: TurnState, player_id: str, game_id: str + board: BoardState, state: TurnState, player_id: str, game_id: str, + immobilized: bool = False, ) -> List[Dict[str, Any]]: """Usable attacks of the Active Pokemon (energy requirement met).""" active = board.active_pokemon(player_id) if not active: return [] + # A passive (Windup Arm) can exempt the whole Pokemon from the gate. + if immobilized and can_attack_despite_conditions(board, active): + immobilized = False abilities = active.get_attribute(AttrID.PIE_ABILITIES) or [] if not isinstance(abilities, list): logging.warning( @@ -677,6 +743,9 @@ def _attack_entries( if state.attack_locked(active.entity_id, ability_id): continue definition = ABILITIES_BY_ID.get(ability_id) + # Asleep/Paralyzed suppression, per-attack exemptable (Windup Arm). + if immobilized and not getattr(definition, "usable_despite_conditions", False): + continue # The player going first cannot attack on turn 1 unless the attack # explicitly allows it (Indeedee's Watch Over). if state.turn_number == 1 \ diff --git a/spirit/game/session/passives.py b/spirit/game/session/passives.py index 64d8a63..86662aa 100644 --- a/spirit/game/session/passives.py +++ b/spirit/game/session/passives.py @@ -122,6 +122,18 @@ class Passive: # Same-key passives count once in effective_max_hp (Abomasnow stacks). stacking_key: Optional[str] = None + # Optional awaitable damage stage: async (ctx, calc, target, carrier) -> + # Optional[int], consulted in ctx.deal_damage AFTER compute_damage and + # BEFORE the HP write (None = unchanged, else the new dealt amount). + # Flip-to-prevent (Infiltrator) and KO-survive (Guts) live here; may queue + # coin flips via ctx so they choreograph inside the attack bracket. + damage_interceptor: Optional[Any] = None + + # Optional awaitable post-attack stage: async (ctx, carrier) -> None, run + # by resolve_attack AFTER knockouts/deferred actions resolve (end-of-turn + # riders like Blunder Policy); queued messages flush as their own brackets. + attack_followup: Optional[Any] = None + def modify_damage_dealt(self, calc: DamageCalc, carrier: BoardEntity): """Attacker-side "does more/less damage" step (runs before W/R).""" @@ -154,9 +166,11 @@ class Passive: return 0 def modify_retreat_cost( - self, cost: int, pokemon: PokemonEntity, carrier: BoardEntity + self, cost: int, pokemon: PokemonEntity, carrier: BoardEntity, + board: BoardState, ) -> int: - """Returns the (possibly reduced) retreat cost (Air Balloon's -2).""" + """Returns the (possibly reduced) retreat cost (Air Balloon's -2); + `board` lets stadium-gated hooks read global state (Thwackey).""" return cost def blocks_attack_effects(self, target: PokemonEntity, carrier: BoardEntity) -> bool: @@ -171,6 +185,10 @@ class Passive: """True to forbid `pokemon` from retreating (Octolock, Flygon).""" return False + def attacks_despite_conditions(self, pokemon: PokemonEntity, carrier: BoardEntity) -> bool: + """True to let `pokemon` attack even while Asleep/Paralyzed (Windup Arm).""" + return False + def blocks_special_conditions( self, target: PokemonEntity, condition: Any, carrier: BoardEntity ) -> bool: @@ -206,11 +224,36 @@ class Passive: """Rewrites an energy's provided-type options (Charizard PGO doubling).""" return options + def modify_pokemon_types( + self, types: List[Any], pokemon: BoardEntity, carrier: BoardEntity + ) -> List[Any]: + """Rewrites `pokemon`'s live type list (Kecleon's Chromashift).""" + return types + + def blocks_energy_removal( + self, energy: BoardEntity, mover_player_id: str, carrier: BoardEntity + ) -> bool: + """True to keep an attached Energy from being moved to hand/deck/ + discard by `mover_player_id`'s Item/Supporter effect (Brazen Tail).""" + return False + def suppresses_special_energy(self, energy: BoardEntity, carrier: BoardEntity) -> bool: """True to neutralize a Special Energy (Temple of Sinnoh): it loses its passive and provides only Colorless.""" return False + def suppresses_tool(self, tool: BoardEntity, carrier: BoardEntity) -> bool: + """True to neutralize an attached Pokemon Tool's passive hooks + (Tool Jammer); evaluated on the unfiltered set like ability locks.""" + return False + + def granted_attacks( + self, board: BoardState, pokemon: PokemonEntity, carrier: BoardEntity + ) -> List[Any]: + """Extra Attack definitions `pokemon` may use (Ditto's Sudden + Transformation, Memory Capsule); costs/locks still apply normally.""" + return [] + def blocks_trainer_play( self, card: BoardEntity, player_id: str, carrier: BoardEntity ) -> bool: @@ -247,6 +290,13 @@ class Passive: no opinion, the smallest override wins.""" return None + def attack_keeps_turn(self, attacker: PokemonEntity, ability: Any, + ctx: Any, carrier: BoardEntity) -> bool: + """True to keep the turn going after `attacker`'s attack resolved + (Fluffy Barrage); runs after knockouts resolve, so KO evidence rides + ctx.knockouts_resolved, not ctx.knockouts.""" + return False + def blocks_ability_effects(self, target: PokemonEntity, carrier: BoardEntity) -> bool: """True to shield `target` from opponents' Ability effects (Corviknight VMAX).""" return False @@ -255,6 +305,60 @@ class Passive: """True to keep `card` from being discarded by an opponent's effect.""" return False + def blocks_trainer_effects( + self, affected_player_id: str, trainer_card: BoardEntity, + trainer_type: Any, carrier: BoardEntity, + affected_entity: Optional[BoardEntity] = None, + board: Optional["BoardState"] = None, + ) -> bool: + """True to shield `affected_player_id`'s side from an opposing trainer + card's effects (Dew Guard); trainer_type is the TrainerType value. + affected_entity is the primitive's direct object (None for + player-level effects like draws/hand shuffles).""" + return False + + def replace_supporter_effect( + self, card: BoardEntity, player_id: str, carrier: BoardEntity + ) -> Optional[Any]: + """Replacement effect coroutine for `player_id`'s Supporter `card` + (Shifty Substitution); None = no replacement.""" + return None + + def taxes_energy_attach( + self, attaching_player_id: str, energy: BoardEntity, + target: PokemonEntity, carrier: BoardEntity, + ) -> bool: + """True to coin-flip `attaching_player_id`'s manual energy attach + (Slimy Room): tails discards the energy instead of attaching.""" + return False + + def retreat_cost_destination( + self, pokemon: PokemonEntity, energy: BoardEntity, carrier: BoardEntity + ) -> Optional[str]: + """Player-area name replacing "discard" for `energy` paid for + `pokemon`'s retreat (Skaters' Park sends basic Energy to "hand").""" + return None + + def counters_on_active_to_bench( + self, pokemon: PokemonEntity, carrier: BoardEntity + ) -> int: + """Damage counters put on an Active that moved to its owner's Bench + during that owner's turn (Spikemuth).""" + return 0 + + def heal_on_evolve( + self, evolved: PokemonEntity, pre_evolution: BoardEntity, + player_id: str, carrier: BoardEntity, + ) -> int: + """Damage healed from a Pokemon `player_id` just evolved from hand + (Wyndon Stadium).""" + return 0 + + def offers_attack_coin_reroll(self, player_id: str, carrier: BoardEntity) -> bool: + """True to let `player_id` re-flip an attack's coins once during + their turn (Glimwood Tangle).""" + return False + def carrier_pokemon(carrier: BoardEntity) -> Optional[PokemonEntity]: """The in-play Pokemon a passive rides: the carrier itself, or the @@ -340,6 +444,16 @@ def _suppressed_special_energy( return any(p.suppresses_special_energy(entity, c) for p, c, _ in triples) +def _suppressed_tool( + triples: List[Tuple[Passive, BoardEntity, bool]], entity: BoardEntity +) -> bool: + """Whether `entity` is an attached Pokemon Tool neutralized by a + suppression passive (Tool Jammer), evaluated on the UNFILTERED set.""" + if entity.get_attribute(AttrID.TRAINER_TYPE) != TrainerType.POKEMON_TOOL.value: + return False + return any(p.suppresses_tool(entity, c) for p, c, _ in triples) + + def active_passives(board: BoardState) -> List[Tuple[Passive, BoardEntity]]: """All (passive, carrier) pairs currently switched on by board position. @@ -356,7 +470,26 @@ def active_passives(board: BoardState) -> List[Tuple[Passive, BoardEntity]]: return [(p, c) for p, c, is_ability in triples if not (is_ability and blocked(c)) - and not _suppressed_special_energy(triples, c)] + and not _suppressed_special_energy(triples, c) + and not _suppressed_tool(triples, c)] + + +def tool_suppressed(board: BoardState, tool: BoardEntity) -> bool: + """Whether an attached Tool is neutralized (Tool Jammer): gates its + granted_abilities in PIE_ABILITIES, not just its passive hooks.""" + return _suppressed_tool(_collect_passives(board), tool) + + +def granted_extra_attacks(board: BoardState, pokemon: PokemonEntity) -> List[Any]: + """All passive-granted extra attacks for `pokemon`, deduped by ability_id.""" + out: List[Any] = [] + seen = set() + for passive, carrier in active_passives(board): + for attack in passive.granted_attacks(board, pokemon, carrier) or []: + if attack.ability_id and attack.ability_id not in seen: + seen.add(attack.ability_id) + out.append(attack) + return out def _descendants(entity: BoardEntity) -> List[BoardEntity]: @@ -421,7 +554,9 @@ def compute_damage( for passive, carrier in passives: passive.modify_weakness(calc, carrier) passive.modify_resistance(calc, carrier) - attacker_types = attacker.get_attribute(AttrID.POKEMON_TYPES) or [] + attacker_types = list(attacker.get_attribute(AttrID.POKEMON_TYPES) or []) + for passive, carrier in passives: + attacker_types = passive.modify_pokemon_types(attacker_types, attacker, carrier) if calc.weakness_applies and any(t in calc.weak_types for t in attacker_types): calc.weakness_hit = True calc.amount *= calc.weakness_multiplier @@ -459,7 +594,7 @@ def effective_retreat_cost(board: BoardState, pokemon: PokemonEntity) -> int: """A Pokemon's retreat cost after cost-modifying passives (Air Balloon's -2).""" cost = int(pokemon.get_attribute(AttrID.RETREAT_COST) or 0) for passive, carrier in active_passives(board): - cost = passive.modify_retreat_cost(cost, pokemon, carrier) + cost = passive.modify_retreat_cost(cost, pokemon, carrier, board) return max(0, cost) @@ -498,6 +633,14 @@ def retreat_blocked(board: BoardState, pokemon: PokemonEntity) -> bool: ) +def can_attack_despite_conditions(board: BoardState, pokemon: PokemonEntity) -> bool: + """Whether a passive lets `pokemon` attack while Asleep/Paralyzed.""" + return any( + passive.attacks_despite_conditions(pokemon, carrier) + for passive, carrier in active_passives(board) + ) + + def conditions_blocked(board: BoardState, target: PokemonEntity, condition: Any) -> bool: """Whether a passive shields `target` from the given Special Condition.""" return any( @@ -538,6 +681,77 @@ def discard_blocked(board: BoardState, card: BoardEntity) -> bool: ) +def trainer_effects_blocked(board: BoardState, affected_player_id: str, + trainer_card: BoardEntity, + affected_entity: Optional[BoardEntity] = None) -> bool: + """Whether a passive shields `affected_player_id` from `trainer_card`'s + effects (Dew Guard); callers scope this to cross-player effects.""" + trainer_type = trainer_card.get_attribute(AttrID.TRAINER_TYPE) + return any( + passive.blocks_trainer_effects( + affected_player_id, trainer_card, trainer_type, carrier, + affected_entity=affected_entity, board=board) + for passive, carrier in active_passives(board) + ) + + +def supporter_effect_replacement(board: BoardState, card: BoardEntity, + player_id: str) -> Optional[Any]: + """First replacement coroutine a passive offers for this Supporter play + (Shifty Substitution), or None.""" + for passive, carrier in active_passives(board): + replacement = passive.replace_supporter_effect(card, player_id, carrier) + if replacement is not None: + return replacement + return None + + +def active_to_bench_counters(board: BoardState, pokemon: PokemonEntity) -> int: + """Damage counters for an Active that moved to its owner's Bench during + the owner's turn (Spikemuth); callers gate on whose turn it is.""" + return sum( + passive.counters_on_active_to_bench(pokemon, carrier) + for passive, carrier in active_passives(board) + ) + + +def evolve_heal_amount(board: BoardState, evolved: PokemonEntity, + pre_evolution: BoardEntity, player_id: str) -> int: + """Damage healed from a Pokemon just evolved from hand (Wyndon Stadium).""" + return sum( + passive.heal_on_evolve(evolved, pre_evolution, player_id, carrier) + for passive, carrier in active_passives(board) + ) + + +def attack_coin_reroll_offered(board: BoardState, player_id: str) -> bool: + """Whether a passive lets `player_id` re-flip attack coins (Glimwood Tangle).""" + return any( + passive.offers_attack_coin_reroll(player_id, carrier) + for passive, carrier in active_passives(board) + ) + + +def energy_attach_taxer(board: BoardState, attaching_player_id: str, + energy: BoardEntity, target: PokemonEntity) -> Optional[BoardEntity]: + """The carrier taxing this manual energy attach (Slimy Room), or None.""" + for passive, carrier in active_passives(board): + if passive.taxes_energy_attach(attaching_player_id, energy, target, carrier): + return carrier + return None + + +def retreat_energy_destination(board: BoardState, pokemon: PokemonEntity, + energy: BoardEntity) -> Optional[str]: + """First non-None area a passive redirects a retreat-cost energy to + (Skaters' Park), or None for the normal discard.""" + for passive, carrier in active_passives(board): + dest = passive.retreat_cost_destination(pokemon, energy, carrier) + if dest is not None: + return dest + return None + + def evolution_blocked(board: BoardState, player_id: str, target: PokemonEntity) -> bool: """Whether a passive forbids `player_id` evolving `target` (Dracovish).""" return any( @@ -567,6 +781,24 @@ def special_energy_suppressed(board: BoardState, energy: BoardEntity) -> bool: return _suppressed_special_energy(_collect_passives(board), energy) +def effective_pokemon_types(board: BoardState, pokemon: BoardEntity) -> List[Any]: + """A Pokemon's live type list after type-rewriting passives (Chromashift).""" + types = list(pokemon.get_attribute(AttrID.POKEMON_TYPES) or []) + for passive, carrier in active_passives(board): + types = passive.modify_pokemon_types(types, pokemon, carrier) + return types + + +def energy_removal_blocked(board: BoardState, mover_player_id: str, + card: BoardEntity) -> bool: + """Whether a passive keeps this attached Energy from being moved to + hand/deck/discard by `mover_player_id`'s trainer effect (Brazen Tail).""" + return any( + passive.blocks_energy_removal(card, mover_player_id, carrier) + for passive, carrier in active_passives(board) + ) + + def energy_provided_options(board: Optional[BoardState], energy: BoardEntity) -> List[List[int]]: """An energy card's provided-type options after suppression (a suppressed Special Energy provides only Colorless) and modify_energy_provided hooks.""" diff --git a/spirit/tools/effect_coverage.py b/spirit/tools/effect_coverage.py index b7d4323..960f507 100644 --- a/spirit/tools/effect_coverage.py +++ b/spirit/tools/effect_coverage.py @@ -15,6 +15,7 @@ SKIP_DIRS = {"__pycache__", "CUSTOM", "Free_Energy"} CARD_CLASSES = { "PokemonCardDef", "ItemCardDef", "SupporterCardDef", "StadiumCardDef", "PokemonToolCardDef", "EnergyCardDef", + "FossilItemCardDef", } ABILITY_CALL_NAMES = {"Attack", "Ability"}