FEAT: implement CRZ cards

This commit is contained in:
Brandon Nguyen
2026-07-14 08:38:12 -07:00
parent b6423c2078
commit 9a7b7e566b
174 changed files with 2367 additions and 435 deletions

View File

@@ -1129,6 +1129,217 @@ def bench_has_room(board, player_id):
and len(bench.children) < effective_bench_capacity(board, player_id)
# --- Brandon (SWSH12) -------------------------------------------------------
def brandon_playable(board, player_id) -> bool:
hand = board.find_player_area(player_id, "hand")
return bool(hand) and len(hand.children) == 1
async def brandon(ctx):
"""Draw a card for each Benched Pokemon (both yours and your opponent's)."""
count = len(_bench_pokemon(ctx.board, ctx.player_id)) \
+ len(_bench_pokemon(ctx.board, ctx.opponent_id))
if count > 0:
await ctx.draw_cards(count)
# --- Candice (SWSH12) --------------------------------------------------------
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
def candice_predicate(card) -> bool:
return is_water_pokemon(card) or is_water_energy_card(card)
# --- Capturing Aroma (SWSH12) ------------------------------------------------
async def capturing_aroma(ctx):
"""Flip a coin: heads searches for an Evolution Pokemon, tails a Basic
Pokemon; reveal it and put it into your hand. Then, shuffle your deck."""
heads, = await ctx.flip_coins(1, "Capturing Aroma")
predicate = is_evolution_pokemon if heads else is_basic_pokemon
prompt = "Choose an Evolution Pokémon to put into your hand." if heads \
else "Choose a Basic Pokémon to put into your hand."
picks = await ctx.search_deck(predicate, count=1, minimum=0, prompt=prompt)
await ctx.put_in_hand(picks, reveal=True)
await ctx.shuffle_deck()
# --- Earthen Seal Stone (SWSH12) ---------------------------------------------
async def star_gravity(ctx):
"""Put damage counters on each of the opponent's Pokemon V until its
remaining HP is 100 (VSTAR Power)."""
for pokemon in ctx.opponent_pokemon_in_play():
if not is_pokemon_v(pokemon.archetype_id):
continue
current = pokemon.get_attribute(AttrID.HP, ctx.max_hp(pokemon))
if current > 100:
await ctx.deal_damage(current - 100, target=pokemon,
apply_modifiers=False, as_counters=True)
# --- Emergency Jelly (SWSH12) -------------------------------------------------
async def emergency_jelly(ctx):
"""End of each turn: if the holder has 30 HP or less remaining and any
damage counters on it, heal 120 damage from it and discard this card."""
pokemon = ctx.source
hp = pokemon.get_attribute(AttrID.HP, 0)
max_hp = ctx.max_hp(pokemon)
if hp <= 30 and hp < max_hp:
await ctx.heal(120, 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])
# --- Furisode Girl (SWSH12) ---------------------------------------------------
async def furisode_girl(ctx):
"""Search the deck for a Basic Pokemon and put it onto the Bench; then
shuffle. You may switch that Pokemon with your Active Pokemon."""
picks = await ctx.search_deck(
is_basic_pokemon, count=1, minimum=0,
prompt="Choose a Basic Pokémon to put onto your Bench.",
)
if not picks:
await ctx.shuffle_deck()
return
target = picks[0]
await ctx.bench_pokemon(target)
await ctx.shuffle_deck()
if await ctx.ask_yes_no("Switch that Pokémon with your Active Pokémon?"):
await ctx.switch_active(ctx.player_id, target)
# --- Lance (SWSH12) -----------------------------------------------------------
def is_dragon_pokemon(card) -> bool:
types = card.get_attribute(AttrID.POKEMON_TYPES) or []
return is_pokemon_card(card) and PokemonTypes.DRAGON.value in types
async def lance(ctx):
"""Search the deck for up to 3 Dragon Pokemon, reveal them, and put them
into your hand. Then, shuffle your deck."""
picks = await ctx.search_deck(
is_dragon_pokemon, count=3, minimum=0,
prompt="Choose up to 3 Dragon Pokémon to put into your hand.",
)
await ctx.put_in_hand(picks, reveal=True)
await ctx.shuffle_deck()
# --- Leafy Camo Poncho (SWSH12) -----------------------------------------------
def leafy_camo_poncho_protects(affected_entity, carrier):
holder = carrier_pokemon(carrier)
return holder is not None and affected_entity is holder
def leafy_camo_poncho_condition(board, carrier):
holder = carrier_pokemon(carrier)
if holder is None:
return False
subs = subtypes_for(holder.archetype_id)
return "VSTAR" in subs or "VMAX" in subs
# --- Primordial Altar (SWSH12) ------------------------------------------------
def primordial_altar_condition(board, player_id, stadium):
return deck_nonempty(board, player_id)
async def primordial_altar(ctx):
"""Once during each player's turn: look at the top card of the deck and
may discard it."""
top = ctx.deck_top(1)
if not top:
return
card = top[0]
idx = await ctx.present_card_choice(
card, "Discard the top card of your deck?",
["Discard", "Keep it on top"],
)
if idx == 0:
await ctx.discard_cards([card])
PRIMORDIAL_ALTAR_ABILITY = Ability(
title="Primordial Altar",
game_text="Once during each player's turn, that player may look at the top card of their deck. They may discard that card.",
activation=Activations.ONCE_PER_TURN,
effect=primordial_altar,
condition=primordial_altar_condition,
)
# --- Professor Laventon (SWSH12) ----------------------------------------------
def _is_hisuian_pokemon(card) -> bool:
if not is_pokemon_card(card):
return False
name = getattr(def_for(card.archetype_id), "display_name", "") or ""
return "Hisuian" in name
def professor_laventon_playable(board, player_id) -> bool:
return any(_is_hisuian_pokemon(c) for c in _discard(board, player_id))
async def professor_laventon(ctx):
"""Put up to 3 Pokemon that have "Hisuian" in their names from the
discard pile into your hand."""
candidates = [c for c in ctx.discard_pile() if _is_hisuian_pokemon(c)]
picks = await ctx.choose_cards(
candidates, 3, minimum=0,
prompt="Choose up to 3 Hisuian Pokémon from your discard pile.",
)
await ctx.put_in_hand(picks, reveal=False)
# --- Quad Stone (SWSH12) -------------------------------------------------------
async def quad_stone(ctx):
"""Use 1: heal 10 from your Active. Use 4 at once (this + 3 more from
hand): heal all damage from each of your Pokemon."""
others = [c for c in ctx.hand()
if c is not ctx.source and c.archetype_id == ctx.source.archetype_id]
use_four = False
if len(others) >= 3 and await ctx.ask_yes_no(
"Use 3 more Quad Stone cards from your hand to heal all damage from "
"each of your Pokémon instead of healing 10 from your Active Pokémon?"
):
picks = await ctx.choose_cards(
others, 3, minimum=3, prompt="Choose 3 more Quad Stone cards to use",
)
if len(picks) >= 3:
await ctx.discard_cards(picks)
for pokemon in ctx.my_pokemon_in_play():
await ctx.heal(9999, pokemon)
use_four = True
if not use_four:
active = ctx.my_active()
if active is not None:
await ctx.heal(10, active)
# --- Wallace (SWSH12) ----------------------------------------------------------
async def wallace(ctx):
"""Draw 3 cards. Your opponent may draw a card; if they do, draw 1 more."""
await ctx.draw_cards(3)
if await ctx.ask_yes_no("Draw a card?", player_id=ctx.opponent_id):
await ctx.draw_cards(1, ctx.opponent_id)
await ctx.draw_cards(1)
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

View File

@@ -376,7 +376,8 @@ class PokemonCardDef(CardDefinition):
subtypes: Optional[List[str]] = None,
attributes: Optional[dict] = None,
passive: Optional[Any] = None,
unplayable_from_hand: bool = False
unplayable_from_hand: bool = False,
setup_as_active: bool = False
):
super().__init__(guid, key, name, collector_number, set_code, rarity, display_name, searchable_by, subtypes, attributes)
# Card-level continuous effect while this Pokemon is top-level in play
@@ -384,6 +385,9 @@ class PokemonCardDef(CardDefinition):
self.passive = passive
# Shedinja: never offered as a hand bench-play (enters play by effect).
self.unplayable_from_hand = unplayable_from_hand
# Luxray CZ's Explosiveness: may be placed as the opening Active
# despite its stage (setup only; bench plays stay Basics-only).
self.setup_as_active = setup_as_active
# Add Pokemon-specific defaults to extra_attributes
self.extra_attributes.update({

View File

@@ -487,16 +487,34 @@ class BoardState:
"""
return bool(self.basic_pokemon_in_hand(player_id))
def setup_active_candidates(self, player_id: str) -> List['PokemonEntity']:
"""Cards playable as the opening Active: Basics first, then hand
Pokemon whose def sets setup_as_active (Luxray CZ's Explosiveness).
The bench offer and mid-game bench plays stay Basics-only."""
from spirit.game.data_utils import def_for # circular-import guard
candidates = self.basic_pokemon_in_hand(player_id)
hand_area = self.find_player_area(player_id, "hand")
for c in (hand_area.children if hand_area else []):
if isinstance(c, PokemonEntity) and c not in candidates \
and getattr(def_for(c.archetype_id), "setup_as_active", False):
candidates.append(c)
return candidates
def player_has_any_basic(self, player_id: str) -> bool:
"""True if the player has a Basic Pokemon anywhere in deck or hand.
Guards the mulligan loop against decks that can never produce a legal
opening hand (which would otherwise reshuffle forever).
"""
from spirit.game.data_utils import def_for # circular-import guard
for area_name in ("deck", "hand"):
area = self.find_player_area(player_id, area_name)
if area and any(self._is_basic_pokemon(c) for c in area.children):
return True
for c in (area.children if area else []):
if self._is_basic_pokemon(c):
return True
if isinstance(c, PokemonEntity) \
and getattr(def_for(c.archetype_id), "setup_as_active", False):
return True
return False
def pokemon_in_play(self, player_id: str) -> List['PokemonEntity']:

View File

@@ -1,5 +1,6 @@
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="d73ca4da-dd21-f428-8051-264ab564587c",
@@ -7,5 +8,7 @@ card = ItemCardDef(
name="com.direwolfdigital.cake.data.archetypes.trainer.Potion.Name",
collector_number=100,
set_code="BW1",
rarity=Rarities.Common
rarity=Rarities.Common,
condition=requires_damaged_pokemon(),
effect=heal_item(30)
)

View File

@@ -1,6 +1,17 @@
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 lost_claw(ctx):
"""70. Put a random card from your opponent's hand in the Lost Zone."""
await ctx.deal_damage()
hand = ctx.hand(ctx.opponent_id)
if hand:
await ctx.move_to_lost_zone([random.choice(hand)])
card = PokemonCardDef(
guid="b8a98898-af7d-52a3-a682-ab6a0ff76dae",
key="CZ",
@@ -28,7 +39,7 @@ card = PokemonCardDef(
game_text="Put a random card from your opponent's hand in the Lost Zone.",
cost={PokemonTypes.DARKNESS: 1, PokemonTypes.COLORLESS: 2},
damage=70,
effect=unimplemented,
effect=lost_claw,
),
],
)

View File

@@ -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.passives import Passive
class CounterPressPassive(Passive):
"""Reflects the damage this Pokemon takes onto the attacker as counters."""
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 target is not carrier:
return None
attacker = calc.attacker
if attacker is not None:
await ctx.deal_damage(calc.amount, target=attacker,
apply_modifiers=False, as_counters=True)
return None
async def counter_press(ctx):
"""90. During your opponent's next turn, a hit on this Pokemon reflects
as damage counters on the attacker (even if this Pokemon is KO'd)."""
await ctx.deal_damage()
ctx.add_passive_through_opponents_turn(ctx.attacker, CounterPressPassive())
card = PokemonCardDef(
guid="ede06640-e9fd-56bb-bcf4-a84577370ce7",
@@ -25,7 +49,7 @@ card = PokemonCardDef(
game_text="During your opponent's next turn, if this Pok\u00e9mon is damaged by an attack (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.",
cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 2},
damage=90,
effect=unimplemented,
effect=counter_press,
),
Attack(
title="Heavy Impact",

View File

@@ -1,5 +1,6 @@
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
from 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="66eca74a-2caf-5210-a324-f4b2aa277233",
@@ -29,7 +30,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),
),
],
)

View File

@@ -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="5c6b91c9-4040-5658-81f8-ec1351d97293",
@@ -11,5 +33,5 @@ card = SupporterCardDef(
collector_number=123,
set_code="CZ",
rarity=Rarities.RareHolo,
effect=unimplemented
effect=bea
)

View File

@@ -1,4 +1,5 @@
from spirit.game.data_utils import SupporterCardDef, unimplemented
from spirit.game.card_effects.trainers import bede, bede_playable
from spirit.game.data_utils import SupporterCardDef
from spirit.game.attributes import Rarities
card = SupporterCardDef(
@@ -11,5 +12,6 @@ card = SupporterCardDef(
collector_number=124,
set_code="CZ",
rarity=Rarities.RareHolo,
effect=unimplemented
effect=bede,
condition=bede_playable
)

View File

@@ -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 flip_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="d57c93dd-02e6-5ad1-9b55-7ea441dd4e43",
@@ -25,7 +36,7 @@ card = PokemonCardDef(
cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1},
damage=80,
damage_operator="x",
effect=unimplemented,
effect=flip_damage(coins=3, per_heads=80, also=_switch_self),
),
],
)

View File

@@ -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 flip_or_nothing
from spirit.game.card_effects.passives_common import prevent_damage_when
def _carefree_pred(calc, carrier):
return calc.is_attack and calc.target is carrier and not calc.to_active
card = PokemonCardDef(
guid="cc5cc393-6005-5fc0-8114-e5d41ba4c6e4",
@@ -21,14 +27,14 @@ card = PokemonCardDef(
Ability(
title="Carefree Countenance",
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(_carefree_pred, attacks_only=False),
),
Attack(
title="Hyper Fang",
game_text="Flip a coin. If tails, this attack does nothing.",
cost={PokemonTypes.COLORLESS: 2},
damage=30,
effect=unimplemented,
effect=flip_or_nothing(),
),
],
)

View File

@@ -1,5 +1,6 @@
from spirit.game.data_utils import PokemonCardDef, Attack, 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="550b78f5-3e2a-50ce-8353-d1a1191b90a9",
@@ -25,7 +26,7 @@ card = PokemonCardDef(
game_text="Draw 2 cards.",
cost={PokemonTypes.COLORLESS: 1},
damage=20,
effect=unimplemented,
effect=draw_attack(2),
),
Attack(
title="Power Edge",

View File

@@ -1,5 +1,6 @@
from spirit.game.data_utils import PokemonCardDef, Attack, 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, heal_targets
card = PokemonCardDef(
guid="9656d298-ea31-505e-adbd-b6bb0bc42a6c",
@@ -23,14 +24,14 @@ 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),
),
Attack(
title="Bloomshine",
game_text="Heal 20 damage from each of your Pok\u00e9mon.",
cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 2},
damage=90,
effect=unimplemented,
effect=heal_targets(20, scope="each_own"),
),
],
)

View File

@@ -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="d41fce1f-9097-555b-b507-4504bf9d6d06",
@@ -22,14 +23,14 @@ card = PokemonCardDef(
title="Festering Saliva",
game_text="Your opponent's Active Pok\u00e9mon is now Burned and Poisoned.",
cost={PokemonTypes.GRASS: 1},
effect=unimplemented,
effect=condition_attack(SpecialConditions.BURNED, SpecialConditions.POISONED),
),
Attack(
title="Bind Down",
game_text="During your opponent's next turn, the Defending Pok\u00e9mon can't retreat.",
cost={PokemonTypes.COLORLESS: 2},
damage=40,
effect=unimplemented,
effect=condition_attack(no_retreat=True),
),
],
)

View File

@@ -1,5 +1,6 @@
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
from 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="ffd5fc1c-42d7-5ff9-9140-707e461a7d8a",
@@ -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),
),
],
)

View File

@@ -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="7c859d59-2894-5783-8c83-60fb3049e133",
key="CZ",
@@ -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",

View File

@@ -1,5 +1,6 @@
from spirit.game.data_utils import PokemonCardDef, Attack, 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
card = PokemonCardDef(
guid="f1fbc56e-ecba-5831-94b7-573419e85f04",
@@ -23,7 +24,7 @@ card = PokemonCardDef(
title="Cycle Draw",
game_text="Discard a card from your hand. If you do, draw 2 cards.",
cost={PokemonTypes.COLORLESS: 1},
effect=unimplemented,
effect=discard_then_draw(1, 2, optional=True),
),
Attack(
title="Flap",

View File

@@ -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="fdc218bf-196c-5954-bb04-bca1ec80a911",
@@ -11,5 +27,6 @@ card = ItemCardDef(
collector_number=125,
set_code="CZ",
rarity=Rarities.Uncommon,
effect=unimplemented
effect=crushing_hammer,
condition=opponent_has_energy_attached
)

View File

@@ -1,6 +1,19 @@
from spirit.game.data_utils import SupporterCardDef, unimplemented
from spirit.game.data_utils import SupporterCardDef
from spirit.game.attributes import Rarities
async def digging_duo(ctx):
"""Flip a coin: heads look at bottom 8, tails bottom 3; put 1 into hand."""
heads, = await ctx.flip_coins(1, "Digging Duo")
bottom = ctx.deck()[:8 if heads else 3]
if bottom:
picks = await ctx.choose_cards(
bottom, 1, prompt="Choose a card to put into your hand.",
)
await ctx.put_in_hand(picks, reveal=False)
await ctx.shuffle_deck()
card = SupporterCardDef(
guid="bba06028-290f-5617-8d42-9f4927565f2f",
key="CZ",
@@ -11,5 +24,5 @@ card = SupporterCardDef(
collector_number=126,
set_code="CZ",
rarity=Rarities.Uncommon,
effect=unimplemented
effect=digging_duo,
)

View File

@@ -1,5 +1,6 @@
from spirit.game.data_utils import PokemonCardDef, Attack, 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 SuddenTransformationPassive
card = PokemonCardDef(
guid="d561efa6-d36b-5004-b823-04531ff6bbdb",
@@ -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(),
),
],
)

View File

@@ -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 _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 rocket_poison(ctx):
"""Your opponent's Active Pokémon is now Poisoned. If this Pokémon
evolved from Skrelp during this turn, put 8 damage counters instead of 1."""
counters = 8 if _evolved_this_turn(ctx) else 1
await ctx.apply_special_condition(
ctx.defender, SpecialConditions.POISONED, poison_counters=counters)
card = PokemonCardDef(
guid="5b049f74-2e13-5394-bb2d-7ea832d36233",
@@ -23,7 +37,7 @@ card = PokemonCardDef(
title="Rocket Poison",
game_text="Your opponent's Active Pok\u00e9mon is now Poisoned. If this Pok\u00e9mon evolved from Skrelp during this turn, put 8 damage counters on that Pok\u00e9mon instead of 1 during Pok\u00e9mon Checkup.",
cost={PokemonTypes.DARKNESS: 1},
effect=unimplemented,
effect=rocket_poison,
),
Attack(
title="Razor Fin",

View File

@@ -1,5 +1,6 @@
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
from 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="78aee80e-9456-52a3-b2e2-55268181d44b",
@@ -24,7 +25,7 @@ card = PokemonCardDef(
game_text="This attack also does 10 damage to 1 of your Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)",
cost={PokemonTypes.COLORLESS: 1},
damage=40,
effect=unimplemented,
effect=snipe_attack(10, pool="bench", count=1, side="mine", also_base=True),
),
Attack(
title="Rolling Dash",
@@ -32,7 +33,7 @@ card = PokemonCardDef(
cost={PokemonTypes.COLORLESS: 2},
damage=60,
damage_operator="+",
effect=unimplemented,
effect=flip_damage(until_tails=True, base=60, per_heads=30),
),
],
)

View File

@@ -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="2b92d3d9-e5bd-5026-88c1-3a99f9c8eb82",
@@ -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,
),
],
)

View File

@@ -1,5 +1,6 @@
from spirit.game.data_utils import PokemonCardDef, Attack, 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="87d64657-cb5b-58a6-82c6-11ff214e6cb1",
@@ -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),
),
],
)

View File

@@ -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="fc815258-d645-5146-95f7-6a78443cd444",
@@ -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),
),
],
)

View File

@@ -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="6806e5e1-1032-5db4-8978-3841d7bd6a7d",
@@ -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",

View File

@@ -1,5 +1,6 @@
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
from 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="1e67d21d-d7f8-5954-b35d-c208ee2eb6e0",
@@ -29,7 +30,7 @@ card = PokemonCardDef(
cost={PokemonTypes.COLORLESS: 3},
damage=80,
damage_operator="+",
effect=unimplemented,
effect=bonus_if(defender_is_v, 80),
),
],
)

View File

@@ -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="c616a72f-74b1-5f40-bbbc-6337bdcf607c",
@@ -11,5 +40,5 @@ card = SupporterCardDef(
collector_number=147,
set_code="CZ",
rarity=Rarities.RareUltra,
effect=unimplemented
effect=elesas_sparkle
)

View File

@@ -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="3b51afab-da44-58d4-935d-a30e014ed572",
@@ -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),
),
],
)

View File

@@ -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 bonus_if
async def draining_kiss(ctx):
"""20. Heal 20 damage from this Pokémon."""
await ctx.deal_damage()
await ctx.heal(20, ctx.attacker)
def _same_hand_size(ctx):
return ctx.hand_size() == ctx.hand_size(ctx.opponent_id)
card = PokemonCardDef(
guid="fa3103ab-be3e-5b66-a8f1-ea0e4e94d521",
@@ -23,7 +35,7 @@ card = PokemonCardDef(
game_text="Heal 20 damage from this Pok\u00e9mon.",
cost={PokemonTypes.PSYCHIC: 1},
damage=20,
effect=unimplemented,
effect=draining_kiss,
),
Attack(
title="Loving Sympathy",
@@ -31,7 +43,7 @@ card = PokemonCardDef(
cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 2},
damage=70,
damage_operator="+",
effect=unimplemented,
effect=bonus_if(_same_hand_size, 70),
),
],
)

View File

@@ -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=127,
set_code="CZ",
rarity=Rarities.Common,
effect=unimplemented
condition=requires_discard(is_basic_energy_card),
effect=recover_from_discard(is_basic_energy_card, count=2, minimum=1, to="hand"),
)

View File

@@ -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=128,
set_code="CZ",
rarity=Rarities.Common,
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."),
)

View File

@@ -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="654fda60-9a95-54fd-9f0d-a4df9c7d55a6",
key="CZ",
@@ -11,5 +26,6 @@ card = ItemCardDef(
collector_number=129,
set_code="CZ",
rarity=Rarities.Uncommon,
effect=unimplemented
condition=energy_switch_condition,
effect=energy_switch,
)

View File

@@ -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 retreat_free_when
from spirit.game.card_effects.pokemon import energy_provides_type
from spirit.game.models.board import BoardState
def _has_fire_energy(pokemon, carrier):
return pokemon is carrier and any(
energy_provides_type(e, PokemonTypes.FIRE.value)
for e in BoardState.attached_energies(pokemon)
)
card = PokemonCardDef(
guid="fae64169-d66c-5a75-a3ec-3aad1982f203",
@@ -21,7 +32,7 @@ card = PokemonCardDef(
Ability(
title="Explosive Heat Dash",
game_text="If this Pok\u00e9mon has any Fire Energy attached, it has no Retreat Cost.",
effect=unimplemented,
passive=retreat_free_when(_has_fire_energy),
),
Attack(
title="Claw Slash",

View File

@@ -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="1997382b-9f81-567f-96c6-d989cb94a6b9",
@@ -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, PokemonTypes.COLORLESS: 1},
damage=20,
effect=unimplemented,
effect=condition_attack(SpecialConditions.PARALYZED, flip=True),
),
],
)

View File

@@ -1,5 +1,6 @@
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
from 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="08260c2c-df89-5676-96ef-75f2b9a433b8",
@@ -24,7 +25,9 @@ card = PokemonCardDef(
title="Powerful Storm",
game_text="This attack does 20 damage for each Energy attached to all of your Pok\u00e9mon.",
cost={PokemonTypes.PSYCHIC: 1},
effect=unimplemented,
damage=20,
damage_operator="x",
effect=damage_per(count_energy("mine"), 20),
),
Attack(
title="Stampede",

View File

@@ -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="d48141ff-3e7c-5905-8dc4-7bee4e9abd02",
@@ -11,5 +12,5 @@ card = SupporterCardDef(
collector_number=130,
set_code="CZ",
rarity=Rarities.Uncommon,
effect=unimplemented
effect=draw_attack(3)
)

View File

@@ -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="b8aab8e6-e710-5acc-90d0-075282434464",
@@ -11,5 +12,5 @@ card = SupporterCardDef(
collector_number=148,
set_code="CZ",
rarity=Rarities.RareUltra,
effect=unimplemented
effect=draw_attack(3)
)

View File

@@ -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="f869e481-4a46-5266-901b-900e2022f3ab",
@@ -11,5 +12,5 @@ card = SupporterCardDef(
collector_number=131,
set_code="CZ",
rarity=Rarities.Uncommon,
effect=unimplemented
effect=draw_attack(3)
)

View File

@@ -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="d73c8c29-ff5d-587d-befd-99ef44c401ba",
@@ -11,5 +12,5 @@ card = SupporterCardDef(
collector_number=149,
set_code="CZ",
rarity=Rarities.RareUltra,
effect=unimplemented
effect=draw_attack(3)
)

View File

@@ -1,5 +1,6 @@
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
from 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="e86de1ae-68bb-51f3-8a82-b9104424ad7f",
@@ -25,7 +26,7 @@ card = PokemonCardDef(
cost={PokemonTypes.METAL: 1},
damage=10,
damage_operator="+",
effect=unimplemented,
effect=flip_bonus(20),
),
],
)

View File

@@ -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="48322948-2b21-5927-a13f-b358e3344f77",
@@ -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),
),
],
)

View File

@@ -1,5 +1,6 @@
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
from 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="56239d56-0d3b-5411-a04d-1c26d1eae253",
@@ -26,7 +27,7 @@ card = PokemonCardDef(
cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 1},
damage=30,
damage_operator="+",
effect=unimplemented,
effect=flip_bonus(60),
),
Attack(
title="Slash",

View File

@@ -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="16bda894-1118-547a-9b79-738880e7035f",
@@ -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="Psybeam",
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),
),
],
)

View File

@@ -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_water_energy(card):
return energy_provides_type(card, PokemonTypes.WATER.value)
card = PokemonCardDef(
guid="d05205ad-77c3-5629-8d38-9e9878b8182b",
@@ -23,7 +30,7 @@ card = PokemonCardDef(
game_text="Search your deck for a Water Energy card and attach it to this Pok\u00e9mon. Then, shuffle your deck.",
cost={PokemonTypes.WATER: 1},
damage=30,
effect=unimplemented,
effect=search_attach_energy(predicate=_is_water_energy, count=1, to_self=True),
),
Attack(
title="Freezing Wind",

View File

@@ -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="accabfca-c8d4-571d-b5e3-43380d1f987e",
@@ -24,7 +25,7 @@ card = PokemonCardDef(
game_text="Your opponent's Active Pok\u00e9mon is now Confused and Poisoned.",
cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1},
damage=20,
effect=unimplemented,
effect=condition_attack(SpecialConditions.CONFUSED, SpecialConditions.POISONED),
),
],
)

View File

@@ -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)]
# No matches still shows the looked-at cards (nothing selectable).
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="86f22ea3-8a9a-5100-b3e1-a5be7f53f923",
@@ -11,5 +28,5 @@ card = ItemCardDef(
collector_number=132,
set_code="CZ",
rarity=Rarities.Uncommon,
effect=unimplemented
effect=great_ball
)

View File

@@ -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="aab2eb28-3de4-5543-80f3-f25e0ebbbbe9",
@@ -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),
),
],
)

View File

@@ -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="da2d4201-db35-5408-bf85-f3d0dad17d1e",
@@ -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(),
),
],
)

View File

@@ -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
def _stakeout_bonus(ctx):
active = ctx.opponent_active()
if active is None:
return False
turn_state = ctx.session.turn_state
return turn_state.became_active_turn.get(active.entity_id) == turn_state.turn_number - 1
card = PokemonCardDef(
guid="e70c910b-dcf4-5666-9b5d-db5ece37d83f",
@@ -25,7 +35,7 @@ card = PokemonCardDef(
cost={PokemonTypes.COLORLESS: 2},
damage=30,
damage_operator="+",
effect=unimplemented,
effect=bonus_if(_stakeout_bonus, 120),
),
Attack(
title="Lunge Out",

View File

@@ -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, SpecialConditions
from spirit.game.card_effects.attacks_common import condition_attack
from spirit.game.card_effects.support_common import requires_damaged_pokemon
async def witchs_domain(ctx):
"""Once during your turn, you may move up to 2 damage counters from your
Pokemon to your opponent's Active 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 up to 2 damage counters from your Pokémon to your "
"opponent's Active Pokémon?"
):
return
source = await ctx.choose_pokemon(
candidates, "Choose 1 of your Pokémon to move damage counters from"
)
if source is None:
return
target = ctx.opponent_active()
if target is None:
return
await ctx.move_damage_counters(source, target, max_count=2)
card = PokemonCardDef(
guid="57f38c45-b660-582a-8e3e-318e595a8bea",
@@ -23,14 +49,16 @@ card = PokemonCardDef(
Ability(
title="Witch's Domain",
game_text="Once during your turn, you may move up to 2 damage counters from your Pok\u00e9mon to your opponent's Active Pok\u00e9mon.",
effect=unimplemented,
activation=Activations.ONCE_PER_TURN,
condition=requires_damaged_pokemon("mine"),
effect=witchs_domain,
),
Attack(
title="G-Max Smite",
game_text="Your opponent's Active Pok\u00e9mon is now Confused.",
cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 2},
damage=150,
effect=unimplemented,
effect=condition_attack(SpecialConditions.CONFUSED),
),
],
)

View File

@@ -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 switch_self_attack
from spirit.game.card_effects.trainers import is_energy_card
async def horoscope(ctx):
"""Look at the top 3 cards of your deck. You may attach any number of
Energy cards you find there to this Pokemon. Put the other cards back."""
top = ctx.deck_top(3)
if not top:
return
energies = [c for c in top if is_energy_card(c)]
picks = await ctx.choose_cards(
energies, max(len(energies), 1), minimum=0,
prompt="Choose Energy cards to attach to this Pokémon.",
display_cards=top,
)
for card in picks:
await ctx.attach_energy(card, ctx.attacker)
card = PokemonCardDef(
guid="81227f69-d3c2-58fa-98e1-25ea982eb0ba",
@@ -23,14 +42,14 @@ card = PokemonCardDef(
title="Horoscope",
game_text="Look at the top 3 cards of your deck. You may attach any number of Energy cards you find there to this Pok\u00e9mon. Put the other cards back in any order.",
cost={PokemonTypes.PSYCHIC: 1},
effect=unimplemented,
effect=horoscope,
),
Attack(
title="Teleportation Burst",
game_text="Switch this Pok\u00e9mon with 1 of your Benched Pok\u00e9mon.",
cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 2},
damage=80,
effect=unimplemented,
effect=switch_self_attack(damage=80, optional=False),
),
],
)

View File

@@ -1,5 +1,6 @@
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
from 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="889fffbc-c90f-5f38-8e62-a2c1a40e0268",
@@ -29,7 +30,7 @@ card = PokemonCardDef(
game_text="This Pok\u00e9mon also does 50 damage to itself.",
cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 2},
damage=150,
effect=unimplemented,
effect=recoil_attack(50),
),
],
)

View File

@@ -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="27413188-f69d-55ba-ab2b-4f72c7337aa1",
key="CZ",
@@ -20,10 +27,10 @@ card = PokemonCardDef(
abilities=[
Attack(
title="Assault Gate",
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.",
game_text="If this Pokémon 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,
),
],
)
)

View File

@@ -1,4 +1,5 @@
from spirit.game.data_utils import SupporterCardDef, unimplemented
from spirit.game.card_effects.trainers import hop
from spirit.game.data_utils import SupporterCardDef
from spirit.game.attributes import Rarities
card = SupporterCardDef(
@@ -11,5 +12,5 @@ card = SupporterCardDef(
collector_number=133,
set_code="CZ",
rarity=Rarities.RareHolo,
effect=unimplemented
effect=hop
)

View File

@@ -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="cd07c0d1-fd80-5ebe-bbea-12232fa13f95",
@@ -23,7 +24,7 @@ card = PokemonCardDef(
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),
),
],
)

View File

@@ -1,5 +1,6 @@
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
from 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="134d56d1-fa37-5ec6-a7bf-441abd394007",
@@ -24,7 +25,7 @@ card = PokemonCardDef(
cost={PokemonTypes.GRASS: 1},
damage=10,
damage_operator="+",
effect=unimplemented,
effect=flip_bonus(20),
),
],
)

View File

@@ -1,5 +1,6 @@
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
from 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="335d48e9-4d6e-52be-aaa4-19ca779b2fbd",
@@ -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),
),
],
)

View File

@@ -1,5 +1,6 @@
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
from 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="4403b192-7c0c-5597-a32d-56a4686d0b6b",
@@ -22,14 +23,14 @@ card = PokemonCardDef(
title="Dual Splash",
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.WATER: 1, PokemonTypes.COLORLESS: 2},
effect=unimplemented,
effect=snipe_attack(50, pool="any", count=2, side="opponent"),
),
Attack(
title="Aqua Typhoon",
game_text="During your next turn, this Pok\u00e9mon can't use Aqua Typhoon.",
cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 3},
damage=210,
effect=unimplemented,
locks_next_turn=True,
),
],
)

View File

@@ -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.pokemon import energy_provides_type
from spirit.game.card_effects.support_common import search_attach_energy
def _is_water_energy(card):
return energy_provides_type(card, PokemonTypes.WATER.value)
async def dynamic_wave(ctx):
"""Put 3 Energy attached to this Pokemon into hand; 180 to 1 of opponent's Pokemon."""
attached = ctx.attached_energies(ctx.attacker)
if attached:
picks = await ctx.choose_cards(
attached, 3,
prompt="Choose 3 Energy attached to this Pokémon to put into your hand",
)
await ctx.put_in_hand(picks, reveal=False)
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(180, target=target)
card = PokemonCardDef(
guid="32672775-3b7e-5d18-a914-d5eaced8cdc9",
@@ -22,13 +45,13 @@ card = PokemonCardDef(
title="Wave Summoning",
game_text="Search your deck for a Water 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_water_energy, count=1, to_self=True),
),
Attack(
title="Dynamic Wave",
game_text="Put 3 Energy attached to this Pok\u00e9mon into your hand. This attack does 180 damage to 1 of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)",
cost={PokemonTypes.WATER: 3, PokemonTypes.COLORLESS: 1},
effect=unimplemented,
effect=dynamic_wave,
),
],
)

View File

@@ -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 wreak_havoc(ctx):
"""80. Flip a coin until tails; discard opponent's top deck card per heads."""
await ctx.deal_damage()
heads = await ctx.flip_until_tails(ctx.ability.title)
if heads:
await ctx.discard_cards(ctx.deck_top(heads, player_id=ctx.opponent_id))
card = PokemonCardDef(
guid="76a0f9a8-6eea-5977-87ed-453818144afb",
key="CZ",
@@ -30,7 +39,7 @@ card = PokemonCardDef(
game_text="Flip a coin until you get tails. For each heads, discard the top card of your opponent's deck.",
cost={PokemonTypes.METAL: 2, PokemonTypes.COLORLESS: 2},
damage=80,
effect=unimplemented,
effect=wreak_havoc,
),
],
)

View File

@@ -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.passives_common import TakesLessPassive
def _ivy_star_condition(board, player_id, pokemon):
opponent_id = next(p for p in board.player_ids if p != player_id)
bench = board.find_player_area(opponent_id, "bench")
return bool(board.active_pokemon(opponent_id)) and bool(bench and bench.children)
async def ivy_star(ctx):
"""You may switch 1 of your opponent's Benched Pokémon with their Active
Pokémon."""
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
if not await ctx.ask_yes_no(
"Switch 1 of your opponent's Benched Pokémon with their Active Pokémon?"
):
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)
async def leaf_guard(ctx):
"""180. During your opponent's next turn, this Pokémon takes 30 less
damage from attacks (after applying Weakness and Resistance)."""
await ctx.deal_damage()
ctx.add_passive_through_opponents_turn(ctx.attacker, TakesLessPassive(30))
card = PokemonCardDef(
guid="1c418037-55c4-561c-b98b-acb0a20099c0",
@@ -22,14 +55,16 @@ card = PokemonCardDef(
Ability(
title="Ivy Star",
game_text="During your turn, you may 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=_ivy_star_condition,
effect=ivy_star,
),
Attack(
title="Leaf 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: 2, PokemonTypes.COLORLESS: 1},
damage=180,
effect=unimplemented,
effect=leaf_guard,
),
],
)

View File

@@ -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 leaf_guard(ctx):
"""30. During your opponent's next turn, this Pokémon takes 30 less
damage from attacks (after applying Weakness and Resistance)."""
await ctx.deal_damage()
ctx.add_passive_through_opponents_turn(ctx.attacker, TakesLessPassive(30))
card = PokemonCardDef(
guid="af49d502-c4b6-5599-bde4-b5c95992105a",
@@ -23,14 +32,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.GRASS: 1},
damage=30,
effect=unimplemented,
effect=leaf_guard,
),
Attack(
title="Slashing Strike",
game_text="During your next turn, this Pok\u00e9mon can't use Slashing Strike.",
cost={PokemonTypes.GRASS: 2, PokemonTypes.COLORLESS: 1},
damage=180,
effect=unimplemented,
locks_next_turn=True,
),
],
)

View File

@@ -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="f8fa0778-c5f3-5966-a0f2-e8aa20803a1e",
@@ -11,5 +22,5 @@ card = SupporterCardDef(
collector_number=134,
set_code="CZ",
rarity=Rarities.RareHolo,
effect=unimplemented
effect=leon_effect
)

View File

@@ -1,5 +1,6 @@
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
from 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="2e9bf2ed-ca51-5c17-8f91-a9057b71cedb",
@@ -25,7 +26,7 @@ card = PokemonCardDef(
cost={PokemonTypes.COLORLESS: 2},
damage=40,
damage_operator="x",
effect=unimplemented,
effect=flip_damage(coins=3, per_heads=40),
),
Attack(
title="Claw Slash",

View File

@@ -1,4 +1,5 @@
from spirit.game.data_utils import ItemCardDef, unimplemented
from spirit.game.card_effects.trainers import lost_vacuum, lost_vacuum_playable
from spirit.game.data_utils import ItemCardDef
from spirit.game.attributes import Rarities
card = ItemCardDef(
@@ -11,5 +12,6 @@ card = ItemCardDef(
collector_number=135,
set_code="CZ",
rarity=Rarities.Uncommon,
effect=unimplemented
effect=lost_vacuum,
condition=lost_vacuum_playable
)

View File

@@ -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="2629a956-6b0c-58b5-a372-91927fa03c96",
@@ -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),
),
],
)

View File

@@ -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 emotional_draw(ctx):
"""Shuffle your hand into your deck. Then, draw 5 cards."""
await ctx.shuffle_into_deck(ctx.hand(), ctx.player_id)
await ctx.draw_cards(5)
card = PokemonCardDef(
guid="0367b96d-ec56-5b5f-96f8-e5ee7689be85",
key="CZ",
@@ -22,7 +29,7 @@ card = PokemonCardDef(
title="Emotional Draw",
game_text="Shuffle your hand into your deck. Then, draw 5 cards.",
cost={PokemonTypes.WATER: 1},
effect=unimplemented,
effect=emotional_draw,
),
Attack(
title="Tackle",

View File

@@ -1,5 +1,6 @@
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
from 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="affeecef-93e0-5191-bdf3-abec1ff30a6e",
@@ -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",

View File

@@ -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 has_tool
async def shorting_spark(ctx):
"""90 to each opponent Pokemon with a Pokemon Tool attached (Bench takes no W/R)."""
active = ctx.opponent_active()
for target in ctx.opponent_pokemon_in_play():
if has_tool(target):
await ctx.deal_damage(90, target=target, apply_modifiers=target is active)
card = PokemonCardDef(
guid="323885a3-01bb-580d-98ad-49079cb9379f",
@@ -23,7 +33,7 @@ card = PokemonCardDef(
title="Shorting Spark",
game_text="This attack does 90 damage to each of your opponent's Pok\u00e9mon that has a Pok\u00e9mon Tool attached. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)",
cost={PokemonTypes.LIGHTNING: 1},
effect=unimplemented,
effect=shorting_spark,
),
Attack(
title="Bite",

View File

@@ -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="8b0a6d3e-8365-5861-85de-47ec0ed75b5f",
@@ -21,17 +31,17 @@ card = PokemonCardDef(
abilities=[
Attack(
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.",
game_text="This attack does 40 damage to 1 of your opponent's Pokémon. (Don't apply Weakness and Resistance for Benched Pokémon.) Switch this Pokémon with 1 of your Benched Pokémon.",
cost={PokemonTypes.LIGHTNING: 1},
effect=unimplemented,
effect=snipe_attack(40, pool="any", count=1, also=_switch_self),
),
Attack(
title="Scar Strikes",
game_text="If your opponent's Active Pok\u00e9mon already has any damage counters on it, this attack does 100 more damage.",
game_text="If your opponent's Active Pokémon already has any damage counters on it, this attack does 100 more damage.",
cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 1},
damage=100,
damage_operator="+",
effect=unimplemented,
effect=bonus_if(has_damage("defender"), 100),
),
],
)
)

View File

@@ -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_trainer_card
card = PokemonCardDef(
guid="ef8ca3ef-7c49-55f9-8a3b-0bf871f8e524",
@@ -18,18 +20,21 @@ card = PokemonCardDef(
weakness_type=PokemonTypes.FIGHTING,
evolves_from="com.direwolfdigital.cake.data.archetypes.pokemon.Luxio.Name",
family_id=403,
setup_as_active=True,
abilities=[
Ability(
title="Explosiveness",
game_text="If this Pok\u00e9mon is in your hand when you are setting up to play, you may put it face down as your Active Pok\u00e9mon.",
effect=unimplemented,
),
Attack(
title="Seeking Fang",
game_text="Search your deck for up to 2 Trainer cards, reveal them, and put them into your hand. Then, shuffle your deck.",
cost={PokemonTypes.COLORLESS: 1},
damage=50,
effect=unimplemented,
effect=search_to_hand(
is_trainer_card, count=2, minimum=0,
prompt="Choose up to 2 Trainer cards to put into your hand.",
),
),
],
)

View File

@@ -1,5 +1,6 @@
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
from 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="e9a82ef0-4501-5963-a606-5cbc18edaff7",
@@ -25,7 +26,7 @@ card = PokemonCardDef(
cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 1},
damage=240,
damage_operator="-",
effect=unimplemented,
effect=damage_per(count_energy(scope="defender"), per=-80, base=240),
),
],
)

View File

@@ -1,5 +1,6 @@
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
from 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="dab72893-000a-5cf9-ae14-fe1a2ce534ff",
@@ -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),
),
],
)

View File

@@ -1,4 +1,5 @@
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
from spirit.game.card_effects.pokemon import energy_mix, psychic_leap
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
card = PokemonCardDef(
@@ -23,14 +24,14 @@ card = PokemonCardDef(
title="Energy Mix",
game_text="Search your deck for an Energy card and attach it to 1 of your Fusion Strike Pok\u00e9mon. Then, shuffle your deck.",
cost={PokemonTypes.PSYCHIC: 1},
effect=unimplemented,
effect=energy_mix,
),
Attack(
title="Psychic Leap",
game_text="You may shuffle this Pok\u00e9mon and all attached cards into your deck.",
cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1},
damage=70,
effect=unimplemented,
effect=psychic_leap,
),
],
)

View File

@@ -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, opponent_prizes_taken_at_least
from spirit.game.card_effects.pokemon import energy_provides_type
from spirit.game.card_effects.support_common import attach_from_discard
def _is_psychic_energy(card):
return energy_provides_type(card, PokemonTypes.PSYCHIC.value)
card = PokemonCardDef(
guid="2577cc87-9e1b-5938-89f6-4dc23574caf9",
@@ -23,7 +30,10 @@ card = PokemonCardDef(
title="Psypump",
game_text="Attach up to 2 Psychic Energy cards from your discard pile to 1 of your Pok\u00e9mon.",
cost={PokemonTypes.COLORLESS: 1},
effect=unimplemented,
effect=attach_from_discard(
predicate=_is_psychic_energy, count=2, target="choice", minimum=0,
prompt="Choose up to 2 Psychic Energy cards from your discard pile to attach.",
),
),
Attack(
title="Limit Break",
@@ -31,7 +41,7 @@ card = PokemonCardDef(
cost={PokemonTypes.PSYCHIC: 2, PokemonTypes.COLORLESS: 1},
damage=90,
damage_operator="+",
effect=unimplemented,
effect=bonus_if(opponent_prizes_taken_at_least(3), 90),
),
],
)

View File

@@ -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="910d43ac-a842-55d1-81b5-f09de43ee5d8",
@@ -11,5 +23,9 @@ card = SupporterCardDef(
collector_number=136,
set_code="CZ",
rarity=Rarities.RareHolo,
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),
)

View File

@@ -1,5 +1,6 @@
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
from 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="fdac5939-1fcb-51c2-844a-581984efe448",
@@ -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),
),
],
)

View File

@@ -1,5 +1,36 @@
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, TRAINER_EFFECTS_BY_GUID, unimplemented
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
from spirit.game.session.effects import is_supporter_card
def _runnable_supporter(card):
if not is_supporter_card(card):
return False
effect = TRAINER_EFFECTS_BY_GUID.get((card.archetype_id or "").lower())
return effect is not None and effect is not unimplemented
def _primate_acting_condition(board, player_id, pokemon):
opponent_id = next(p for p in board.player_ids if p != player_id)
discard = board.find_player_area(opponent_id, "discard")
return bool(discard) and any(_runnable_supporter(c) for c in discard.children)
async def primate_acting(ctx):
"""Choose a Supporter card from the opponent's discard pile and use its
effect as the effect of this attack."""
candidates = [c for c in ctx.discard_pile(ctx.opponent_id) if _runnable_supporter(c)]
if not candidates:
return
picked = await ctx.choose_cards(
candidates, 1, minimum=1,
prompt="Choose a Supporter card from your opponent's discard pile",
)
if not picked:
return
effect = TRAINER_EFFECTS_BY_GUID[(picked[0].archetype_id or "").lower()]
await effect(ctx)
card = PokemonCardDef(
guid="eafa4d0e-eb5e-5eea-a370-7b74de785952",
@@ -22,7 +53,8 @@ card = PokemonCardDef(
title="Primate Acting",
game_text="Choose a Supporter card from your opponent's discard pile and use the effect of that card as the effect of this attack.",
cost={PokemonTypes.COLORLESS: 1},
effect=unimplemented,
condition=_primate_acting_condition,
effect=primate_acting,
),
Attack(
title="Hammer In",

View File

@@ -1,5 +1,6 @@
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
from 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="4ceae16b-44a9-5072-adaa-0b1ed27fef8a",
@@ -29,7 +30,7 @@ card = PokemonCardDef(
game_text="This Pok\u00e9mon also does 30 damage to itself.",
cost={PokemonTypes.DARKNESS: 2, PokemonTypes.COLORLESS: 1},
damage=160,
effect=unimplemented,
effect=recoil_attack(30),
),
],
)

View File

@@ -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(
@@ -21,10 +22,10 @@ card = PokemonCardDef(
abilities=[
Attack(
title="Reckless Charge",
game_text="This Pok\u00e9mon also does 10 damage to itself.",
game_text="This Pokémon also does 10 damage to itself.",
cost={PokemonTypes.METAL: 1},
damage=30,
effect=unimplemented,
effect=recoil_attack(10),
),
],
)
)

View File

@@ -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(
@@ -20,10 +21,10 @@ card = PokemonCardDef(
abilities=[
Attack(
title="Wild Charge",
game_text="This Pok\u00e9mon also does 30 damage to itself.",
game_text="This Pokémon also does 30 damage to itself.",
cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 1},
damage=90,
effect=unimplemented,
effect=recoil_attack(30),
),
],
)
)

View File

@@ -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="715f3cd8-7b81-5d2b-9884-283de7d5c1a3",
@@ -22,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.COLORLESS: 1},
effect=unimplemented,
effect=search_to_bench(count=2),
),
Attack(
title="Continuous Tumble",
@@ -30,7 +32,7 @@ card = PokemonCardDef(
cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 1},
damage=30,
damage_operator="+",
effect=unimplemented,
effect=flip_damage(until_tails=True, bonus_per_heads=30),
),
],
)

View File

@@ -1,15 +1,29 @@
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="d4644be3-57c5-546d-aeec-4cb076dd48ed",
key="CZ",
name="com.direwolfdigital.cake.data.archetypes.trainer.PokBall.Name",
display_name="Poké Ball",
searchable_by=["Pok\u00c3\u00a9 Ball", "Item"],
searchable_by=["Poké Ball", "Item"],
subtypes=["Item"],
collector_number=137,
set_code="CZ",
rarity=Rarities.Common,
effect=unimplemented
effect=poke_ball
)

View File

@@ -1,15 +1,33 @@
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="14cbcefb-fac6-56ca-8a73-9d93e81c7699",
key="CZ",
name="com.direwolfdigital.cake.data.archetypes.trainer.PokmonCatcher.Name",
display_name="Pokémon Catcher",
searchable_by=["Pok\u00c3\u00a9mon Catcher", "Item"],
searchable_by=["Pokémon Catcher", "Item"],
subtypes=["Item"],
collector_number=138,
set_code="CZ",
rarity=Rarities.Uncommon,
effect=unimplemented
effect=pokemon_catcher,
condition=opponent_has_bench
)

View File

@@ -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="0673acf4-d7f4-52c1-87cd-67e9eda9f4e1",
@@ -11,5 +12,6 @@ card = ItemCardDef(
collector_number=139,
set_code="CZ",
rarity=Rarities.Common,
effect=unimplemented
condition=requires_damaged_pokemon(),
effect=heal_item(30)
)

View File

@@ -1,4 +1,5 @@
from spirit.game.data_utils import SupporterCardDef, unimplemented
from spirit.game.card_effects.trainers import professors_research
from spirit.game.data_utils import SupporterCardDef
from spirit.game.attributes import Rarities
card = SupporterCardDef(
@@ -11,5 +12,5 @@ card = SupporterCardDef(
collector_number=150,
set_code="CZ",
rarity=Rarities.RareUltra,
effect=unimplemented
effect=professors_research
)

View File

@@ -1,5 +1,6 @@
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
from 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="5b63994a-3cb9-56f4-85d5-cb399a8373b6",
@@ -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),
),
],
)

View File

@@ -1,4 +1,5 @@
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
from spirit.game.card_effects.pokemon import ExcitedHeartPassive
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
card = PokemonCardDef(
@@ -20,15 +21,15 @@ card = PokemonCardDef(
abilities=[
Ability(
title="Excited Heart",
game_text="This Pok\u00e9mon's attacks cost Colorless less for each Prize card your opponent has taken.",
effect=unimplemented,
game_text="This Pokémon's attacks cost Colorless less for each Prize card your opponent has taken.",
passive=ExcitedHeartPassive(),
),
Attack(
title="Combustion Blast",
game_text="During your next turn, this Pok\u00e9mon can't use Combustion Blast.",
game_text="During your next turn, this Pokémon can't use Combustion Blast.",
cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 4},
damage=250,
effect=unimplemented,
locks_next_turn=True,
),
],
)
)

View File

@@ -1,5 +1,15 @@
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers, is_pokemon_v
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
from spirit.game.card_effects.attacks_common import snipe_attack
async def shocking_block(ctx):
"""Whenever any player attaches an Energy from hand to their Pokemon V, put 2 damage counters on it."""
receiver = ctx.energy_receiver
if receiver is None or not is_pokemon_v(receiver.archetype_id):
return
await ctx.deal_damage(20, target=receiver, apply_modifiers=False, as_counters=True)
card = PokemonCardDef(
guid="b6f25036-ac03-5190-ba5e-f4f448c4528c",
@@ -21,13 +31,14 @@ card = PokemonCardDef(
Ability(
title="Shocking Block",
game_text="Whenever any player attaches an Energy card from their hand to 1 of their Pok\u00e9mon V, put 2 damage counters on that Pok\u00e9mon.",
effect=unimplemented,
trigger=Triggers.ON_ENERGY_ATTACHED,
effect=shocking_block,
),
Attack(
title="Linear Attack",
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, side="opponent"),
),
],
)

View File

@@ -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.support_common import search_to_bench
from spirit.game.card_effects.trainers import is_pokemon_vmax
from spirit.game.session.effects import is_pokemon_card
def _is_vmax(card):
return is_pokemon_card(card) and is_pokemon_vmax(card.archetype_id)
async def climactic_gate(ctx):
"""You may search up to 2 Pokemon VMAX to the Bench; using it ends the turn."""
if not await ctx.ask_yes_no(
"Search your deck for up to 2 Pokémon VMAX and put them onto "
"your Bench? If you do, your turn ends."
):
return
await search_to_bench(predicate=_is_vmax, count=2)(ctx)
ctx.ends_turn = True
card = PokemonCardDef(
guid="d799e888-b9fa-5bb8-b022-acc672128675",
@@ -20,7 +39,8 @@ card = PokemonCardDef(
Ability(
title="Climactic Gate",
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 Pok\u00e9mon VMAX and put them onto your Bench. Then, shuffle your deck. If you use this Ability, your turn ends.",
effect=unimplemented,
trigger=Triggers.ON_PLAY,
effect=climactic_gate,
),
Attack(
title="Power Beam",

View File

@@ -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="cc0d13f8-938c-5a84-96d8-e9cc96b22bb1",
@@ -11,5 +27,10 @@ card = SupporterCardDef(
collector_number=140,
set_code="CZ",
rarity=Rarities.RareHolo,
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,
),
)

View File

@@ -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="963902d0-48a5-507a-ae11-816234a0cd76",
@@ -11,5 +56,6 @@ card = ItemCardDef(
collector_number=141,
set_code="CZ",
rarity=Rarities.Uncommon,
effect=unimplemented
effect=_rare_candy,
condition=_rare_candy_condition,
)

View File

@@ -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="c902b0f4-1b29-5b8a-a6f5-80d8b80e4639",
@@ -21,15 +55,16 @@ 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",
game_text="You may discard any amount of basic Fire Energy or any amount of 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 any amount of basic Fire Energy or any amount of 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=max_burst,
),
],
)
)

View File

@@ -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="42d2e5b0-e92a-5d96-9e6b-429164987df7",
@@ -21,15 +55,16 @@ 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",
game_text="You may discard any amount of basic Fire Energy or any amount of 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 any amount of basic Fire Energy or any amount of 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=max_burst,
),
],
)
)

View File

@@ -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="cab0d1d1-5756-59e7-8693-34e498b89845",
@@ -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,
),
],
)
)

View File

@@ -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 lock_all_attacks
from spirit.game.session.effects import full_stack
def _star_guardian_condition(board, player_id, pokemon):
opponent_id = next(p for p in board.player_ids if p != player_id)
prizes = board.find_player_area(opponent_id, "prizePile")
bench = board.find_player_area(opponent_id, "bench")
return bool(prizes) and len(prizes.children) == 1 and bool(bench and bench.children)
async def star_guardian(ctx):
"""VSTAR Power: if the opponent has exactly 1 Prize left, you may make
them discard 1 of their Benched Pokemon and all attached cards."""
if not await ctx.ask_yes_no(
"Choose 1 of your opponent's Benched Pokémon? They discard that "
"Pokémon and all attached cards."
):
return
target = await ctx.choose_pokemon(
ctx.opponent_bench(), "Choose 1 of your opponent's Benched Pokémon"
)
if target is None:
return
await ctx.discard_cards(full_stack(target))
async def giga_impact(ctx):
"""230. During your next turn, this Pokemon can't attack."""
await ctx.deal_damage()
lock_all_attacks(ctx, ctx.attacker)
card = PokemonCardDef(
guid="0e4aa482-8295-5b75-b012-daf58cb96397",
@@ -22,14 +54,16 @@ card = PokemonCardDef(
Ability(
title="Star Guardian",
game_text="During your turn, if your opponent has exactly 1 Prize card remaining, you may choose 1 of your opponent's Benched Pok\u00e9mon. They discard that Pok\u00e9mon and all attached cards. (You can't use more than 1 VSTAR Power in a game.)",
effect=unimplemented,
vstar=True,
condition=_star_guardian_condition,
effect=star_guardian,
),
Attack(
title="Giga Impact",
game_text="During your next turn, this Pok\u00e9mon can't attack.",
cost={PokemonTypes.COLORLESS: 3},
damage=230,
effect=unimplemented,
effect=giga_impact,
),
],
)

View File

@@ -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 damage_per, damage_counters_on
async def _confuse_self(ctx):
await ctx.apply_special_condition(ctx.attacker, SpecialConditions.CONFUSED)
card = PokemonCardDef(
guid="1da3abc5-6669-50ab-bbe9-e80900566bdc",
@@ -29,7 +35,8 @@ card = PokemonCardDef(
cost={PokemonTypes.COLORLESS: 3},
damage=100,
damage_operator="+",
effect=unimplemented,
effect=damage_per(damage_counters_on("self"), 10, base=100,
also=_confuse_self),
),
],
)

View File

@@ -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="0205ee20-a659-547a-9f16-f0eb96fe47d6",
@@ -11,5 +18,9 @@ card = ItemCardDef(
collector_number=142,
set_code="CZ",
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),
)

View File

@@ -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 invite_out(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, "Invite Out"))[0]
if not heads:
return
active = ctx.opponent_active()
bench = ctx.opponent_bench()
if active is None or not bench or ctx.effects_blocked(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="498a4795-0542-573f-96e5-54f951ec5e36",
key="CZ",
@@ -22,7 +39,7 @@ card = PokemonCardDef(
title="Invite Out",
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=invite_out,
),
Attack(
title="Smash Kick",

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