mirror of
https://github.com/Bratah123/Spirit-PTCGO.git
synced 2026-09-09 21:25:42 -05:00
FEAT!: implement all SWSH -> SIT format
This commit is contained in:
@@ -676,6 +676,21 @@ def discard_for_bonus(source: str = "hand", predicate=None, max_count: int = 1,
|
||||
return effect
|
||||
|
||||
|
||||
def smokescreen_attack(damage: Optional[int] = None, also=None):
|
||||
"""Printed (or damage) damage; "during your opponent's next turn, if the
|
||||
Defending Pokemon tries to attack, your opponent flips a coin. If tails,
|
||||
that attack doesn't happen" (Smokescreen / Sand Attack / Blinding Beam).
|
||||
The rider no-ops versus an attack-effect-shielded target."""
|
||||
async def effect(ctx):
|
||||
await ctx.deal_damage(damage)
|
||||
defender = ctx.defender
|
||||
if defender is not None and not ctx.effects_blocked(defender):
|
||||
ctx.require_attack_flip(defender)
|
||||
if also is not None:
|
||||
await also(ctx)
|
||||
return effect
|
||||
|
||||
|
||||
async def discard_random_from_hand(ctx, player_id: Optional[str] = None,
|
||||
count: int = 1) -> list:
|
||||
"""Discards `count` random cards from a player's hand (reveals via the
|
||||
|
||||
@@ -14,11 +14,12 @@ preds (no_retreat / ability_lock / healing_block / retreat_free) take
|
||||
|
||||
from typing import Any, Callable, Optional
|
||||
|
||||
from spirit.game.attributes import AttrID
|
||||
from spirit.game.attributes import AttrID, TrainerType
|
||||
from spirit.game.session.passives import (
|
||||
Passive,
|
||||
TurnDamageModifier,
|
||||
carrier_pokemon,
|
||||
effective_max_hp,
|
||||
)
|
||||
|
||||
|
||||
@@ -207,7 +208,7 @@ class RetreatDiscountPassive(Passive):
|
||||
self.amount = amount
|
||||
self.protects = _protects_pred(target_pred)
|
||||
|
||||
def modify_retreat_cost(self, cost, pokemon, carrier):
|
||||
def modify_retreat_cost(self, cost, pokemon, carrier, board):
|
||||
if not self.protects(pokemon, carrier):
|
||||
return cost
|
||||
return max(0, cost - self.amount)
|
||||
@@ -225,7 +226,7 @@ class RetreatFreeWhenPassive(Passive):
|
||||
def __init__(self, pred):
|
||||
self.pred = pred
|
||||
|
||||
def modify_retreat_cost(self, cost, pokemon, carrier):
|
||||
def modify_retreat_cost(self, cost, pokemon, carrier, board):
|
||||
return 0 if self.pred(pokemon, carrier) else cost
|
||||
|
||||
|
||||
@@ -435,6 +436,158 @@ def attack_effect_shield_passive(protects="carrier") -> Passive:
|
||||
return AttackEffectShieldPassive(protects)
|
||||
|
||||
|
||||
class FlipPreventDamagePassive(Passive):
|
||||
""""If any damage is done to this Pokemon by attacks, flip a coin. If
|
||||
heads, prevent that damage" (Infiltrator / Primate Dexterity)."""
|
||||
|
||||
def __init__(self, title="", protects="carrier"):
|
||||
self.title = title
|
||||
self.protects = _protects_pred(protects)
|
||||
|
||||
async def damage_interceptor(self, ctx, calc, target, carrier):
|
||||
if not (calc.is_attack and calc.is_opposing and calc.amount > 0):
|
||||
return None
|
||||
if not self.protects(target, carrier):
|
||||
return None
|
||||
heads = await ctx.flip_coins(1, self.title,
|
||||
source=carrier_pokemon(carrier) or carrier)
|
||||
return 0 if heads and heads[0] else None
|
||||
|
||||
|
||||
def flip_prevent_damage_passive(title="") -> Passive:
|
||||
"""Flip-to-prevent shield: heads prevents the whole attack hit; the flip
|
||||
choreographs inside the attack bracket (queued on the attack ctx)."""
|
||||
return FlipPreventDamagePassive(title)
|
||||
|
||||
|
||||
class GutsSurvivePassive(Passive):
|
||||
""""If this Pokemon would be Knocked Out by damage from an attack, [flip a
|
||||
coin -- if heads,] it is not Knocked Out and its remaining HP becomes
|
||||
`hp_floor`" (Guts; Sturdy with flip=False + require_full_hp=True)."""
|
||||
|
||||
def __init__(self, hp_floor=10, title="Guts", flip=True,
|
||||
require_full_hp=False, protects="carrier"):
|
||||
self.hp_floor = hp_floor
|
||||
self.title = title
|
||||
self.flip = flip
|
||||
self.require_full_hp = require_full_hp
|
||||
self.protects = _protects_pred(protects)
|
||||
|
||||
async def damage_interceptor(self, ctx, calc, target, carrier):
|
||||
if not (calc.is_attack and calc.is_opposing and calc.amount > 0):
|
||||
return None
|
||||
if not self.protects(target, carrier):
|
||||
return None
|
||||
current = target.get_attribute(AttrID.HP, 0)
|
||||
if calc.amount < current:
|
||||
return None # would not Knock Out
|
||||
if self.require_full_hp and current < effective_max_hp(calc.board, target):
|
||||
return None
|
||||
if self.flip:
|
||||
heads = await ctx.flip_coins(1, self.title,
|
||||
source=carrier_pokemon(carrier) or carrier)
|
||||
if not (heads and heads[0]):
|
||||
return None
|
||||
return max(0, current - self.hp_floor)
|
||||
|
||||
|
||||
def guts_survive_passive(hp_floor=10, title="Guts", flip=True,
|
||||
require_full_hp=False) -> Passive:
|
||||
"""KO-survive interceptor: only when the hit would KO; heads (or always
|
||||
with flip=False) rewrites the dealt amount so remaining HP = hp_floor."""
|
||||
return GutsSurvivePassive(hp_floor, title, flip, require_full_hp)
|
||||
|
||||
|
||||
class TrainerEffectShieldPassive(Passive):
|
||||
"""Shields the carrier's owner from opposing trainer-card effects
|
||||
(Dew Guard: "prevent all effects of that card done to you or your hand")."""
|
||||
|
||||
def __init__(self, supporters_only=True, protects=None, while_active=False,
|
||||
condition=None):
|
||||
self.supporters_only = supporters_only
|
||||
# protects(affected_entity_or_None, carrier): scopes the shield to
|
||||
# matching direct objects (None = the whole side, legacy behavior).
|
||||
self.protects = protects
|
||||
self.while_active = while_active
|
||||
self.condition = condition
|
||||
|
||||
def blocks_trainer_effects(self, affected_player_id, trainer_card,
|
||||
trainer_type, carrier, affected_entity=None,
|
||||
board=None):
|
||||
if self.supporters_only and trainer_type != TrainerType.SUPPORTER.value:
|
||||
return False
|
||||
if affected_player_id != carrier.owning_player_id:
|
||||
return False
|
||||
if self.while_active \
|
||||
and not is_in_active_spot(carrier_pokemon(carrier) or carrier):
|
||||
return False
|
||||
if self.condition is not None and not self.condition(board, carrier):
|
||||
return False
|
||||
if self.protects is not None:
|
||||
return bool(self.protects(affected_entity, carrier))
|
||||
return True
|
||||
|
||||
|
||||
def trainer_effect_shield_passive(supporters_only=True, protects=None,
|
||||
while_active=False, condition=None) -> Passive:
|
||||
"""Dew Guard shape; the engine already scopes it to effects a DIFFERENT
|
||||
player's trainer card does to the shielded side. protects/while_active/
|
||||
condition(board, carrier) narrow the shield (Princess's Curtain, Baffling)."""
|
||||
return TrainerEffectShieldPassive(supporters_only, protects, while_active,
|
||||
condition)
|
||||
|
||||
|
||||
class SupporterReplacementPassive(Passive):
|
||||
"""Opposing Supporter cards resolve `replacement` instead of their own
|
||||
effect (Shifty Substitution: "each Supporter card in your opponent's hand
|
||||
has the effect 'Draw 3 cards.'")."""
|
||||
|
||||
def __init__(self, replacement, opponents_only=True, while_active=True):
|
||||
self.replacement = replacement
|
||||
self.opponents_only = opponents_only
|
||||
self.while_active = while_active
|
||||
|
||||
def replace_supporter_effect(self, card, player_id, carrier):
|
||||
if self.opponents_only and player_id == carrier.owning_player_id:
|
||||
return None
|
||||
if self.while_active:
|
||||
holder = carrier_pokemon(carrier) or carrier
|
||||
if not is_in_active_spot(holder):
|
||||
return None
|
||||
return self.replacement
|
||||
|
||||
|
||||
def replace_opponent_supporters(replacement, while_active=True) -> Passive:
|
||||
"""Shifty Substitution shape: `replacement` is an async def effect(ctx)
|
||||
run as the opponent's Supporter effect (trainer ctx, shields apply)."""
|
||||
return SupporterReplacementPassive(replacement, while_active=while_active)
|
||||
|
||||
|
||||
class EnergyAttachTaxPassive(Passive):
|
||||
"""Opposing manual energy attaches flip a coin; tails discards the energy
|
||||
instead of attaching (Slimy Room; per text the turn attachment is NOT
|
||||
used up)."""
|
||||
|
||||
def __init__(self, opponents_only=True, while_active=True):
|
||||
self.opponents_only = opponents_only
|
||||
self.while_active = while_active
|
||||
|
||||
def taxes_energy_attach(self, attaching_player_id, energy, target, carrier):
|
||||
if self.opponents_only \
|
||||
and attaching_player_id == carrier.owning_player_id:
|
||||
return False
|
||||
if self.while_active:
|
||||
holder = carrier_pokemon(carrier) or carrier
|
||||
if not is_in_active_spot(holder):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def energy_attach_tax_passive(opponents_only=True, while_active=True) -> Passive:
|
||||
"""Slimy Room shape: taxes the opponent's manual attach with a coin flip."""
|
||||
return EnergyAttachTaxPassive(opponents_only, while_active)
|
||||
|
||||
|
||||
# ======================================================================
|
||||
# GROUP B -- temporary-shield effect factories (expiring temp passives)
|
||||
# ======================================================================
|
||||
@@ -602,7 +755,7 @@ class SelfRetreatCostRaisePassive(Passive):
|
||||
def __init__(self, extra):
|
||||
self.extra = extra
|
||||
|
||||
def modify_retreat_cost(self, cost, pokemon, carrier):
|
||||
def modify_retreat_cost(self, cost, pokemon, carrier, board):
|
||||
if carrier_pokemon(carrier) is not pokemon:
|
||||
return cost
|
||||
return cost + self.extra
|
||||
|
||||
@@ -9,19 +9,22 @@ from spirit.game.attributes import (
|
||||
SpecialConditions,
|
||||
)
|
||||
from spirit.game.data_utils import (
|
||||
ABILITIES_BY_ID, Ability, Attack, ability_id_for, def_for, is_pokemon_v,
|
||||
subtypes_for,
|
||||
ABILITIES_BY_ID, Ability, Attack, ability_id_for, def_for, has_rule_box,
|
||||
is_pokemon_v, subtypes_for,
|
||||
)
|
||||
from spirit.game.session.constants import BENCH_CAPACITY
|
||||
from spirit.game.session.effects import (
|
||||
full_stack,
|
||||
is_basic_pokemon,
|
||||
is_colorless_no_rule_box,
|
||||
is_pokemon_card,
|
||||
is_special_energy,
|
||||
is_supporter_card,
|
||||
is_trainer_card,
|
||||
)
|
||||
from spirit.game.session.passives import Passive, carrier_pokemon
|
||||
from spirit.game.session.passives import (
|
||||
Passive, carrier_pokemon, effective_bench_capacity,
|
||||
)
|
||||
from spirit.game.models.board import PokemonEntity
|
||||
|
||||
|
||||
@@ -40,7 +43,8 @@ class MysteriousNestPassive(Passive):
|
||||
|
||||
async def read_the_wind(ctx):
|
||||
"""Discard a card from your hand. If you do, draw 3 cards."""
|
||||
if await ctx.discard_from_hand(1, minimum=0, prompt="Discard a card from your hand"):
|
||||
# No "you may": with a card in hand the discard is mandatory.
|
||||
if await ctx.discard_from_hand(1, minimum=1, prompt="Discard a card from your hand"):
|
||||
await ctx.draw_cards(3)
|
||||
|
||||
|
||||
@@ -193,6 +197,26 @@ class ExcitedHeartPassive(Passive):
|
||||
return cost
|
||||
|
||||
|
||||
# --- Ditto (PGO): Sudden Transformation ------------------------------------
|
||||
|
||||
class SuddenTransformationPassive(Passive):
|
||||
"""May use the attacks of Basic non-Rule-Box Pokemon in the owner's
|
||||
discard pile (energy costs still apply)."""
|
||||
|
||||
def granted_attacks(self, board, pokemon, carrier):
|
||||
if carrier is not pokemon:
|
||||
return []
|
||||
discard = board.find_player_area(pokemon.owning_player_id, "discard")
|
||||
attacks = []
|
||||
for card in (discard.children if discard else []):
|
||||
if not is_basic_pokemon(card) or has_rule_box(card.archetype_id):
|
||||
continue
|
||||
for ability in getattr(def_for(card.archetype_id), "abilities", None) or []:
|
||||
if isinstance(ability, Attack):
|
||||
attacks.append(ability)
|
||||
return attacks
|
||||
|
||||
|
||||
# --- Raikou (VIV): Amazing Shot -------------------------------------------
|
||||
|
||||
async def amazing_shot(ctx):
|
||||
@@ -902,3 +926,239 @@ async def teraspark(ctx):
|
||||
ctx, ctx.opponent_bench(), 40,
|
||||
"Choose a Benched Pokémon to take 40 damage",
|
||||
)
|
||||
|
||||
|
||||
# --- Word of Ruin / Doom Curse (delayed Knock Out) ------------------------
|
||||
|
||||
async def delayed_knockout(ctx):
|
||||
"""At the end of your opponent's next turn, the Defending Pokemon will
|
||||
be Knocked Out (dropped if it leaves the Active spot or evolves)."""
|
||||
target = ctx.defender
|
||||
if target is None or ctx.effects_blocked(target):
|
||||
return
|
||||
ctx.visual_targets = [target.entity_id]
|
||||
owner_id = target.owning_player_id
|
||||
target_id = target.entity_id
|
||||
|
||||
def _guard(board):
|
||||
active = board.active_pokemon(owner_id) if owner_id else None
|
||||
return active is not None and active.entity_id == target_id
|
||||
|
||||
async def _fire(session):
|
||||
pokemon = session.board_state.get_entity(target_id)
|
||||
if isinstance(pokemon, PokemonEntity):
|
||||
await session._resolve_raw_knockout(pokemon)
|
||||
|
||||
ctx.schedule_at_checkup(1, _fire, guard=_guard)
|
||||
|
||||
|
||||
# --- Top Entry / Emergency Entry (bench straight from the turn draw) ------
|
||||
|
||||
def top_entry(draw_count=0):
|
||||
"""If drawn at the beginning of your turn and your Bench isn't full, you
|
||||
may put this Pokemon onto your Bench (Emergency Entry also draws)."""
|
||||
async def effect(ctx):
|
||||
source = ctx.source
|
||||
owner = source.owning_player_id or ctx.player_id
|
||||
bench = ctx.board.find_player_area(owner, "bench")
|
||||
if not bench \
|
||||
or len(bench.children) >= effective_bench_capacity(ctx.board, owner):
|
||||
return
|
||||
if not await ctx.ask_yes_no("Put this Pokémon onto your Bench?"):
|
||||
return
|
||||
if await ctx.bench_pokemon(source) and draw_count:
|
||||
await ctx.draw_cards(draw_count)
|
||||
return effect
|
||||
|
||||
|
||||
# --- Origin Forme Dialga VSTAR (BRS): Star Chronos ------------------------
|
||||
|
||||
async def star_chronos(ctx):
|
||||
"""220. Take another turn after this one. (Skip Pokemon Checkup.)"""
|
||||
await ctx.deal_damage()
|
||||
ctx.take_extra_turn()
|
||||
|
||||
|
||||
# --- Medicham V (EVS): Yoga Loop ------------------------------------------
|
||||
|
||||
def yoga_loop_condition(board, player_id, pokemon):
|
||||
"""Unusable if any of your Pokemon used Yoga Loop during your last turn."""
|
||||
prev = board.turn_state.attack_titles_prev_turn_by_player.get(player_id) or []
|
||||
return "Yoga Loop" not in prev
|
||||
|
||||
|
||||
async def yoga_loop(ctx):
|
||||
"""2 damage counters on 1 opposing Pokemon; a KO grants an extra turn."""
|
||||
target = await ctx.choose_pokemon(
|
||||
ctx.opponent_pokemon_in_play(), "Choose 1 of your opponent's Pokémon"
|
||||
)
|
||||
if target is None:
|
||||
return
|
||||
await ctx.deal_damage(20, target=target, as_counters=True)
|
||||
if target in ctx.knockouts:
|
||||
ctx.take_extra_turn()
|
||||
|
||||
|
||||
# --- Jumpluff (EVS): Fluffy Barrage ---------------------------------------
|
||||
|
||||
class FluffyBarragePassive(Passive):
|
||||
"""May attack twice each turn (the printed KO sentence is timing reminder
|
||||
text, not a condition): the first attack keeps the turn, and legal
|
||||
actions collapse to attacks-only afterwards, so the player either attacks
|
||||
again or clicks End Turn."""
|
||||
|
||||
def attack_keeps_turn(self, attacker, ability, ctx, carrier):
|
||||
if attacker is not carrier:
|
||||
return False
|
||||
uses = [e for e in ctx.session.turn_state.attacks_used
|
||||
if e[0] == carrier.entity_id]
|
||||
return len(uses) == 1
|
||||
|
||||
|
||||
# --- Devolution (Rewind Beam / Downgrading Beam / Curse of Devolution) ----
|
||||
|
||||
def devolvable(pokemon) -> bool:
|
||||
"""An evolved in-play Pokemon with its previous stage tucked underneath."""
|
||||
evolves_from = pokemon.get_attribute(AttrID.EVOLUTION_LOGIC_FROM)
|
||||
return bool(evolves_from) and any(
|
||||
isinstance(c, PokemonEntity)
|
||||
and c.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) == evolves_from
|
||||
for c in pokemon.children
|
||||
)
|
||||
|
||||
|
||||
def devolve_depth(pokemon) -> int:
|
||||
"""How many evolution cards can be peeled off the stack."""
|
||||
depth, current = 0, pokemon
|
||||
while True:
|
||||
evolves_from = current.get_attribute(AttrID.EVOLUTION_LOGIC_FROM)
|
||||
nxt = next(
|
||||
(c for c in current.children
|
||||
if isinstance(c, PokemonEntity)
|
||||
and c.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) == evolves_from),
|
||||
None,
|
||||
) if evolves_from else None
|
||||
if nxt is None:
|
||||
return depth
|
||||
depth, current = depth + 1, nxt
|
||||
|
||||
|
||||
async def rewind_beam(ctx):
|
||||
"""180. Devolve the opposing evolved Active: highest Stage card to hand."""
|
||||
await ctx.deal_damage()
|
||||
defender = ctx.defender
|
||||
if defender is not None and devolvable(defender) and not ctx.effects_blocked(defender):
|
||||
await ctx.devolve_pokemon(defender, steps=1, destination="hand")
|
||||
|
||||
|
||||
async def downgrading_beam(ctx):
|
||||
"""Devolve 1 opposing evolved Pokemon, removing any number of Evolution
|
||||
cards; the opponent shuffles them into their deck."""
|
||||
candidates = [p for p in ctx.opponent_pokemon_in_play()
|
||||
if devolvable(p) and not ctx.effects_blocked(p)]
|
||||
if not candidates:
|
||||
return
|
||||
target = await ctx.choose_pokemon(
|
||||
candidates, "Choose 1 of your opponent's evolved Pokémon"
|
||||
)
|
||||
if target is None:
|
||||
return
|
||||
owner_id = target.owning_player_id
|
||||
depth = devolve_depth(target)
|
||||
steps = 1
|
||||
if depth > 1:
|
||||
steps = 1 + await ctx.choose(
|
||||
"How many Evolution cards will you remove?",
|
||||
[str(n + 1) for n in range(depth)],
|
||||
)
|
||||
removed = await ctx.devolve_pokemon(target, steps=steps, destination="deck")
|
||||
if removed:
|
||||
await ctx.shuffle_deck(owner_id)
|
||||
|
||||
|
||||
async def curse_of_devolution(ctx):
|
||||
"""On evolve: you may devolve 1 opposing Benched evolved Pokemon."""
|
||||
candidates = [p for p in ctx.opponent_bench()
|
||||
if devolvable(p) and not ctx.effects_blocked(p)]
|
||||
if not candidates:
|
||||
return
|
||||
if not await ctx.ask_yes_no("Devolve 1 of your opponent's Benched Pokémon?"):
|
||||
return
|
||||
target = await ctx.choose_pokemon(
|
||||
candidates, "Choose 1 of your opponent's Benched evolved Pokémon"
|
||||
)
|
||||
if target is not None:
|
||||
await ctx.devolve_pokemon(target, steps=1, destination="hand")
|
||||
|
||||
|
||||
# --- Identity swaps (Stance Change / V Transformation / Phantom Transformation)
|
||||
|
||||
|
||||
def _same_name(entity, other) -> bool:
|
||||
return entity.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) \
|
||||
== other.get_attribute(AttrID.EVOLUTION_LOGIC_NAME)
|
||||
|
||||
|
||||
def stance_change_condition(board, player_id, pokemon):
|
||||
"""A same-named card (an Aegislash) sits in the hand."""
|
||||
hand = board.find_player_area(player_id, "hand")
|
||||
return bool(hand) and any(_same_name(c, pokemon) for c in hand.children)
|
||||
|
||||
|
||||
async def stance_change(ctx):
|
||||
"""Switch this Pokemon with an Aegislash in your hand; everything remains."""
|
||||
candidates = [c for c in ctx.hand() if _same_name(c, ctx.source)]
|
||||
picks = await ctx.choose_cards(
|
||||
candidates, 1, minimum=1,
|
||||
prompt="Choose an Aegislash to switch with this Pokémon",
|
||||
)
|
||||
if picks:
|
||||
await ctx.identity_swap(ctx.source, picks[0], destination="hand")
|
||||
|
||||
|
||||
def _is_basic_pokemon_v(card) -> bool:
|
||||
return is_basic_pokemon(card) and is_pokemon_v(card.archetype_id)
|
||||
|
||||
|
||||
def v_transformation_condition(board, player_id, pokemon):
|
||||
discard = board.find_player_area(player_id, "discard")
|
||||
return bool(discard) and any(_is_basic_pokemon_v(c) for c in discard.children)
|
||||
|
||||
|
||||
async def v_transformation(ctx):
|
||||
"""Switch this Pokemon with a Basic Pokemon V from the discard pile."""
|
||||
candidates = [c for c in ctx.discard_pile() if _is_basic_pokemon_v(c)]
|
||||
picks = await ctx.choose_cards(
|
||||
candidates, 1, minimum=1,
|
||||
prompt="Choose a Basic Pokémon V to switch with this Pokémon",
|
||||
)
|
||||
if picks:
|
||||
await ctx.identity_swap(ctx.source, picks[0], destination="discard")
|
||||
|
||||
|
||||
def _phantom_candidate(card, zoroark) -> bool:
|
||||
return (
|
||||
is_pokemon_card(card)
|
||||
and card.get_attribute(AttrID.STAGE) == PokemonStage.STAGE1.value
|
||||
and not _same_name(card, zoroark)
|
||||
)
|
||||
|
||||
|
||||
def phantom_transformation_condition(board, player_id, pokemon):
|
||||
discard = board.find_player_area(player_id, "discard")
|
||||
return bool(discard) and any(
|
||||
_phantom_candidate(c, pokemon) for c in discard.children)
|
||||
|
||||
|
||||
async def phantom_transformation(ctx):
|
||||
"""Discard this Pokemon and all attached cards; a Stage 1 (non-Zoroark)
|
||||
from the discard pile takes its place as a fresh Pokemon."""
|
||||
candidates = [c for c in ctx.discard_pile()
|
||||
if _phantom_candidate(c, ctx.source)]
|
||||
picks = await ctx.choose_cards(
|
||||
candidates, 1, minimum=1,
|
||||
prompt="Choose a Stage 1 Pokémon to put in this Pokémon's place",
|
||||
)
|
||||
if picks:
|
||||
await ctx.identity_swap(ctx.source, picks[0], destination="discard",
|
||||
transfer=False)
|
||||
|
||||
@@ -5,7 +5,8 @@ from spirit.game.attributes import (
|
||||
)
|
||||
from spirit.game.models.board import PokemonEntity
|
||||
from spirit.game.data_utils import (
|
||||
Ability, Activations, has_rule_box, is_pokemon_v, subtypes_for,
|
||||
Ability, Activations, Attack, def_for, has_rule_box, is_pokemon_v,
|
||||
subtypes_for,
|
||||
)
|
||||
from spirit.game.session.constants import BENCH_CAPACITY
|
||||
from spirit.game.session.effects import (
|
||||
@@ -22,7 +23,9 @@ from spirit.game.session.passives import (
|
||||
Passive,
|
||||
TurnDamageModifier,
|
||||
carrier_pokemon,
|
||||
effective_bench_capacity,
|
||||
effective_max_hp,
|
||||
trainer_play_blocked,
|
||||
)
|
||||
|
||||
|
||||
@@ -584,6 +587,39 @@ async def battle_vip_pass(ctx):
|
||||
await ctx.shuffle_deck()
|
||||
|
||||
|
||||
def dream_ball_playable(board, player_id):
|
||||
"""Dream Ball never plays from hand: its window is the prize-take prompt."""
|
||||
return False
|
||||
|
||||
|
||||
async def dream_ball(ctx):
|
||||
"""Search your deck for a Pokemon and put it onto your Bench."""
|
||||
picks = await ctx.search_deck(
|
||||
is_pokemon_card, count=1, minimum=0,
|
||||
prompt="Choose a Pokémon to put onto your Bench.",
|
||||
)
|
||||
for card in picks:
|
||||
await ctx.bench_pokemon(card)
|
||||
await ctx.shuffle_deck()
|
||||
|
||||
|
||||
async def dream_ball_prize_window(ctx):
|
||||
"""Taken as a face-down Prize: you may play it before it settles in hand."""
|
||||
ctx.suppress_announce = True
|
||||
board, pid = ctx.board, ctx.player_id
|
||||
bench = board.find_player_area(pid, "bench")
|
||||
if not bench or len(bench.children) >= effective_bench_capacity(board, pid):
|
||||
return
|
||||
if not ctx.deck(pid):
|
||||
return
|
||||
if trainer_play_blocked(board, pid, ctx.source):
|
||||
return
|
||||
if not await ctx.ask_yes_no("Play Dream Ball? Search your deck for a "
|
||||
"Pokémon and put it onto your Bench."):
|
||||
return
|
||||
await ctx.session._execute_play_trainer(pid, ctx.source)
|
||||
|
||||
|
||||
async def power_tablet(ctx):
|
||||
"""This turn, your Fusion Strike Pokemon's attacks do 30 more damage to
|
||||
the opponent's Active Pokemon (before Weakness and Resistance)."""
|
||||
@@ -681,6 +717,30 @@ class LostCityPassive(Passive):
|
||||
return "lostZone"
|
||||
|
||||
|
||||
class CollapsedStadiumPassive(Passive):
|
||||
"""Each player can't have more than 4 Benched Pokemon (excess is discarded
|
||||
by enforce_bench_capacity when this comes into play)."""
|
||||
|
||||
def bench_capacity(self, player_id, carrier):
|
||||
return 4
|
||||
|
||||
|
||||
async def gapejaw_bog_watch(ctx):
|
||||
"""Gapejaw Bog: 2 damage counters on any Basic just benched from hand."""
|
||||
pokemon = ctx.benched_pokemon
|
||||
if pokemon is None:
|
||||
return
|
||||
await ctx.deal_damage(20, target=pokemon, apply_modifiers=False,
|
||||
as_counters=True)
|
||||
|
||||
|
||||
class SkatersParkPassive(Passive):
|
||||
"""Basic Energy paid for either player's retreat goes to hand instead."""
|
||||
|
||||
def retreat_cost_destination(self, pokemon, energy, carrier):
|
||||
return "hand" if is_basic_energy_card(energy) else None
|
||||
|
||||
|
||||
# ======================================================================
|
||||
# Lost Zone Box + Regigigas decks
|
||||
# ======================================================================
|
||||
@@ -897,17 +957,56 @@ async def hisuian_heavy_ball(ctx):
|
||||
await ctx.look_at_prizes_take_basic()
|
||||
|
||||
|
||||
# --- Peonia (CRE, Supporter) ----------------------------------------------
|
||||
|
||||
def peonia_playable(board, player_id) -> bool:
|
||||
prizes = board.find_player_area(player_id, "prizePile")
|
||||
return bool(prizes and prizes.children)
|
||||
|
||||
|
||||
async def peonia(ctx):
|
||||
"""Put up to 3 Prize cards into your hand. Then, for each Prize card put
|
||||
into your hand this way, put a card from your hand face down as a Prize."""
|
||||
taken = await ctx.take_prizes(3, minimum=1, check_win=False)
|
||||
if not taken:
|
||||
return
|
||||
picks = await ctx.choose_cards(
|
||||
list(ctx.hand()), len(taken), minimum=len(taken),
|
||||
prompt=f"Choose {len(taken)} card(s) to put face down as Prize cards",
|
||||
)
|
||||
await ctx.put_in_prizes(picks)
|
||||
|
||||
|
||||
# --- Air Balloon (SSH, Pokemon Tool) -------------------------------------
|
||||
|
||||
class AirBalloonPassive(Passive):
|
||||
"""The Retreat Cost of the holder is [C][C] less."""
|
||||
|
||||
def modify_retreat_cost(self, cost, pokemon, carrier):
|
||||
def modify_retreat_cost(self, cost, pokemon, carrier, board):
|
||||
if carrier_pokemon(carrier) is pokemon:
|
||||
return cost - 2
|
||||
return cost
|
||||
|
||||
|
||||
# --- Memory Capsule (SWSH4) ------------------------------------------------
|
||||
|
||||
class MemoryCapsulePassive(Passive):
|
||||
"""The holder can use any attack from its tucked previous Evolutions
|
||||
(energy costs still apply)."""
|
||||
|
||||
def granted_attacks(self, board, pokemon, carrier):
|
||||
if carrier_pokemon(carrier) is not pokemon:
|
||||
return []
|
||||
attacks = []
|
||||
for child in pokemon.children:
|
||||
if not isinstance(child, PokemonEntity):
|
||||
continue
|
||||
for ability in getattr(def_for(child.archetype_id), "abilities", None) or []:
|
||||
if isinstance(ability, Attack):
|
||||
attacks.append(ability)
|
||||
return attacks
|
||||
|
||||
|
||||
# --- Training Court (RCL, Stadium) ---------------------------------------
|
||||
|
||||
def training_court_condition(board, player_id, stadium):
|
||||
@@ -927,6 +1026,44 @@ async def training_court(ctx):
|
||||
await ctx.put_in_hand(picks, reveal=False)
|
||||
|
||||
|
||||
# --- Thorton (SWSH11) -------------------------------------------------------
|
||||
|
||||
def _basic_pokemon_in_play(board, player_id):
|
||||
"""In-play Basics; fossils count (Basic Pokemon in play, Trainer CARD_TYPE)."""
|
||||
from spirit.game.attributes import AttrID, PokemonStage
|
||||
from spirit.game.models.board import PokemonEntity
|
||||
return [
|
||||
p for p in board.pokemon_in_play(player_id)
|
||||
if isinstance(p, PokemonEntity)
|
||||
and p.get_attribute(AttrID.STAGE) == PokemonStage.BASIC.value
|
||||
]
|
||||
|
||||
|
||||
def thorton_condition(board, player_id):
|
||||
if not any(is_basic_pokemon(c) for c in _discard(board, player_id)):
|
||||
return False
|
||||
return bool(_basic_pokemon_in_play(board, player_id))
|
||||
|
||||
|
||||
async def thorton(ctx):
|
||||
"""Choose a Basic Pokemon in your discard pile and switch it with 1 of
|
||||
your Basic Pokemon in play; everything remains on the new Pokemon."""
|
||||
candidates = [c for c in ctx.discard_pile() if is_basic_pokemon(c)]
|
||||
in_play = _basic_pokemon_in_play(ctx.board, ctx.player_id)
|
||||
if not candidates or not in_play:
|
||||
return
|
||||
picks = await ctx.choose_cards(
|
||||
candidates, 1, minimum=1,
|
||||
prompt="Choose a Basic Pokémon from your discard pile",
|
||||
)
|
||||
if not picks:
|
||||
return
|
||||
target = await ctx.choose_pokemon(
|
||||
in_play, "Choose 1 of your Basic Pokémon in play to switch it with")
|
||||
if target is not None:
|
||||
await ctx.identity_swap(target, picks[0], destination="discard")
|
||||
|
||||
|
||||
TRAINING_COURT_ABILITY = Ability(
|
||||
title="Training Court",
|
||||
game_text="Once during each player's turn, that player may put a basic Energy card from their discard pile into their hand.",
|
||||
@@ -934,3 +1071,80 @@ TRAINING_COURT_ABILITY = Ability(
|
||||
effect=training_court,
|
||||
condition=training_court_condition,
|
||||
)
|
||||
|
||||
|
||||
# --- Fossils (Unidentified Fossil / Rare Fossil) ---------------------------
|
||||
|
||||
class FossilBodyPassive(Passive):
|
||||
"""Fossil rules text: this card can't retreat; Rare Fossil additionally
|
||||
can't be affected by Special Conditions."""
|
||||
|
||||
def __init__(self, blocks_conditions: bool = False):
|
||||
self.condition_immune = blocks_conditions
|
||||
|
||||
def blocks_retreat(self, pokemon, carrier):
|
||||
return pokemon is carrier
|
||||
|
||||
def blocks_special_conditions(self, target, condition, carrier):
|
||||
return self.condition_immune and target is carrier
|
||||
|
||||
|
||||
async def fossil_discard(ctx):
|
||||
"""Discard this fossil from play; attached cards go to the discard too."""
|
||||
fossil = ctx.source
|
||||
was_active = fossil is ctx.my_active()
|
||||
discard = ctx.board.find_player_area(ctx.player_id, "discard")
|
||||
if discard is not None and discard.entity_id not in ctx.visual_targets:
|
||||
ctx.visual_targets.append(discard.entity_id)
|
||||
await ctx.discard_cards([c for c in full_stack(fossil) if c is not fossil])
|
||||
await ctx.discard_cards([fossil])
|
||||
if was_active:
|
||||
async def _promote():
|
||||
if not await ctx.session._promote_new_active(ctx.player_id):
|
||||
screen_name = ctx.session.players[ctx.player_id].screen_name
|
||||
await ctx.session.end_game(
|
||||
ctx.opponent_id, f"{screen_name} has no Pokémon left"
|
||||
)
|
||||
ctx.deferred_actions.append(_promote)
|
||||
|
||||
|
||||
def fossil_discard_ability() -> Ability:
|
||||
""""At any time during your turn, you may discard this card from play."
|
||||
Fresh instance per print: ability_id derives from the owning card GUID."""
|
||||
return Ability(
|
||||
"Discard",
|
||||
"At any time during your turn, you may discard this card from play.",
|
||||
activation=Activations.ONCE_PER_TURN,
|
||||
effect=fossil_discard,
|
||||
)
|
||||
|
||||
|
||||
def is_rare_fossil(card) -> bool:
|
||||
return getattr(def_for(card.archetype_id), "display_name", None) == "Rare Fossil"
|
||||
|
||||
|
||||
def bench_has_room(board, player_id):
|
||||
bench = board.find_player_area(player_id, "bench")
|
||||
return bench is not None \
|
||||
and len(bench.children) < effective_bench_capacity(board, player_id)
|
||||
|
||||
|
||||
def fossil_search(fossil_predicate, count: int = 2,
|
||||
label: str = "Rare Fossil"):
|
||||
"""Search your deck for up to `count` matching fossil cards and put them
|
||||
onto your Bench; then shuffle (Relicanth's Fossil Search, Cara Liss)."""
|
||||
async def effect(ctx):
|
||||
bench = ctx.board.find_player_area(ctx.player_id, "bench")
|
||||
space = effective_bench_capacity(ctx.board, ctx.player_id) \
|
||||
- len(bench.children) if bench else 0
|
||||
if space <= 0:
|
||||
return
|
||||
picks = await ctx.search_deck(
|
||||
fossil_predicate, count=min(count, space), minimum=0,
|
||||
prompt=f"Choose up to {min(count, space)} {label} cards to put "
|
||||
f"onto your Bench.",
|
||||
)
|
||||
for card in picks:
|
||||
await ctx.bench_pokemon(card)
|
||||
await ctx.shuffle_deck()
|
||||
return effect
|
||||
|
||||
@@ -123,6 +123,25 @@ class Triggers:
|
||||
# BEFORE the KO'd stack moves (energies still attached); ctx carries
|
||||
# ko_pokemon / ko_from_attack / ko_attacker.
|
||||
ON_ALLY_KNOCKED_OUT = "on_ally_knocked_out"
|
||||
# Either player manually put a Basic from hand onto their Bench (Gapejaw
|
||||
# Bog); ctx carries benching_player_id / benched_pokemon.
|
||||
ON_POKEMON_BENCHED = "on_pokemon_benched"
|
||||
# This card was drawn by the beginning-of-turn draw (Lombre "Top Entry");
|
||||
# fires after the card lands in hand (approximation of "before you put it
|
||||
# into your hand").
|
||||
ON_TURN_DRAWN = "on_turn_drawn"
|
||||
# This card was just taken as a face-down Prize into hand (Dream Ball).
|
||||
ON_TAKEN_AS_PRIZE = "on_taken_as_prize"
|
||||
# Another of the owner's Pokemon evolved via a hand-played evolution card
|
||||
# (Eevee "Resonant Evolution"); ctx carries evolved_pokemon / evolved_from.
|
||||
ON_ALLY_EVOLVED = "on_ally_evolved"
|
||||
# Fires for each of the turn player's in-play Pokemon after their turn
|
||||
# ends, before the checkup (Radiant Venusaur "Sunny Bloom").
|
||||
END_OF_TURN = "end_of_turn"
|
||||
# This card was discarded from its owner's hand by an OPPOSING player's
|
||||
# effect (Amoonguss "Surprise Spores"); fires via the acting ctx's
|
||||
# deferred_actions, after that effect's choreography flushes.
|
||||
ON_DISCARDED_FROM_HAND = "on_discarded_from_hand"
|
||||
|
||||
|
||||
class Activations:
|
||||
@@ -241,7 +260,8 @@ class Attack(Ability):
|
||||
vstar: bool = False,
|
||||
locks_next_turn: bool = False,
|
||||
condition: Optional[Callable] = None,
|
||||
usable_first_turn: bool = False
|
||||
usable_first_turn: bool = False,
|
||||
usable_despite_conditions: bool = False
|
||||
):
|
||||
super().__init__(title, game_text, ability_type, effect, vstar=vstar,
|
||||
condition=condition)
|
||||
@@ -252,6 +272,8 @@ class Attack(Ability):
|
||||
self.locks_next_turn = locks_next_turn
|
||||
# Exempt from the "going first can't attack on turn 1" rule (Indeedee).
|
||||
self.usable_first_turn = usable_first_turn
|
||||
# Offered even while the user is Asleep/Paralyzed (Windup Arm-style).
|
||||
self.usable_despite_conditions = usable_despite_conditions
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
d = super().to_dict()
|
||||
@@ -431,7 +453,8 @@ class TrainerCardDef(CardDefinition):
|
||||
the `unimplemented` marker, or None for cards with no effect scripted.
|
||||
|
||||
`condition(board, player_id) -> bool` gates when the card may be played
|
||||
at all (e.g. Ultra Ball needs 2 other cards in hand to discard).
|
||||
at all (e.g. Ultra Ball needs 2 other cards in hand to discard); a
|
||||
3-arg `condition(board, player_id, card)` also gets the hand copy (Nugget).
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
@@ -444,6 +467,7 @@ class TrainerCardDef(CardDefinition):
|
||||
trainer_type: TrainerType,
|
||||
effect: Optional[Any] = None,
|
||||
condition: Optional[Callable] = None,
|
||||
abilities: Optional[List[Ability]] = None,
|
||||
display_name: Optional[str] = None,
|
||||
searchable_by: Optional[List[str]] = None,
|
||||
subtypes: Optional[List[str]] = None,
|
||||
@@ -452,6 +476,13 @@ class TrainerCardDef(CardDefinition):
|
||||
super().__init__(guid, key, name, collector_number, set_code, rarity, display_name, searchable_by, subtypes, attributes)
|
||||
self.effect = effect
|
||||
self.condition = condition
|
||||
# Trainers have no PIE_ABILITIES slot; declared abilities register for
|
||||
# the session's trigger scans only (Dream Ball's ON_TAKEN_AS_PRIZE).
|
||||
self.abilities: List[Ability] = abilities or []
|
||||
for idx, a in enumerate(self.abilities):
|
||||
if not a.ability_id:
|
||||
a.ability_id = ability_id_for(guid, idx + 100) # slot 0 = StadiumCardDef.ability
|
||||
ABILITIES_BY_ID[a.ability_id] = a
|
||||
if effect is not None:
|
||||
TRAINER_EFFECTS_BY_GUID[guid.lower()] = effect
|
||||
self.extra_attributes.update({
|
||||
@@ -464,6 +495,33 @@ class ItemCardDef(TrainerCardDef):
|
||||
kwargs['trainer_type'] = TrainerType.ITEM
|
||||
super().__init__(**kwargs)
|
||||
|
||||
class FossilItemCardDef(ItemCardDef):
|
||||
"""Item played as if it were a Basic Colorless Pokemon (fossils).
|
||||
|
||||
The archetype stays a Trainer-Item (real PTCGO shape: deck-builder
|
||||
grouping, Item locks, mulligan/setup exclusion); plays_as_pokemon makes
|
||||
the board entity a PokemonEntity so it benches, evolves, takes damage and
|
||||
offers its PIE abilities like a Basic while in play.
|
||||
"""
|
||||
plays_as_pokemon = True
|
||||
|
||||
def __init__(self, hp: int, passive: Optional[Any] = None, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.passive = passive
|
||||
self.extra_attributes.update({
|
||||
str(AttrID.HP.value): {"type": "int", "value": hp},
|
||||
str(AttrID.STAGE.value): {"type": "int", "value": PokemonStage.BASIC.value},
|
||||
str(AttrID.POKEMON_TYPES.value): {
|
||||
"type": "json", "value": json.dumps([PokemonTypes.COLORLESS.value])
|
||||
},
|
||||
str(AttrID.RETREAT_COST.value): {"type": "int", "value": 0},
|
||||
str(AttrID.PIE_ABILITIES.value): {
|
||||
"type": "json",
|
||||
"value": json.dumps([a.to_dict() for a in self.abilities]),
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
class SupporterCardDef(TrainerCardDef):
|
||||
def __init__(self, **kwargs):
|
||||
kwargs['trainer_type'] = TrainerType.SUPPORTER
|
||||
|
||||
@@ -256,6 +256,10 @@ def create_card_entity(card_obj: Card, owning_player_id: Optional[str] = None, e
|
||||
elif c_type == CardType.ENERGY.value:
|
||||
return EnergyEntity(card_obj, owning_player_id, entity_id)
|
||||
else:
|
||||
from spirit.game.data_utils import def_for # circular-import guard
|
||||
if getattr(def_for(card_obj.guid), "plays_as_pokemon", False):
|
||||
# Fossils: Trainer archetype, Pokemon entity on the board.
|
||||
return PokemonEntity(card_obj, owning_player_id, entity_id)
|
||||
return TrainerEntity(card_obj, owning_player_id, entity_id)
|
||||
|
||||
|
||||
@@ -452,8 +456,11 @@ class BoardState:
|
||||
|
||||
@staticmethod
|
||||
def _is_basic_pokemon(entity: BoardEntity) -> bool:
|
||||
# Fossils (Trainer archetypes played as Pokemon) don't count for
|
||||
# mulligans or setup placement.
|
||||
return (
|
||||
isinstance(entity, PokemonEntity)
|
||||
and entity.card_obj.get_attribute_value(AttrID.CARD_TYPE) == CardType.POKEMON.value
|
||||
and entity.card_obj.get_attribute_value(AttrID.STAGE) == PokemonStage.BASIC.value
|
||||
)
|
||||
|
||||
@@ -643,9 +650,10 @@ class BoardState:
|
||||
continue
|
||||
bench.children.sort(key=self.bench_slot_of)
|
||||
occupied = {self.bench_slot_of(c) for c in bench.children}
|
||||
slots = bench.get_attribute(AttrID.AREA_SLOTS) or BENCH_SLOT_COUNT
|
||||
bench.set_attribute(
|
||||
AttrID.AREA_EMPTY_SLOTS,
|
||||
[s for s in range(BENCH_SLOT_COUNT) if s not in occupied],
|
||||
[s for s in range(slots) if s not in occupied],
|
||||
)
|
||||
|
||||
def serialize(self, viewer_id: Optional[str] = None) -> Dict[str, Any]:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.card_effects.passives_common import flip_protection
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
|
||||
card = PokemonCardDef(
|
||||
@@ -24,7 +25,7 @@ card = PokemonCardDef(
|
||||
title="Star Protection",
|
||||
game_text="Flip a coin. If heads, during your opponent's next turn, prevent all damage done to this Pok\u00e9mon by attacks.",
|
||||
cost={PokemonTypes.COLORLESS: 1},
|
||||
effect=unimplemented,
|
||||
effect=flip_protection(prevent=True),
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,4 +1,5 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.card_effects.passives_common import flip_protection
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
|
||||
card = PokemonCardDef(
|
||||
@@ -23,7 +24,7 @@ card = PokemonCardDef(
|
||||
title="Star Protection",
|
||||
game_text="Flip a coin. If heads, during your opponent's next turn, prevent all damage done to this Pok\u00e9mon by attacks.",
|
||||
cost={PokemonTypes.COLORLESS: 1},
|
||||
effect=unimplemented,
|
||||
effect=flip_protection(prevent=True),
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,7 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.support_common import recover_from_discard, requires_discard
|
||||
from spirit.game.card_effects.attacks_common import damage_per, count_energy
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="03fc082d-f894-5f17-96af-f266f3ae3d82",
|
||||
@@ -23,7 +25,8 @@ card = PokemonCardDef(
|
||||
title="Temporal Backflow",
|
||||
game_text="Put a card from your discard pile into your hand.",
|
||||
cost={PokemonTypes.METAL: 1},
|
||||
effect=unimplemented,
|
||||
condition=requires_discard(),
|
||||
effect=recover_from_discard(count=1, minimum=1, reveal=False, to="hand"),
|
||||
),
|
||||
Attack(
|
||||
title="Metal Blast",
|
||||
@@ -31,7 +34,7 @@ card = PokemonCardDef(
|
||||
cost={PokemonTypes.COLORLESS: 3},
|
||||
damage=60,
|
||||
damage_operator="+",
|
||||
effect=unimplemented,
|
||||
effect=damage_per(count_energy("self", energy_type=PokemonTypes.METAL), 20, base=60),
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,22 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID
|
||||
from spirit.game.card_effects.passives_common import prevent_damage_when
|
||||
|
||||
|
||||
def _max_balloon_shield(calc, carrier):
|
||||
if calc.target is not carrier:
|
||||
return False
|
||||
attacker = calc.attacker
|
||||
return attacker is not None and \
|
||||
attacker.get_attribute(AttrID.STAGE) == PokemonStage.BASIC.value
|
||||
|
||||
|
||||
async def _max_balloon(ctx):
|
||||
await ctx.deal_damage()
|
||||
ctx.add_passive_through_opponents_turn(
|
||||
ctx.attacker, prevent_damage_when(_max_balloon_shield)
|
||||
)
|
||||
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="a3f7a335-9942-5930-9395-5f6d4d26754e",
|
||||
@@ -25,7 +42,7 @@ card = PokemonCardDef(
|
||||
game_text="During your opponent's next turn, prevent all damage done to this Pok\u00e9mon by attacks from Basic Pok\u00e9mon.",
|
||||
cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 2},
|
||||
damage=160,
|
||||
effect=unimplemented,
|
||||
effect=_max_balloon,
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,13 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions
|
||||
from spirit.game.card_effects.attacks_common import condition_attack, flip_or_nothing
|
||||
from spirit.game.card_effects.passives_common import apply_protection
|
||||
|
||||
|
||||
async def _fly_success(ctx):
|
||||
await ctx.deal_damage()
|
||||
await apply_protection(ctx, prevent=True, effects_too=True)
|
||||
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="f13a80b5-f37a-54ae-8277-1ed8c8cc6e15",
|
||||
@@ -21,17 +29,17 @@ card = PokemonCardDef(
|
||||
abilities=[
|
||||
Attack(
|
||||
title="Thunder Shock",
|
||||
game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.",
|
||||
game_text="Flip a coin. If heads, your opponent's Active Pokémon is now Paralyzed.",
|
||||
cost={PokemonTypes.LIGHTNING: 1},
|
||||
damage=20,
|
||||
effect=unimplemented,
|
||||
effect=condition_attack(SpecialConditions.PARALYZED, flip=True),
|
||||
),
|
||||
Attack(
|
||||
title="Fly",
|
||||
game_text="Flip a coin. If tails, this attack does nothing. If heads, during your opponent's next turn, prevent all damage from and effects of attacks done to this Pok\u00e9mon.",
|
||||
game_text="Flip a coin. If tails, this attack does nothing. If heads, during your opponent's next turn, prevent all damage from and effects of attacks done to this Pokémon.",
|
||||
cost={PokemonTypes.COLORLESS: 3},
|
||||
damage=120,
|
||||
effect=unimplemented,
|
||||
effect=flip_or_nothing(then=_fly_success),
|
||||
),
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.attacks_common import mill_scaled_damage
|
||||
from spirit.game.card_effects.support_common import is_energy
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="9affb786-a38e-5429-84b8-37ebec853940",
|
||||
@@ -24,7 +26,7 @@ card = PokemonCardDef(
|
||||
cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 1},
|
||||
damage=80,
|
||||
damage_operator="x",
|
||||
effect=unimplemented,
|
||||
effect=mill_scaled_damage(5, 80, pred=is_energy),
|
||||
),
|
||||
Attack(
|
||||
title="Massive Rend",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.attacks_common import self_energy_discard_attack, snipe_attack
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="24eeb34e-c276-5fec-b384-33e4d9670532",
|
||||
@@ -23,14 +24,14 @@ card = PokemonCardDef(
|
||||
title="Sacred Fire",
|
||||
game_text="This attack does 50 damage to 1 of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)",
|
||||
cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 1},
|
||||
effect=unimplemented,
|
||||
effect=snipe_attack(50, pool="any", count=1),
|
||||
),
|
||||
Attack(
|
||||
title="Fire Blast",
|
||||
game_text="Discard an Energy from this Pok\u00e9mon.",
|
||||
cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1},
|
||||
damage=120,
|
||||
effect=unimplemented,
|
||||
effect=self_energy_discard_attack(count=1),
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,26 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.pokemon import is_energy_card
|
||||
|
||||
|
||||
async def aqua_storm(ctx):
|
||||
"""Discard the top 5 of your deck; 50 damage per Energy card discarded
|
||||
this way to each of 2 chosen opposing Benched Pokémon (no W/R)."""
|
||||
milled = ctx.deck_top(5)
|
||||
await ctx.discard_cards(milled)
|
||||
energy_count = sum(1 for c in milled if is_energy_card(c))
|
||||
bench = ctx.opponent_bench()
|
||||
if not bench:
|
||||
return
|
||||
targets = await ctx.choose_cards(
|
||||
bench, 2, prompt="Choose 2 of your opponent's Benched Pokémon",
|
||||
)
|
||||
if energy_count <= 0:
|
||||
return
|
||||
amount = 50 * energy_count
|
||||
for target in targets:
|
||||
await ctx.deal_damage(amount, target=target, apply_modifiers=False)
|
||||
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="b6921130-84f9-5a4b-b273-47fba72812f9",
|
||||
@@ -22,7 +43,7 @@ card = PokemonCardDef(
|
||||
title="Aqua Storm",
|
||||
game_text="Discard the top 5 cards of your deck, and then choose 2 of your opponent's Benched Pok\u00e9mon. This attack does 50 damage for each Energy card you discarded in this way to each of those Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)",
|
||||
cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1},
|
||||
effect=unimplemented,
|
||||
effect=aqua_storm,
|
||||
),
|
||||
Attack(
|
||||
title="Surf",
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.attacks_common import damage_per, lock_all_attacks
|
||||
from spirit.game.session.legal_actions import energy_provided_count
|
||||
|
||||
|
||||
def _both_actives_energy(ctx):
|
||||
total = 0
|
||||
for pokemon in (ctx.my_active(), ctx.opponent_active()):
|
||||
if pokemon is None:
|
||||
continue
|
||||
for energy in ctx.attached_energies(pokemon):
|
||||
total += energy_provided_count(energy)
|
||||
return total
|
||||
|
||||
|
||||
async def _deep_crush(ctx):
|
||||
await ctx.deal_damage()
|
||||
lock_all_attacks(ctx, ctx.attacker)
|
||||
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="2b73e21f-77dd-5462-8012-bea5eb1a13f1",
|
||||
@@ -25,14 +43,14 @@ card = PokemonCardDef(
|
||||
cost={PokemonTypes.COLORLESS: 2},
|
||||
damage=20,
|
||||
damage_operator="x",
|
||||
effect=unimplemented,
|
||||
effect=damage_per(_both_actives_energy, 20),
|
||||
),
|
||||
Attack(
|
||||
title="Deep Crush",
|
||||
game_text="During your next turn, this Pok\u00e9mon can't attack.",
|
||||
cost={PokemonTypes.COLORLESS: 4},
|
||||
damage=160,
|
||||
effect=unimplemented,
|
||||
effect=_deep_crush,
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,14 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID
|
||||
from spirit.game.card_effects.attacks_common import snipe_attack
|
||||
|
||||
|
||||
async def lunar_pain(ctx):
|
||||
"""Double the number of damage counters on each of your opponent's Pokemon."""
|
||||
for pokemon in ctx.opponent_pokemon_in_play():
|
||||
current = (ctx.max_hp(pokemon) - pokemon.get_attribute(AttrID.HP, 0)) // 10
|
||||
if current > 0:
|
||||
await ctx.deal_damage(current * 10, target=pokemon, as_counters=True)
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="4fd81eeb-9609-5689-b7c6-1c7892dd1f28",
|
||||
@@ -24,14 +33,14 @@ card = PokemonCardDef(
|
||||
title="Lunar Pain",
|
||||
game_text="Double the number of damage counters on each of your opponent's Pok\u00e9mon.",
|
||||
cost={PokemonTypes.COLORLESS: 2},
|
||||
effect=unimplemented,
|
||||
effect=lunar_pain,
|
||||
),
|
||||
Attack(
|
||||
title="Psychic Shot",
|
||||
game_text="This attack also does 30 damage to 1 of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)",
|
||||
cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 2},
|
||||
damage=130,
|
||||
effect=unimplemented,
|
||||
effect=snipe_attack(30, also_base=True),
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,26 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.session.effects import is_item_card
|
||||
from spirit.game.card_effects.pokemon import in_active_spot
|
||||
|
||||
|
||||
async def mysterious_tail(ctx):
|
||||
"""You may look at the top 6 cards of your deck, reveal an Item card you
|
||||
find there, and put it into your hand. Shuffle the rest back."""
|
||||
if not await ctx.ask_yes_no("Look at the top 6 cards of your deck?"):
|
||||
return
|
||||
top = ctx.deck_top(6)
|
||||
candidates = [c for c in top if is_item_card(c)]
|
||||
picks = []
|
||||
if candidates:
|
||||
picks = await ctx.choose_cards(
|
||||
candidates, 1, minimum=0,
|
||||
prompt="Choose an Item card to put into your hand.",
|
||||
display_cards=top,
|
||||
)
|
||||
await ctx.put_in_hand(picks, reveal=True)
|
||||
await ctx.shuffle_deck()
|
||||
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="637c92b7-bbaa-5279-b62d-0dc204ee4e6c",
|
||||
@@ -22,7 +43,9 @@ card = PokemonCardDef(
|
||||
Ability(
|
||||
title="Mysterious Tail",
|
||||
game_text="Once during your turn, if this Pok\u00e9mon is in the Active Spot, you may look at the top 6 cards of your deck, reveal an Item card you find there, and put it into your hand. Shuffle the other cards back into your deck.",
|
||||
effect=unimplemented,
|
||||
activation=Activations.ONCE_PER_TURN,
|
||||
condition=in_active_spot,
|
||||
effect=mysterious_tail,
|
||||
),
|
||||
Attack(
|
||||
title="Psyshot",
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.session.effects import is_item_card
|
||||
from spirit.game.card_effects.pokemon import in_active_spot
|
||||
|
||||
|
||||
async def mysterious_tail(ctx):
|
||||
"""You may look at the top 6 cards of your deck, reveal an Item card you
|
||||
find there, and put it into your hand. Shuffle the rest back."""
|
||||
if not await ctx.ask_yes_no("Look at the top 6 cards of your deck?"):
|
||||
return
|
||||
top = ctx.deck_top(6)
|
||||
candidates = [c for c in top if is_item_card(c)]
|
||||
picks = []
|
||||
if candidates:
|
||||
picks = await ctx.choose_cards(
|
||||
candidates, 1, minimum=0,
|
||||
prompt="Choose an Item card to put into your hand.",
|
||||
display_cards=top,
|
||||
)
|
||||
await ctx.put_in_hand(picks, reveal=True)
|
||||
await ctx.shuffle_deck()
|
||||
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="37a0065c-8bb7-5076-aa62-100bb1c1896e",
|
||||
@@ -22,7 +43,9 @@ card = PokemonCardDef(
|
||||
Ability(
|
||||
title="Mysterious Tail",
|
||||
game_text="Once during your turn, if this Pok\u00e9mon is in the Active Spot, you may look at the top 6 cards of your deck, reveal an Item card you find there, and put it into your hand. Shuffle the other cards back into your deck.",
|
||||
effect=unimplemented,
|
||||
activation=Activations.ONCE_PER_TURN,
|
||||
condition=in_active_spot,
|
||||
effect=mysterious_tail,
|
||||
),
|
||||
Attack(
|
||||
title="Psyshot",
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID, TrainerType
|
||||
from spirit.game.session.passives import Passive
|
||||
from spirit.game.card_effects.passives_common import is_in_active_spot, boost_own_next_turn
|
||||
|
||||
|
||||
class AbsoluteSpacePassive(Passive):
|
||||
def blocks_trainer_play(self, card, player_id, carrier):
|
||||
if player_id == carrier.owning_player_id:
|
||||
return False
|
||||
if not is_in_active_spot(carrier):
|
||||
return False
|
||||
return card.get_attribute(AttrID.TRAINER_TYPE) == TrainerType.STADIUM.value
|
||||
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="70959369-220f-5338-bdef-cfebe8044717",
|
||||
@@ -21,7 +33,7 @@ card = PokemonCardDef(
|
||||
Ability(
|
||||
title="Absolute Space",
|
||||
game_text="As long as this Pok\u00e9mon is in the Active Spot, your opponent can't play any Stadium cards from their hand.",
|
||||
effect=unimplemented,
|
||||
passive=AbsoluteSpacePassive(),
|
||||
),
|
||||
Attack(
|
||||
title="Overdrive Smash",
|
||||
@@ -29,7 +41,7 @@ card = PokemonCardDef(
|
||||
cost={PokemonTypes.WATER: 1, PokemonTypes.COLORLESS: 2},
|
||||
damage=80,
|
||||
damage_operator="+",
|
||||
effect=unimplemented,
|
||||
effect=boost_own_next_turn(80, attack_title="Overdrive Smash"),
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,6 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.attacks_common import flip_damage
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="3966cba0-7b69-5294-a19d-ed9721139381",
|
||||
@@ -28,7 +29,7 @@ card = PokemonCardDef(
|
||||
game_text="Flip a coin. If tails, this Pok\u00e9mon also does 10 damage to itself.",
|
||||
cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 1},
|
||||
damage=30,
|
||||
effect=unimplemented,
|
||||
effect=flip_damage(tails_self_damage=10),
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,6 @@
|
||||
from spirit.game.data_utils import SupporterCardDef, unimplemented
|
||||
from spirit.game.data_utils import SupporterCardDef
|
||||
from spirit.game.attributes import Rarities
|
||||
from spirit.game.card_effects.trainers import professors_research
|
||||
|
||||
card = SupporterCardDef(
|
||||
guid="86d26aad-0a8b-5aa8-bec2-82c4ec9104a2",
|
||||
@@ -11,5 +12,5 @@ card = SupporterCardDef(
|
||||
collector_number=23,
|
||||
set_code="CEL25",
|
||||
rarity=Rarities.RareHolo,
|
||||
effect=unimplemented
|
||||
effect=professors_research
|
||||
)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.attacks_common import spread_damage, bonus_if, named_in_play
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="1134864a-2e23-5bd8-ae2b-16b9f2c992d9",
|
||||
@@ -22,7 +23,7 @@ card = PokemonCardDef(
|
||||
title="Scorching Wind",
|
||||
game_text="This attack does 20 damage to each of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)",
|
||||
cost={PokemonTypes.COLORLESS: 2},
|
||||
effect=unimplemented,
|
||||
effect=spread_damage(20, side="opponent"),
|
||||
),
|
||||
Attack(
|
||||
title="Black Flame",
|
||||
@@ -30,7 +31,7 @@ card = PokemonCardDef(
|
||||
cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1},
|
||||
damage=80,
|
||||
damage_operator="+",
|
||||
effect=unimplemented,
|
||||
effect=bonus_if(named_in_play("Zekrom"), 80),
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,36 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.trainers import is_basic_energy_card
|
||||
|
||||
|
||||
def rush_in_condition(board, player_id, pokemon):
|
||||
return pokemon is not board.active_pokemon(player_id)
|
||||
|
||||
|
||||
async def rush_in(ctx):
|
||||
"""Once during your turn, if this Pokémon is on your Bench, you may
|
||||
switch it with your Active Pokémon."""
|
||||
await ctx.switch_active(ctx.player_id, ctx.source)
|
||||
|
||||
|
||||
async def solar_geyser(ctx):
|
||||
"""Printed damage, then attach up to 2 basic Energy cards from your
|
||||
discard pile to 1 of your Benched Pokémon."""
|
||||
await ctx.deal_damage()
|
||||
cards = [c for c in ctx.discard_pile() if is_basic_energy_card(c)]
|
||||
bench = ctx.my_bench()
|
||||
if not cards or not bench:
|
||||
return
|
||||
picks = await ctx.choose_cards(
|
||||
cards, 2, minimum=1,
|
||||
prompt="Choose up to 2 basic Energy cards from your discard pile to attach",
|
||||
)
|
||||
if not picks:
|
||||
return
|
||||
target = await ctx.choose_pokemon(bench, "Choose a Benched Pokémon") or bench[0]
|
||||
for card in picks:
|
||||
await ctx.attach_energy(card, target)
|
||||
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="9f4f4d4d-f28f-532a-a1da-09ec3186a1a5",
|
||||
@@ -23,14 +54,16 @@ card = PokemonCardDef(
|
||||
Ability(
|
||||
title="Rush In",
|
||||
game_text="Once during your turn, if this Pok\u00e9mon is on your Bench, you may switch it with your Active Pok\u00e9mon.",
|
||||
effect=unimplemented,
|
||||
activation=Activations.ONCE_PER_TURN,
|
||||
condition=rush_in_condition,
|
||||
effect=rush_in,
|
||||
),
|
||||
Attack(
|
||||
title="Solar Geyser",
|
||||
game_text="Attach up to 2 basic Energy cards from your discard pile to 1 of your Benched Pok\u00e9mon.",
|
||||
cost={PokemonTypes.COLORLESS: 2},
|
||||
damage=100,
|
||||
effect=unimplemented,
|
||||
effect=solar_geyser,
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,6 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.attacks_common import spread_damage
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="fa92a5b1-0306-59dd-b692-cdebfc9985c9",
|
||||
@@ -24,7 +25,7 @@ card = PokemonCardDef(
|
||||
game_text="This attack also does 30 damage to each of your opponent's Benched Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)",
|
||||
cost={PokemonTypes.WATER: 3},
|
||||
damage=160,
|
||||
effect=unimplemented,
|
||||
effect=spread_damage(30, side="opponent", also_base=True),
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,45 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import (
|
||||
AttrID, CLIENT_POKEMON_TYPE_NAMES, PokemonTypes, PokemonStage, Rarities,
|
||||
)
|
||||
from spirit.game.card_effects.trainers import is_basic_energy_card
|
||||
|
||||
|
||||
async def breath_of_life(ctx):
|
||||
"""Search up to 3 basic Energy of different types and attach them to your
|
||||
Pokémon in any way you like. Then, shuffle your deck."""
|
||||
deck_cards = list(ctx.deck(ctx.player_id))
|
||||
reps = []
|
||||
labels = {}
|
||||
seen_types = []
|
||||
for card in deck_cards:
|
||||
if not is_basic_energy_card(card):
|
||||
continue
|
||||
types = card.get_attribute(AttrID.POKEMON_TYPES) or []
|
||||
if not types or types[0] in seen_types:
|
||||
continue
|
||||
seen_types.append(types[0])
|
||||
reps.append(card)
|
||||
labels[card.entity_id] = f"{CLIENT_POKEMON_TYPE_NAMES[PokemonTypes(types[0])]} Energy"
|
||||
|
||||
if not reps:
|
||||
await ctx.shuffle_deck()
|
||||
return
|
||||
|
||||
picks = await ctx.choose_cards(
|
||||
reps, 3, minimum=0,
|
||||
prompt="Choose up to 3 basic Energy cards of different types.",
|
||||
display_cards=deck_cards,
|
||||
)
|
||||
for energy in picks:
|
||||
label = labels[energy.entity_id]
|
||||
target = await ctx.choose_pokemon(
|
||||
ctx.my_pokemon_in_play(), f"Choose a Pokémon to attach {label} to"
|
||||
)
|
||||
if target is not None:
|
||||
await ctx.attach_energy(energy, target)
|
||||
await ctx.shuffle_deck()
|
||||
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="bf180f7a-320b-507f-80e6-dede7ace3b13",
|
||||
@@ -22,7 +62,7 @@ card = PokemonCardDef(
|
||||
title="Breath of Life",
|
||||
game_text="Search your deck for up to 3 basic Energy cards of different types and attach them to your Pok\u00e9mon in any way you like. Then, shuffle your deck.",
|
||||
cost={PokemonTypes.COLORLESS: 2},
|
||||
effect=unimplemented,
|
||||
effect=breath_of_life,
|
||||
),
|
||||
Attack(
|
||||
title="Aurora Horns",
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.session.effects import is_special_energy
|
||||
|
||||
|
||||
async def cry_of_destruction(ctx):
|
||||
"""Discard up to 3 Special Energy from your opponent's Pokemon."""
|
||||
energies = []
|
||||
for pokemon in ctx.opponent_pokemon_in_play():
|
||||
if ctx.effects_blocked(pokemon):
|
||||
continue
|
||||
energies.extend(e for e in ctx.attached_energies(pokemon) if is_special_energy(e))
|
||||
if not energies:
|
||||
return
|
||||
picks = await ctx.choose_cards(
|
||||
energies, min(3, len(energies)), minimum=0,
|
||||
prompt="Discard up to 3 Special Energy from your opponent's Pokémon.",
|
||||
)
|
||||
await ctx.discard_cards(picks)
|
||||
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="904e53b8-7d9f-56b2-9258-ddd922439997",
|
||||
@@ -23,7 +41,7 @@ card = PokemonCardDef(
|
||||
title="Cry of Destruction",
|
||||
game_text="Discard up to 3 Special Energy from your opponent's Pok\u00e9mon.",
|
||||
cost={PokemonTypes.COLORLESS: 2},
|
||||
effect=unimplemented,
|
||||
effect=cry_of_destruction,
|
||||
),
|
||||
Attack(
|
||||
title="Dark Feather",
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations
|
||||
from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.pokemon import is_energy_card
|
||||
from spirit.game.card_effects.support_common import search_attach_energy
|
||||
from spirit.game.card_effects.attacks_common import damage_per, count_energy
|
||||
|
||||
|
||||
def _is_psychic_energy_card(card):
|
||||
types = card.get_attribute(AttrID.POKEMON_TYPES) or []
|
||||
return is_energy_card(card) and PokemonTypes.PSYCHIC.value in types
|
||||
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="a61f827e-fa94-56e2-9dab-cf53202828b1",
|
||||
@@ -21,7 +30,9 @@ card = PokemonCardDef(
|
||||
Ability(
|
||||
title="Roar of the Sword",
|
||||
game_text="Once during your turn, you may search your deck for a Psychic Energy card and attach it to 1 of your Pok\u00e9mon. Then, shuffle your deck. If you use this Ability, your turn ends.",
|
||||
effect=unimplemented,
|
||||
activation=Activations.ONCE_PER_TURN,
|
||||
ends_turn=True,
|
||||
effect=search_attach_energy(predicate=_is_psychic_energy_card, count=1, distribute=False),
|
||||
),
|
||||
Attack(
|
||||
title="Storm Slash",
|
||||
@@ -29,7 +40,7 @@ card = PokemonCardDef(
|
||||
cost={PokemonTypes.COLORLESS: 3},
|
||||
damage=60,
|
||||
damage_operator="+",
|
||||
effect=unimplemented,
|
||||
effect=damage_per(count_energy("self", energy_type=PokemonTypes.PSYCHIC), 30, base=60),
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,13 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID
|
||||
from spirit.game.card_effects.passives_common import takes_less_passive
|
||||
from spirit.game.card_effects.pokemon import is_pokemon_vmax
|
||||
|
||||
_is_fighting = lambda p: PokemonTypes.FIGHTING.value in (p.get_attribute(AttrID.POKEMON_TYPES) or [])
|
||||
_protects_fighting_team = lambda target, carrier: (
|
||||
target.owning_player_id == carrier.owning_player_id and _is_fighting(target)
|
||||
)
|
||||
_attacker_is_vmax = lambda attacker: is_pokemon_vmax(attacker.archetype_id)
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="f4b957a8-0f26-5aeb-b968-294fdf0bee7e",
|
||||
@@ -21,7 +29,10 @@ card = PokemonCardDef(
|
||||
Ability(
|
||||
title="Growl of the Shield",
|
||||
game_text="All of your Fighting Pok\u00e9mon take 20 less damage from attacks from your opponent's Pok\u00e9mon VMAX (after applying Weakness and Resistance). You can't apply more than 1 Growl of the Shield Ability at a time.",
|
||||
effect=unimplemented,
|
||||
passive=takes_less_passive(
|
||||
20, protects=_protects_fighting_team,
|
||||
attacker_pred=_attacker_is_vmax, stack_key="GrowlOfTheShield",
|
||||
),
|
||||
),
|
||||
Attack(
|
||||
title="Heavy Impact",
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.attacks_common import bonus_if, named_in_play
|
||||
|
||||
|
||||
async def field_crush(ctx):
|
||||
"""30 damage; if your opponent has a Stadium in play, discard it."""
|
||||
await ctx.deal_damage()
|
||||
stadium = ctx.stadium_in_play()
|
||||
if stadium is not None and stadium.owning_player_id == ctx.opponent_id:
|
||||
await ctx.discard_stadium()
|
||||
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="09b5c42b-c34c-54dc-a807-f4049b33d6dc",
|
||||
@@ -23,7 +33,7 @@ card = PokemonCardDef(
|
||||
game_text="If your opponent has a Stadium in play, discard it.",
|
||||
cost={PokemonTypes.COLORLESS: 2},
|
||||
damage=30,
|
||||
effect=unimplemented,
|
||||
effect=field_crush,
|
||||
),
|
||||
Attack(
|
||||
title="White Thunder",
|
||||
@@ -31,7 +41,7 @@ card = PokemonCardDef(
|
||||
cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 1},
|
||||
damage=80,
|
||||
damage_operator="+",
|
||||
effect=unimplemented,
|
||||
effect=bonus_if(named_in_play("Reshiram"), 80),
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,6 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.passives_common import flip_protection
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="3e3bc7ff-4b50-57ff-9cd1-fbdfd61ee1fc",
|
||||
@@ -22,7 +23,7 @@ card = PokemonCardDef(
|
||||
title="Bustle",
|
||||
game_text="Flip a coin. If heads, during your opponent's next turn, prevent all damage from and effects of attacks done to this Pok\u00e9mon.",
|
||||
cost={PokemonTypes.COLORLESS: 1},
|
||||
effect=unimplemented,
|
||||
effect=flip_protection(prevent=True, effects_too=True),
|
||||
),
|
||||
Attack(
|
||||
title="Slap",
|
||||
|
||||
@@ -1,5 +1,27 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.attacks_common import count_energy, flip_or_nothing
|
||||
from spirit.game.card_effects.support_common import search_attach_energy
|
||||
from spirit.game.card_effects.trainers import is_grass_energy_card
|
||||
|
||||
_grass_attached = count_energy("self", energy_type=PokemonTypes.GRASS)
|
||||
|
||||
growing_tall = flip_or_nothing(then=search_attach_energy(
|
||||
is_grass_energy_card, count=5,
|
||||
prompt="Choose up to 5 Grass Energy cards to attach to your Pokémon.",
|
||||
))
|
||||
|
||||
|
||||
async def head_swing(ctx):
|
||||
"""30 to 1 opposing Pokemon per Grass Energy attached (no W/R on Bench)."""
|
||||
amount = 30 * _grass_attached(ctx)
|
||||
if amount <= 0:
|
||||
return
|
||||
target = await ctx.choose_pokemon(
|
||||
ctx.opponent_pokemon_in_play(), "Choose 1 of your opponent's Pokémon"
|
||||
)
|
||||
if target is not None:
|
||||
await ctx.deal_damage(amount, target=target)
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="010594a7-0853-5833-933f-009a6aea83f6",
|
||||
@@ -22,13 +44,13 @@ card = PokemonCardDef(
|
||||
title="Growing Tall",
|
||||
game_text="Flip a coin. If heads, search your deck for up to 5 Grass Energy cards and attach them to your Pok\u00e9mon in any way you like. Then, shuffle your deck.",
|
||||
cost={PokemonTypes.GRASS: 1},
|
||||
effect=unimplemented,
|
||||
effect=growing_tall,
|
||||
),
|
||||
Attack(
|
||||
title="Head Swing",
|
||||
game_text="This attack does 30 damage to 1 of your opponent's Pok\u00e9mon for each Grass Energy attached to this Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)",
|
||||
cost={PokemonTypes.COLORLESS: 3},
|
||||
effect=unimplemented,
|
||||
effect=head_swing,
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,27 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.attacks_common import count_energy, flip_or_nothing
|
||||
from spirit.game.card_effects.support_common import search_attach_energy
|
||||
from spirit.game.card_effects.trainers import is_grass_energy_card
|
||||
|
||||
_grass_attached = count_energy("self", energy_type=PokemonTypes.GRASS)
|
||||
|
||||
growing_tall = flip_or_nothing(then=search_attach_energy(
|
||||
is_grass_energy_card, count=5,
|
||||
prompt="Choose up to 5 Grass Energy cards to attach to your Pokémon.",
|
||||
))
|
||||
|
||||
|
||||
async def head_swing(ctx):
|
||||
"""30 to 1 opposing Pokemon per Grass Energy attached (no W/R on Bench)."""
|
||||
amount = 30 * _grass_attached(ctx)
|
||||
if amount <= 0:
|
||||
return
|
||||
target = await ctx.choose_pokemon(
|
||||
ctx.opponent_pokemon_in_play(), "Choose 1 of your opponent's Pokémon"
|
||||
)
|
||||
if target is not None:
|
||||
await ctx.deal_damage(amount, target=target)
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="4076648a-3fa3-560e-8533-22af1c518294",
|
||||
@@ -22,13 +44,13 @@ card = PokemonCardDef(
|
||||
title="Growing Tall",
|
||||
game_text="Flip a coin. If heads, search your deck for up to 5 Grass Energy cards and attach them to your Pok\u00e9mon in any way you like. Then, shuffle your deck.",
|
||||
cost={PokemonTypes.GRASS: 1},
|
||||
effect=unimplemented,
|
||||
effect=growing_tall,
|
||||
),
|
||||
Attack(
|
||||
title="Head Swing",
|
||||
game_text="This attack does 30 damage to 1 of your opponent's Pok\u00e9mon for each Grass Energy attached to this Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)",
|
||||
cost={PokemonTypes.COLORLESS: 3},
|
||||
effect=unimplemented,
|
||||
effect=head_swing,
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,17 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.support_common import search_to_hand
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities
|
||||
|
||||
|
||||
async def super_fang(ctx):
|
||||
"""Damage counters on the opponent's Active until its remaining HP is 10."""
|
||||
target = ctx.defender
|
||||
if target is None:
|
||||
return
|
||||
hp = target.get_attribute(AttrID.HP, 0)
|
||||
if hp > 10:
|
||||
await ctx.deal_damage(hp - 10, target=target, as_counters=True)
|
||||
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="2d7be742-d405-5c3c-befb-5ef93a4db3ea",
|
||||
@@ -23,13 +35,16 @@ card = PokemonCardDef(
|
||||
title="Chase Up",
|
||||
game_text="Search your deck for a card and put it into your hand. Then, shuffle your deck.",
|
||||
cost={PokemonTypes.DARKNESS: 1},
|
||||
effect=unimplemented,
|
||||
effect=search_to_hand(
|
||||
None, count=1, reveal=False,
|
||||
prompt="Choose a card to put into your hand.",
|
||||
),
|
||||
),
|
||||
Attack(
|
||||
title="Super Fang",
|
||||
game_text="Put damage counters on your opponent's Active Pok\u00e9mon until its remaining HP is 10.",
|
||||
game_text="Put damage counters on your opponent's Active Pokémon until its remaining HP is 10.",
|
||||
cost={PokemonTypes.COLORLESS: 3},
|
||||
effect=unimplemented,
|
||||
effect=super_fang,
|
||||
),
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.attacks_common import flip_or_nothing
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="5f958813-ec88-5319-99da-07def6d246ce",
|
||||
@@ -23,7 +24,7 @@ card = PokemonCardDef(
|
||||
game_text="Flip a coin. If tails, this attack does nothing.",
|
||||
cost={PokemonTypes.COLORLESS: 2},
|
||||
damage=50,
|
||||
effect=unimplemented,
|
||||
effect=flip_or_nothing(),
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,7 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.attacks_common import count_energy, flip_damage
|
||||
from spirit.game.card_effects.passives_common import flip_prevent_damage_passive
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="822d008a-f144-56e1-ae7e-3779ccfd515c",
|
||||
@@ -22,7 +24,7 @@ card = PokemonCardDef(
|
||||
Ability(
|
||||
title="Primate Dexterity",
|
||||
game_text="If any damage is done to this Pok\u00e9mon by attacks, flip a coin. If heads, prevent that damage.",
|
||||
effect=unimplemented,
|
||||
passive=flip_prevent_damage_passive("Primate Dexterity"),
|
||||
),
|
||||
Attack(
|
||||
title="Full Tilt Fling",
|
||||
@@ -30,7 +32,7 @@ card = PokemonCardDef(
|
||||
cost={PokemonTypes.COLORLESS: 1},
|
||||
damage=60,
|
||||
damage_operator="x",
|
||||
effect=unimplemented,
|
||||
effect=flip_damage(coins_from=count_energy("self"), per_heads=60),
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,7 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions
|
||||
from spirit.game.card_effects.attacks_common import condition_attack
|
||||
from spirit.game.card_effects.support_common import heal_attack
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="f26bde5e-3a31-57e4-9922-90109bcac0ab",
|
||||
@@ -24,14 +26,15 @@ card = PokemonCardDef(
|
||||
game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed and Poisoned.",
|
||||
cost={PokemonTypes.COLORLESS: 1},
|
||||
damage=20,
|
||||
effect=unimplemented,
|
||||
effect=condition_attack(
|
||||
SpecialConditions.PARALYZED, SpecialConditions.POISONED, flip=True),
|
||||
),
|
||||
Attack(
|
||||
title="Absorb",
|
||||
game_text="Heal 50 damage from this Pok\u00e9mon.",
|
||||
cost={PokemonTypes.GRASS: 1, PokemonTypes.COLORLESS: 1},
|
||||
damage=50,
|
||||
effect=unimplemented,
|
||||
effect=heal_attack(amount=50),
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,14 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, def_for
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.passives_common import team_damage_boost_passive
|
||||
from spirit.game.session.effects import is_basic_pokemon, is_water_pokemon
|
||||
|
||||
|
||||
def _ice_symbol_attacker(pokemon):
|
||||
if not (is_basic_pokemon(pokemon) and is_water_pokemon(pokemon)):
|
||||
return False
|
||||
definition = def_for(pokemon.archetype_id)
|
||||
return getattr(definition, "display_name", "") != "Articuno"
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="cde48857-1d2a-5303-8d56-23de5aba41b2",
|
||||
@@ -21,7 +30,7 @@ card = PokemonCardDef(
|
||||
Ability(
|
||||
title="Ice Symbol",
|
||||
game_text="Your Basic Water Pok\u00e9mon's attacks, except any Articuno, do 10 more damage to your opponent's Active Pok\u00e9mon (before applying Weakness and Resistance).",
|
||||
effect=unimplemented,
|
||||
passive=team_damage_boost_passive(10, attacker_pred=_ice_symbol_attacker),
|
||||
),
|
||||
Attack(
|
||||
title="Freezing Wind",
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID
|
||||
from spirit.game.card_effects.passives_common import Passive, carrier_pokemon, is_in_active_spot
|
||||
|
||||
|
||||
class ReassuringDamPassive(Passive):
|
||||
def blocks_discard(self, card, carrier):
|
||||
pokemon = carrier_pokemon(carrier)
|
||||
if pokemon is None or is_in_active_spot(pokemon):
|
||||
return False
|
||||
if card.owning_player_id != pokemon.owning_player_id:
|
||||
return False
|
||||
parent = getattr(card, "parent", None)
|
||||
return bool(parent) and parent.get_attribute(AttrID.NAME) == "deck"
|
||||
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="b836a69f-4559-5d3c-aba3-bdd242fdcf57",
|
||||
@@ -21,8 +34,8 @@ card = PokemonCardDef(
|
||||
abilities=[
|
||||
Ability(
|
||||
title="Reassuring Dam",
|
||||
game_text="As long as this Pok\u00e9mon is on your Bench, cards in your deck can't be discarded by effects of your opponent's attacks, Abilities, Item cards, or Supporter cards.",
|
||||
effect=unimplemented,
|
||||
game_text="As long as this Pokémon is on your Bench, cards in your deck can't be discarded by effects of your opponent's attacks, Abilities, Item cards, or Supporter cards.",
|
||||
passive=ReassuringDamPassive(),
|
||||
),
|
||||
Attack(
|
||||
title="Hammer In",
|
||||
@@ -30,4 +43,4 @@ card = PokemonCardDef(
|
||||
damage=80,
|
||||
),
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.attacks_common import recoil_attack
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="f1d171c0-781c-56dc-a3af-4701a785ac44",
|
||||
@@ -23,7 +24,7 @@ card = PokemonCardDef(
|
||||
game_text="This Pok\u00e9mon also does 10 damage to itself.",
|
||||
cost={PokemonTypes.COLORLESS: 3},
|
||||
damage=50,
|
||||
effect=unimplemented,
|
||||
effect=recoil_attack(10),
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,36 @@
|
||||
from spirit.game.data_utils import SupporterCardDef, unimplemented
|
||||
from spirit.game.attributes import Rarities
|
||||
from spirit.game.data_utils import SupporterCardDef
|
||||
from spirit.game.attributes import AttrID, PokemonTypes, Rarities
|
||||
from spirit.game.card_effects.trainers import is_energy_card
|
||||
|
||||
|
||||
def _is_water_energy_card(card) -> bool:
|
||||
types = card.get_attribute(AttrID.POKEMON_TYPES) or []
|
||||
return is_energy_card(card) and PokemonTypes.WATER.value in types
|
||||
|
||||
|
||||
async def blanche(ctx):
|
||||
"""Draw 2. If you drew any, flip a coin; heads attaches a Water Energy from your discard to a Benched Pokemon."""
|
||||
drawn = await ctx.draw_cards(2)
|
||||
if not drawn:
|
||||
return
|
||||
flips = await ctx.flip_coins(1, "Blanche")
|
||||
if not flips[0]:
|
||||
return
|
||||
bench = ctx.my_bench()
|
||||
if not bench:
|
||||
return
|
||||
energies = [c for c in ctx.discard_pile() if _is_water_energy_card(c)]
|
||||
if not energies:
|
||||
return
|
||||
picks = await ctx.choose_cards(
|
||||
energies, 1, minimum=1,
|
||||
prompt="Choose a Water Energy card to attach",
|
||||
)
|
||||
if not picks:
|
||||
return
|
||||
target = await ctx.choose_pokemon(bench, "Choose a Benched Pokémon") or bench[0]
|
||||
await ctx.attach_energy(picks[0], target)
|
||||
|
||||
|
||||
card = SupporterCardDef(
|
||||
guid="b89d3384-9761-5044-b628-1bf669a74f9b",
|
||||
@@ -11,5 +42,5 @@ card = SupporterCardDef(
|
||||
collector_number=64,
|
||||
set_code="PGO",
|
||||
rarity=Rarities.Uncommon,
|
||||
effect=unimplemented
|
||||
effect=blanche,
|
||||
)
|
||||
|
||||
@@ -1,5 +1,36 @@
|
||||
from spirit.game.data_utils import SupporterCardDef, unimplemented
|
||||
from spirit.game.attributes import Rarities
|
||||
from spirit.game.data_utils import SupporterCardDef
|
||||
from spirit.game.attributes import AttrID, PokemonTypes, Rarities
|
||||
from spirit.game.card_effects.trainers import is_energy_card
|
||||
|
||||
|
||||
def _is_water_energy_card(card) -> bool:
|
||||
types = card.get_attribute(AttrID.POKEMON_TYPES) or []
|
||||
return is_energy_card(card) and PokemonTypes.WATER.value in types
|
||||
|
||||
|
||||
async def blanche(ctx):
|
||||
"""Draw 2. If you drew any, flip a coin; heads attaches a Water Energy from your discard to a Benched Pokemon."""
|
||||
drawn = await ctx.draw_cards(2)
|
||||
if not drawn:
|
||||
return
|
||||
flips = await ctx.flip_coins(1, "Blanche")
|
||||
if not flips[0]:
|
||||
return
|
||||
bench = ctx.my_bench()
|
||||
if not bench:
|
||||
return
|
||||
energies = [c for c in ctx.discard_pile() if _is_water_energy_card(c)]
|
||||
if not energies:
|
||||
return
|
||||
picks = await ctx.choose_cards(
|
||||
energies, 1, minimum=1,
|
||||
prompt="Choose a Water Energy card to attach",
|
||||
)
|
||||
if not picks:
|
||||
return
|
||||
target = await ctx.choose_pokemon(bench, "Choose a Benched Pokémon") or bench[0]
|
||||
await ctx.attach_energy(picks[0], target)
|
||||
|
||||
|
||||
card = SupporterCardDef(
|
||||
guid="cd61417a-2231-5a88-bf24-355dcb158eae",
|
||||
@@ -11,5 +42,5 @@ card = SupporterCardDef(
|
||||
collector_number=82,
|
||||
set_code="PGO",
|
||||
rarity=Rarities.RareRainbow,
|
||||
effect=unimplemented
|
||||
effect=blanche,
|
||||
)
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.support_common import search_attach_energy
|
||||
from spirit.game.card_effects.attacks_common import damage_per, count_energy
|
||||
|
||||
_vitality_spring_search = search_attach_energy(count=6, distribute=True, shuffle=True)
|
||||
|
||||
|
||||
async def vitality_spring(ctx):
|
||||
"""Once during your turn, you may search up to 6 Energy and attach them
|
||||
in any way you like. If you do, your turn ends."""
|
||||
if await ctx.ask_yes_no(
|
||||
"Search your deck for up to 6 Energy cards and attach them to "
|
||||
"your Pokémon in any way you like?"
|
||||
):
|
||||
await _vitality_spring_search(ctx)
|
||||
ctx.ends_turn = True
|
||||
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="49dab7f1-a23f-5747-954d-9305cf47d5df",
|
||||
@@ -22,7 +38,8 @@ card = PokemonCardDef(
|
||||
Ability(
|
||||
title="Vitality Spring",
|
||||
game_text="Once during your turn, you may search your deck for up to 6 Energy cards and attach them to your Pok\u00e9mon in any way you like. Then, shuffle your deck. If you use this Ability, your turn ends.",
|
||||
effect=unimplemented,
|
||||
activation=Activations.ONCE_PER_TURN,
|
||||
effect=vitality_spring,
|
||||
),
|
||||
Attack(
|
||||
title="Hydro Pump",
|
||||
@@ -30,7 +47,9 @@ card = PokemonCardDef(
|
||||
cost={PokemonTypes.COLORLESS: 4},
|
||||
damage=90,
|
||||
damage_operator="+",
|
||||
effect=unimplemented,
|
||||
effect=damage_per(
|
||||
count_energy("self", energy_type=PokemonTypes.WATER), 30, base=90
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,16 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID
|
||||
|
||||
|
||||
async def enriching_egg(ctx):
|
||||
"""Heal all damage from 1 of your Benched Pokemon."""
|
||||
bench = [p for p in ctx.my_bench() if p.get_attribute(AttrID.HP, 0) < ctx.max_hp(p)]
|
||||
if not bench:
|
||||
return
|
||||
target = await ctx.choose_pokemon(bench, "Choose a Benched Pokémon to heal")
|
||||
if target is not None:
|
||||
await ctx.heal(ctx.max_hp(target) - target.get_attribute(AttrID.HP, 0), target)
|
||||
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="a1534f88-20e1-5912-88f1-0642734752be",
|
||||
@@ -23,7 +34,7 @@ card = PokemonCardDef(
|
||||
title="Enriching Egg",
|
||||
game_text="Heal all damage from 1 of your Benched Pok\u00e9mon.",
|
||||
cost={PokemonTypes.COLORLESS: 1},
|
||||
effect=unimplemented,
|
||||
effect=enriching_egg,
|
||||
),
|
||||
Attack(
|
||||
title="Zen Headbutt",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.attacks_common import snipe_attack
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="d9bf59c3-dd42-583a-9a26-c2dd2eb69d91",
|
||||
@@ -23,7 +24,7 @@ card = PokemonCardDef(
|
||||
title="Split Bomb",
|
||||
game_text="This attack does 50 damage to 2 of your opponent's Pok\u00e9mon. (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)",
|
||||
cost={PokemonTypes.FIRE: 1, PokemonTypes.COLORLESS: 1},
|
||||
effect=unimplemented,
|
||||
effect=snipe_attack(50, pool="any", count=2),
|
||||
),
|
||||
Attack(
|
||||
title="Heat Blast",
|
||||
|
||||
@@ -1,5 +1,37 @@
|
||||
from spirit.game.data_utils import SupporterCardDef, unimplemented
|
||||
from spirit.game.attributes import Rarities
|
||||
from spirit.game.data_utils import SupporterCardDef
|
||||
from spirit.game.attributes import Rarities, PokemonTypes, AttrID
|
||||
from spirit.game.card_effects.trainers import is_basic_energy_card
|
||||
|
||||
|
||||
async def candela(ctx):
|
||||
"""Draw 2. If you drew any cards, flip a coin; heads attaches a Fire
|
||||
Energy card from your discard pile to 1 of your Benched Pokemon."""
|
||||
drawn = await ctx.draw_cards(2)
|
||||
if drawn <= 0:
|
||||
return
|
||||
heads = (await ctx.flip_coins(1, "Candela"))[0]
|
||||
if not heads:
|
||||
return
|
||||
bench = ctx.my_bench()
|
||||
energies = [
|
||||
c for c in ctx.discard_pile()
|
||||
if is_basic_energy_card(c)
|
||||
and PokemonTypes.FIRE.value in (c.get_attribute(AttrID.POKEMON_TYPES) or [])
|
||||
]
|
||||
if not bench or not energies:
|
||||
return
|
||||
picks = await ctx.choose_cards(
|
||||
energies, 1, minimum=1,
|
||||
prompt="Choose a Fire Energy card from your discard pile.",
|
||||
)
|
||||
if not picks:
|
||||
return
|
||||
target = await ctx.choose_pokemon(
|
||||
bench, "Choose a Benched Pokémon to attach Fire Energy to"
|
||||
)
|
||||
if target is not None:
|
||||
await ctx.attach_energy(picks[0], target)
|
||||
|
||||
|
||||
card = SupporterCardDef(
|
||||
guid="e4edecca-d6f5-5615-95ff-18f243103caf",
|
||||
@@ -11,5 +43,5 @@ card = SupporterCardDef(
|
||||
collector_number=65,
|
||||
set_code="PGO",
|
||||
rarity=Rarities.Uncommon,
|
||||
effect=unimplemented
|
||||
effect=candela
|
||||
)
|
||||
|
||||
@@ -1,5 +1,37 @@
|
||||
from spirit.game.data_utils import SupporterCardDef, unimplemented
|
||||
from spirit.game.attributes import Rarities
|
||||
from spirit.game.data_utils import SupporterCardDef
|
||||
from spirit.game.attributes import Rarities, PokemonTypes, AttrID
|
||||
from spirit.game.card_effects.trainers import is_basic_energy_card
|
||||
|
||||
|
||||
async def candela(ctx):
|
||||
"""Draw 2. If you drew any cards, flip a coin; heads attaches a Fire
|
||||
Energy card from your discard pile to 1 of your Benched Pokemon."""
|
||||
drawn = await ctx.draw_cards(2)
|
||||
if drawn <= 0:
|
||||
return
|
||||
heads = (await ctx.flip_coins(1, "Candela"))[0]
|
||||
if not heads:
|
||||
return
|
||||
bench = ctx.my_bench()
|
||||
energies = [
|
||||
c for c in ctx.discard_pile()
|
||||
if is_basic_energy_card(c)
|
||||
and PokemonTypes.FIRE.value in (c.get_attribute(AttrID.POKEMON_TYPES) or [])
|
||||
]
|
||||
if not bench or not energies:
|
||||
return
|
||||
picks = await ctx.choose_cards(
|
||||
energies, 1, minimum=1,
|
||||
prompt="Choose a Fire Energy card from your discard pile.",
|
||||
)
|
||||
if not picks:
|
||||
return
|
||||
target = await ctx.choose_pokemon(
|
||||
bench, "Choose a Benched Pokémon to attach Fire Energy to"
|
||||
)
|
||||
if target is not None:
|
||||
await ctx.attach_energy(picks[0], target)
|
||||
|
||||
|
||||
card = SupporterCardDef(
|
||||
guid="71160c61-af09-5d56-aafd-b1886d76a0d4",
|
||||
@@ -11,5 +43,5 @@ card = SupporterCardDef(
|
||||
collector_number=83,
|
||||
set_code="PGO",
|
||||
rarity=Rarities.RareRainbow,
|
||||
effect=unimplemented
|
||||
effect=candela
|
||||
)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.support_common import heal_targets
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="8506860a-4fe4-5cf8-ae12-ed16c472b45b",
|
||||
@@ -22,7 +23,7 @@ card = PokemonCardDef(
|
||||
title="Delicious Egg",
|
||||
game_text="Heal 30 damage from 1 of your Benched Pok\u00e9mon.",
|
||||
cost={PokemonTypes.COLORLESS: 1},
|
||||
effect=unimplemented,
|
||||
effect=heal_targets(30, "bench_choice"),
|
||||
),
|
||||
Attack(
|
||||
title="Gentle Slap",
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.attacks_common import self_energy_discard_attack
|
||||
from spirit.game.card_effects.pokemon import energy_provides_type
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.session.passives import Passive, active_passives
|
||||
|
||||
|
||||
class BurnBrightlyPassive(Passive):
|
||||
def modify_energy_provided(self, options, energy, holder, board):
|
||||
if holder is None or energy.get_attribute(AttrID.IS_SPECIAL_ENERGY):
|
||||
return options
|
||||
if not energy_provides_type(energy, PokemonTypes.FIRE.value):
|
||||
return options
|
||||
if any(len(option) >= 2 for option in options):
|
||||
return options
|
||||
active_here = any(
|
||||
isinstance(p, BurnBrightlyPassive) and c.owning_player_id == holder.owning_player_id
|
||||
for p, c in active_passives(board)
|
||||
)
|
||||
if not active_here:
|
||||
return options
|
||||
return [list(option) * 2 for option in options]
|
||||
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="0102ad8f-c8f6-5a87-ad03-66c7fe4282b3",
|
||||
@@ -22,14 +43,14 @@ card = PokemonCardDef(
|
||||
Ability(
|
||||
title="Burn Brightly",
|
||||
game_text="Each basic Fire Energy attached to your Pok\u00e9mon provides FireFire Energy. You can't apply more than 1 Burn Brightly Ability at a time.",
|
||||
effect=unimplemented,
|
||||
passive=BurnBrightlyPassive(),
|
||||
),
|
||||
Attack(
|
||||
title="Flare Blitz",
|
||||
game_text="Discard all Fire Energy from this Pok\u00e9mon.",
|
||||
cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 2},
|
||||
damage=170,
|
||||
effect=unimplemented,
|
||||
effect=self_energy_discard_attack(all_energy=True, energy_type=PokemonTypes.FIRE),
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,7 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.pokemon import energy_provides_type
|
||||
from spirit.game.card_effects.support_common import search_attach_energy
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="83d1e4c7-0f9e-54ea-abc8-e4abe32cabdd",
|
||||
@@ -23,7 +25,11 @@ card = PokemonCardDef(
|
||||
game_text="Search your deck for a Fire Energy card and attach it to this Pok\u00e9mon. Then, shuffle your deck.",
|
||||
cost={PokemonTypes.FIRE: 1},
|
||||
damage=10,
|
||||
effect=unimplemented,
|
||||
effect=search_attach_energy(
|
||||
predicate=lambda c: energy_provides_type(c, PokemonTypes.FIRE.value),
|
||||
count=1, to_self=True,
|
||||
prompt="Choose a Fire Energy card to attach.",
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,4 +1,5 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.card_effects.attacks_common import self_energy_discard_attack
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
|
||||
card = PokemonCardDef(
|
||||
@@ -29,7 +30,7 @@ card = PokemonCardDef(
|
||||
game_text="Discard a Fire Energy from this Pok\u00e9mon.",
|
||||
cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 2},
|
||||
damage=100,
|
||||
effect=unimplemented,
|
||||
effect=self_energy_discard_attack(count=1, energy_type=PokemonTypes.FIRE),
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,13 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.attacks_common import condition_attack
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions
|
||||
|
||||
|
||||
async def counter(ctx):
|
||||
"""20 damage, +that much more if this Pokemon was damaged by an attack during the opponent's last turn."""
|
||||
bonus = ctx.damage_taken_last_turn(ctx.attacker)
|
||||
await ctx.deal_damage(20 + bonus)
|
||||
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="a636cb92-fb8b-563d-a978-eb976e5123a6",
|
||||
@@ -24,7 +32,7 @@ card = PokemonCardDef(
|
||||
cost={PokemonTypes.FIGHTING: 1},
|
||||
damage=20,
|
||||
damage_operator="+",
|
||||
effect=unimplemented,
|
||||
effect=counter,
|
||||
),
|
||||
Attack(
|
||||
title="Dynamic Punch",
|
||||
@@ -32,7 +40,7 @@ card = PokemonCardDef(
|
||||
cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 2},
|
||||
damage=90,
|
||||
damage_operator="+",
|
||||
effect=unimplemented,
|
||||
effect=condition_attack(SpecialConditions.CONFUSED, flip=True, heads_bonus_damage=90),
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,13 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.attacks_common import condition_attack
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions
|
||||
|
||||
|
||||
async def counter(ctx):
|
||||
"""20 damage, +that much more if this Pokemon was damaged by an attack during the opponent's last turn."""
|
||||
bonus = ctx.damage_taken_last_turn(ctx.attacker)
|
||||
await ctx.deal_damage(20 + bonus)
|
||||
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="b840bfb9-657c-5d73-88ad-6fce80654bd6",
|
||||
@@ -24,7 +32,7 @@ card = PokemonCardDef(
|
||||
cost={PokemonTypes.FIGHTING: 1},
|
||||
damage=20,
|
||||
damage_operator="+",
|
||||
effect=unimplemented,
|
||||
effect=counter,
|
||||
),
|
||||
Attack(
|
||||
title="Dynamic Punch",
|
||||
@@ -32,7 +40,7 @@ card = PokemonCardDef(
|
||||
cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 2},
|
||||
damage=90,
|
||||
damage_operator="+",
|
||||
effect=unimplemented,
|
||||
effect=condition_attack(SpecialConditions.CONFUSED, flip=True, heads_bonus_damage=90),
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,13 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.attacks_common import condition_attack
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions
|
||||
|
||||
|
||||
async def counter(ctx):
|
||||
"""20 damage, +that much more if this Pokemon was damaged by an attack during the opponent's last turn."""
|
||||
bonus = ctx.damage_taken_last_turn(ctx.attacker)
|
||||
await ctx.deal_damage(20 + bonus)
|
||||
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="a6cded83-22e8-57ab-bae6-f51a1a627c0e",
|
||||
@@ -24,7 +32,7 @@ card = PokemonCardDef(
|
||||
cost={PokemonTypes.FIGHTING: 1},
|
||||
damage=20,
|
||||
damage_operator="+",
|
||||
effect=unimplemented,
|
||||
effect=counter,
|
||||
),
|
||||
Attack(
|
||||
title="Dynamic Punch",
|
||||
@@ -32,7 +40,7 @@ card = PokemonCardDef(
|
||||
cost={PokemonTypes.FIGHTING: 1, PokemonTypes.COLORLESS: 2},
|
||||
damage=90,
|
||||
damage_operator="+",
|
||||
effect=unimplemented,
|
||||
effect=condition_attack(SpecialConditions.CONFUSED, flip=True, heads_bonus_damage=90),
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,6 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.pokemon import SuddenTransformationPassive
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="b37b1ba4-3f8a-555a-bf98-caaccf0e8dfe",
|
||||
@@ -21,7 +22,7 @@ card = PokemonCardDef(
|
||||
Ability(
|
||||
title="Sudden Transformation",
|
||||
game_text="This Pok\u00e9mon can use the attacks of any Basic Pok\u00e9mon in your discard pile, except for Pok\u00e9mon with a Rule Box (Pok\u00e9mon V, Pok\u00e9mon-GX, etc. have Rule Boxes). (You still need the necessary Energy to use each attack.)",
|
||||
effect=unimplemented,
|
||||
passive=SuddenTransformationPassive(),
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,24 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.attacks_common import lock_all_attacks
|
||||
from spirit.game.card_effects.pokemon import energy_provides_type
|
||||
from spirit.game.card_effects.support_common import look_top_attach_energy
|
||||
|
||||
|
||||
def _is_water_or_lightning_energy(card):
|
||||
return (
|
||||
energy_provides_type(card, PokemonTypes.WATER.value)
|
||||
or energy_provides_type(card, PokemonTypes.LIGHTNING.value)
|
||||
)
|
||||
|
||||
|
||||
async def giga_impact(ctx):
|
||||
"""250 damage. During your next turn, this Pokemon can't attack."""
|
||||
await ctx.deal_damage()
|
||||
lock_all_attacks(ctx, ctx.attacker)
|
||||
|
||||
|
||||
draconic_star = look_top_attach_energy(12, predicate=_is_water_or_lightning_energy)
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="58fa5388-ecc3-5215-ae28-39216771ceae",
|
||||
@@ -23,13 +42,14 @@ card = PokemonCardDef(
|
||||
game_text="During your next turn, this Pok\u00e9mon can't attack.",
|
||||
cost={PokemonTypes.WATER: 1, PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 2},
|
||||
damage=250,
|
||||
effect=unimplemented,
|
||||
effect=giga_impact,
|
||||
),
|
||||
Attack(
|
||||
title="Draconic Star",
|
||||
game_text="Look at the top 12 cards of your deck and attach any number of Water or Lightning Energy cards you find there to your Pok\u00e9mon in any way you like. Shuffle the other cards back into your deck. (You can't use more than 1 VSTAR Power in a game.)",
|
||||
cost={PokemonTypes.COLORLESS: 1},
|
||||
effect=unimplemented,
|
||||
vstar=True,
|
||||
effect=draconic_star,
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,24 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.attacks_common import lock_all_attacks
|
||||
from spirit.game.card_effects.pokemon import energy_provides_type
|
||||
from spirit.game.card_effects.support_common import look_top_attach_energy
|
||||
|
||||
|
||||
def _is_water_or_lightning_energy(card):
|
||||
return (
|
||||
energy_provides_type(card, PokemonTypes.WATER.value)
|
||||
or energy_provides_type(card, PokemonTypes.LIGHTNING.value)
|
||||
)
|
||||
|
||||
|
||||
async def giga_impact(ctx):
|
||||
"""250 damage. During your next turn, this Pokemon can't attack."""
|
||||
await ctx.deal_damage()
|
||||
lock_all_attacks(ctx, ctx.attacker)
|
||||
|
||||
|
||||
draconic_star = look_top_attach_energy(12, predicate=_is_water_or_lightning_energy)
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="a56db22e-4b35-56f3-ba94-732e42148400",
|
||||
@@ -23,13 +42,14 @@ card = PokemonCardDef(
|
||||
game_text="During your next turn, this Pok\u00e9mon can't attack.",
|
||||
cost={PokemonTypes.WATER: 1, PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 2},
|
||||
damage=250,
|
||||
effect=unimplemented,
|
||||
effect=giga_impact,
|
||||
),
|
||||
Attack(
|
||||
title="Draconic Star",
|
||||
game_text="Look at the top 12 cards of your deck and attach any number of Water or Lightning Energy cards you find there to your Pok\u00e9mon in any way you like. Shuffle the other cards back into your deck. (You can't use more than 1 VSTAR Power in a game.)",
|
||||
cost={PokemonTypes.COLORLESS: 1},
|
||||
effect=unimplemented,
|
||||
vstar=True,
|
||||
effect=draconic_star,
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,6 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.attacks_common import discard_opponent_energy_attack
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="f56edb89-68c2-555a-844a-fb0460efae15",
|
||||
@@ -19,10 +20,10 @@ card = PokemonCardDef(
|
||||
abilities=[
|
||||
Attack(
|
||||
title="Hyper Beam",
|
||||
game_text="Discard an Energy from your opponent's Active Pok\u00e9mon.",
|
||||
game_text="Discard an Energy from your opponent's Active Pokémon.",
|
||||
cost={PokemonTypes.WATER: 1, PokemonTypes.LIGHTNING: 1},
|
||||
damage=60,
|
||||
effect=unimplemented,
|
||||
effect=discard_opponent_energy_attack(count=1),
|
||||
),
|
||||
Attack(
|
||||
title="Buster Tail",
|
||||
@@ -30,4 +31,4 @@ card = PokemonCardDef(
|
||||
damage=160,
|
||||
),
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.attacks_common import discard_opponent_energy_attack
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="f9dbadd3-d1f8-5518-b84b-4ce04911567c",
|
||||
@@ -19,10 +20,10 @@ card = PokemonCardDef(
|
||||
abilities=[
|
||||
Attack(
|
||||
title="Hyper Beam",
|
||||
game_text="Discard an Energy from your opponent's Active Pok\u00e9mon.",
|
||||
game_text="Discard an Energy from your opponent's Active Pokémon.",
|
||||
cost={PokemonTypes.WATER: 1, PokemonTypes.LIGHTNING: 1},
|
||||
damage=60,
|
||||
effect=unimplemented,
|
||||
effect=discard_opponent_energy_attack(count=1),
|
||||
),
|
||||
Attack(
|
||||
title="Buster Tail",
|
||||
@@ -30,4 +31,4 @@ card = PokemonCardDef(
|
||||
damage=160,
|
||||
),
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1,6 +1,20 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
import random
|
||||
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
|
||||
|
||||
async def whiny_voice(ctx):
|
||||
"""Choose a random card from your opponent's hand. Your opponent reveals
|
||||
that card and shuffles it into their deck."""
|
||||
hand = ctx.hand(ctx.opponent_id)
|
||||
if not hand:
|
||||
return
|
||||
card = random.choice(hand)
|
||||
await ctx.reveal_cards([card])
|
||||
await ctx.shuffle_into_deck([card], ctx.opponent_id)
|
||||
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="d61727a9-46c1-5dfc-b296-1b53d72a7352",
|
||||
key="PGO",
|
||||
@@ -22,7 +36,7 @@ card = PokemonCardDef(
|
||||
title="Whiny Voice",
|
||||
game_text="Choose a random card from your opponent's hand. Your opponent reveals that card and shuffles it into their deck.",
|
||||
cost={PokemonTypes.COLORLESS: 1},
|
||||
effect=unimplemented,
|
||||
effect=whiny_voice,
|
||||
),
|
||||
Attack(
|
||||
title="Tackle",
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
from spirit.game.data_utils import ItemCardDef, unimplemented
|
||||
from spirit.game.data_utils import ItemCardDef
|
||||
from spirit.game.attributes import Rarities
|
||||
from spirit.game.card_effects.support_common import search_to_bench
|
||||
from spirit.game.session.effects import is_basic_pokemon
|
||||
|
||||
|
||||
async def egg_incubator(ctx):
|
||||
"""Flip a coin. Heads: search a Basic Pokemon onto your Bench, shuffle.
|
||||
Tails: put this card on the bottom of your deck instead of the discard pile."""
|
||||
heads = (await ctx.flip_coins(1, "Egg Incubator"))[0]
|
||||
if heads:
|
||||
await search_to_bench(
|
||||
predicate=is_basic_pokemon, count=1,
|
||||
prompt="Choose a Basic Pokémon to put onto your Bench.",
|
||||
)(ctx)
|
||||
else:
|
||||
await ctx.put_on_bottom_of_deck(ctx.source)
|
||||
|
||||
|
||||
card = ItemCardDef(
|
||||
guid="10bee995-3c57-5f12-98d3-3f1b45c62180",
|
||||
@@ -11,5 +27,5 @@ card = ItemCardDef(
|
||||
collector_number=66,
|
||||
set_code="PGO",
|
||||
rarity=Rarities.Uncommon,
|
||||
effect=unimplemented
|
||||
effect=egg_incubator,
|
||||
)
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
from spirit.game.data_utils import ItemCardDef, unimplemented
|
||||
from spirit.game.data_utils import ItemCardDef
|
||||
from spirit.game.attributes import Rarities
|
||||
from spirit.game.card_effects.support_common import search_to_bench
|
||||
from spirit.game.session.effects import is_basic_pokemon
|
||||
|
||||
|
||||
async def egg_incubator(ctx):
|
||||
"""Flip a coin. Heads: search a Basic Pokemon onto your Bench, shuffle.
|
||||
Tails: put this card on the bottom of your deck instead of the discard pile."""
|
||||
heads = (await ctx.flip_coins(1, "Egg Incubator"))[0]
|
||||
if heads:
|
||||
await search_to_bench(
|
||||
predicate=is_basic_pokemon, count=1,
|
||||
prompt="Choose a Basic Pokémon to put onto your Bench.",
|
||||
)(ctx)
|
||||
else:
|
||||
await ctx.put_on_bottom_of_deck(ctx.source)
|
||||
|
||||
|
||||
card = ItemCardDef(
|
||||
guid="440d3bd9-bc70-5512-ba16-a634259fc77d",
|
||||
@@ -11,5 +27,5 @@ card = ItemCardDef(
|
||||
collector_number=87,
|
||||
set_code="PGO",
|
||||
rarity=Rarities.RareSecret,
|
||||
effect=unimplemented
|
||||
effect=egg_incubator,
|
||||
)
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.attacks_common import bonus_if
|
||||
|
||||
|
||||
def _entered_active_this_turn(ctx):
|
||||
return ctx.entered_active_this_turn(ctx.attacker)
|
||||
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="c5904577-7950-5bbb-b720-30dc82474c01",
|
||||
@@ -25,7 +31,7 @@ card = PokemonCardDef(
|
||||
cost={PokemonTypes.WATER: 1},
|
||||
damage=20,
|
||||
damage_operator="+",
|
||||
effect=unimplemented,
|
||||
effect=bonus_if(_entered_active_this_turn, 90),
|
||||
),
|
||||
Attack(
|
||||
title="Slash",
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.attacks_common import mill_attack
|
||||
|
||||
|
||||
async def wreak_havoc(ctx):
|
||||
"""Flip a coin until you get tails. For each heads, discard the top 2
|
||||
cards of your opponent's deck."""
|
||||
heads = await ctx.flip_until_tails("Wreak Havoc")
|
||||
if heads:
|
||||
await ctx.discard_cards(ctx.deck_top(heads * 2, player_id=ctx.opponent_id))
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="d9f36c0f-95d4-5b0b-90c7-f3e460535e53",
|
||||
@@ -23,14 +32,14 @@ card = PokemonCardDef(
|
||||
title="Wreak Havoc",
|
||||
game_text="Flip a coin until you get tails. For each heads, discard the top 2 cards of your opponent's deck.",
|
||||
cost={PokemonTypes.COLORLESS: 1},
|
||||
effect=unimplemented,
|
||||
effect=wreak_havoc,
|
||||
),
|
||||
Attack(
|
||||
title="Wild Splash",
|
||||
game_text="Discard the top 5 cards of your deck.",
|
||||
cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 2},
|
||||
damage=230,
|
||||
effect=unimplemented,
|
||||
effect=mill_attack(5, opponent=False),
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,7 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.support_common import search_to_hand
|
||||
from spirit.game.session.effects import is_pokemon_card
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="cc24f629-0c9d-51da-9bbb-0bd69465899f",
|
||||
@@ -23,7 +25,10 @@ card = PokemonCardDef(
|
||||
title="Summoning Aroma",
|
||||
game_text="Search your deck for up to 2 Pok\u00e9mon, reveal them, and put them into your hand. Then, shuffle your deck.",
|
||||
cost={PokemonTypes.GRASS: 1},
|
||||
effect=unimplemented,
|
||||
effect=search_to_hand(
|
||||
is_pokemon_card, count=2, reveal=True,
|
||||
prompt="Choose up to 2 Pokémon to put into your hand.",
|
||||
),
|
||||
),
|
||||
Attack(
|
||||
title="Razor Leaf",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions
|
||||
from spirit.game.card_effects.attacks_common import condition_attack
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="8a31d277-f608-586a-81b1-4c44d9a63f12",
|
||||
@@ -23,7 +24,7 @@ card = PokemonCardDef(
|
||||
game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.",
|
||||
cost={PokemonTypes.WATER: 1},
|
||||
damage=20,
|
||||
effect=unimplemented,
|
||||
effect=condition_attack(SpecialConditions.PARALYZED, flip=True),
|
||||
),
|
||||
Attack(
|
||||
title="Surf",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.attacks_common import flip_bonus
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="8771ac8b-2fbd-51ca-a017-543027ff58b7",
|
||||
@@ -24,7 +25,7 @@ card = PokemonCardDef(
|
||||
cost={PokemonTypes.COLORLESS: 1},
|
||||
damage=10,
|
||||
damage_operator="+",
|
||||
effect=unimplemented,
|
||||
effect=flip_bonus(10),
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,7 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.pokemon import read_the_wind
|
||||
from spirit.game.card_effects.attacks_common import damage_per, count_energy
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="81288712-7261-5003-9462-a3e56043a648",
|
||||
@@ -23,7 +25,7 @@ card = PokemonCardDef(
|
||||
title="Cycle Draw",
|
||||
game_text="Discard a card from your hand. If you do, draw 3 cards.",
|
||||
cost={PokemonTypes.PSYCHIC: 1},
|
||||
effect=unimplemented,
|
||||
effect=read_the_wind,
|
||||
),
|
||||
Attack(
|
||||
title="Moon Kinesis",
|
||||
@@ -31,7 +33,7 @@ card = PokemonCardDef(
|
||||
cost={PokemonTypes.COLORLESS: 3},
|
||||
damage=30,
|
||||
damage_operator="+",
|
||||
effect=unimplemented,
|
||||
effect=damage_per(count_energy("self", energy_type=PokemonTypes.PSYCHIC.value), 30),
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,21 @@
|
||||
from spirit.game.data_utils import ItemCardDef, unimplemented
|
||||
from spirit.game.data_utils import ItemCardDef
|
||||
from spirit.game.attributes import Rarities
|
||||
from spirit.game.session.effects import is_pokemon_card
|
||||
|
||||
|
||||
async def lure_module_effect(ctx):
|
||||
"""Each player reveals the top 3 of their deck; Pokemon found go to hand,
|
||||
the rest is shuffled back."""
|
||||
for pid in (ctx.player_id, ctx.opponent_id):
|
||||
top = ctx.deck_top(3, player_id=pid)
|
||||
if not top:
|
||||
continue
|
||||
await ctx.reveal_cards(top)
|
||||
matches = [c for c in top if is_pokemon_card(c)]
|
||||
if matches:
|
||||
await ctx.put_in_hand(matches, reveal=False)
|
||||
await ctx.shuffle_deck(player_id=pid)
|
||||
|
||||
|
||||
card = ItemCardDef(
|
||||
guid="d17d2b10-6400-5d79-9ae6-63504e64447f",
|
||||
@@ -11,5 +27,5 @@ card = ItemCardDef(
|
||||
collector_number=67,
|
||||
set_code="PGO",
|
||||
rarity=Rarities.Uncommon,
|
||||
effect=unimplemented
|
||||
effect=lure_module_effect
|
||||
)
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
from spirit.game.data_utils import ItemCardDef, unimplemented
|
||||
from spirit.game.data_utils import ItemCardDef
|
||||
from spirit.game.attributes import Rarities
|
||||
from spirit.game.session.effects import is_pokemon_card
|
||||
|
||||
|
||||
async def lure_module_effect(ctx):
|
||||
"""Each player reveals the top 3 of their deck; Pokemon found go to hand,
|
||||
the rest is shuffled back."""
|
||||
for pid in (ctx.player_id, ctx.opponent_id):
|
||||
top = ctx.deck_top(3, player_id=pid)
|
||||
if not top:
|
||||
continue
|
||||
await ctx.reveal_cards(top)
|
||||
matches = [c for c in top if is_pokemon_card(c)]
|
||||
if matches:
|
||||
await ctx.put_in_hand(matches, reveal=False)
|
||||
await ctx.shuffle_deck(player_id=pid)
|
||||
|
||||
|
||||
card = ItemCardDef(
|
||||
guid="132ad4d9-8492-544c-afd9-f8899fd7ac12",
|
||||
@@ -11,5 +27,5 @@ card = ItemCardDef(
|
||||
collector_number=88,
|
||||
set_code="PGO",
|
||||
rarity=Rarities.RareSecret,
|
||||
effect=unimplemented
|
||||
effect=lure_module_effect
|
||||
)
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID
|
||||
from spirit.game.card_effects.support_common import search_to_hand
|
||||
from spirit.game.card_effects.attacks_common import damage_per, count_discard
|
||||
|
||||
|
||||
def _is_magikarp(card):
|
||||
return card.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) == "Magikarp"
|
||||
|
||||
|
||||
def _is_magikarp_or_gyarados(card):
|
||||
return card.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) in ("Magikarp", "Gyarados")
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="9c7cea7e-3a5f-51fd-9b32-4edaa5613811",
|
||||
@@ -22,7 +32,7 @@ card = PokemonCardDef(
|
||||
title="Lively Grouping",
|
||||
game_text="Search your deck for any number of Magikarp, reveal them, and put them into your hand. Then, shuffle your deck.",
|
||||
cost={PokemonTypes.COLORLESS: 1},
|
||||
effect=unimplemented,
|
||||
effect=search_to_hand(_is_magikarp, count=60, minimum=0, reveal=True),
|
||||
),
|
||||
Attack(
|
||||
title="Raging Fin",
|
||||
@@ -30,7 +40,7 @@ card = PokemonCardDef(
|
||||
cost={PokemonTypes.COLORLESS: 2},
|
||||
damage=10,
|
||||
damage_operator="+",
|
||||
effect=unimplemented,
|
||||
effect=damage_per(count_discard("mine", pred=_is_magikarp_or_gyarados), 30, base=10),
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,11 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.attacks_common import damage_per, count_energy
|
||||
|
||||
|
||||
def _extra_metal_energy(ctx) -> int:
|
||||
return max(0, count_energy("self", energy_type=PokemonTypes.METAL)(ctx) - 3)
|
||||
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="a5db3f2a-bd2a-5f4c-bd92-e5639bb833a5",
|
||||
@@ -26,7 +32,7 @@ card = PokemonCardDef(
|
||||
cost={PokemonTypes.METAL: 3},
|
||||
damage=160,
|
||||
damage_operator="+",
|
||||
effect=unimplemented,
|
||||
effect=damage_per(_extra_metal_energy, 60, base=160, cap=280),
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,11 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.attacks_common import damage_per, count_energy
|
||||
|
||||
|
||||
def _extra_metal_energy(ctx) -> int:
|
||||
return max(0, count_energy("self", energy_type=PokemonTypes.METAL)(ctx) - 3)
|
||||
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="28f9a8c6-31ad-5713-9fe3-6d6a54c5e278",
|
||||
@@ -26,7 +32,7 @@ card = PokemonCardDef(
|
||||
cost={PokemonTypes.METAL: 3},
|
||||
damage=160,
|
||||
damage_operator="+",
|
||||
effect=unimplemented,
|
||||
effect=damage_per(_extra_metal_energy, 60, base=160, cap=280),
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,23 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.trainers import is_metal_energy_card
|
||||
|
||||
|
||||
async def _arm_charge(ctx):
|
||||
await ctx.deal_damage()
|
||||
energies = [c for c in ctx.hand() if is_metal_energy_card(c)]
|
||||
if not energies:
|
||||
return
|
||||
if not await ctx.ask_yes_no(
|
||||
"Attach a Metal Energy card from your hand to this Pokémon?"
|
||||
):
|
||||
return
|
||||
picked = await ctx.choose_cards(
|
||||
energies, 1, minimum=1, prompt="Choose a Metal Energy card to attach"
|
||||
)
|
||||
if picked:
|
||||
await ctx.attach_energy(picked[0], ctx.attacker)
|
||||
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="b77472f2-c926-5367-b469-8e680bc0e647",
|
||||
@@ -24,7 +42,7 @@ card = PokemonCardDef(
|
||||
game_text="You may attach a Metal Energy card from your hand to this Pok\u00e9mon.",
|
||||
cost={PokemonTypes.METAL: 2},
|
||||
damage=50,
|
||||
effect=unimplemented,
|
||||
effect=_arm_charge,
|
||||
),
|
||||
Attack(
|
||||
title="Mega Punch",
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.trainers import is_metal_energy_card
|
||||
|
||||
|
||||
async def _arm_charge(ctx):
|
||||
await ctx.deal_damage()
|
||||
energies = [c for c in ctx.hand() if is_metal_energy_card(c)]
|
||||
if not energies:
|
||||
return
|
||||
if not await ctx.ask_yes_no(
|
||||
"Attach a Metal Energy card from your hand to this Pokémon?"
|
||||
):
|
||||
return
|
||||
picked = await ctx.choose_cards(
|
||||
energies, 1, minimum=1, prompt="Choose a Metal Energy card to attach"
|
||||
)
|
||||
if picked:
|
||||
await ctx.attach_energy(picked[0], ctx.attacker)
|
||||
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="415f4d10-3d01-5d2e-a550-fde9f4534c5b",
|
||||
@@ -24,7 +42,7 @@ card = PokemonCardDef(
|
||||
game_text="You may attach a Metal Energy card from your hand to this Pok\u00e9mon.",
|
||||
cost={PokemonTypes.METAL: 2},
|
||||
damage=50,
|
||||
effect=unimplemented,
|
||||
effect=_arm_charge,
|
||||
),
|
||||
Attack(
|
||||
title="Mega Punch",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.attacks_common import flip_damage
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="1eb170c9-255f-5716-af07-873244bb18c4",
|
||||
@@ -31,7 +32,7 @@ card = PokemonCardDef(
|
||||
cost={PokemonTypes.METAL: 1, PokemonTypes.COLORLESS: 2},
|
||||
damage=30,
|
||||
damage_operator="+",
|
||||
effect=unimplemented,
|
||||
effect=flip_damage(coins=2, bonus_per_heads=90),
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,6 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions
|
||||
from spirit.game.card_effects.attacks_common import condition_attack
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="2112d529-f8b5-5117-948d-fb1c17a49bf7",
|
||||
@@ -24,7 +25,7 @@ card = PokemonCardDef(
|
||||
game_text="Flip a coin. If heads, your opponent's Active Pok\u00e9mon is now Paralyzed.",
|
||||
cost={PokemonTypes.COLORLESS: 2},
|
||||
damage=20,
|
||||
effect=unimplemented,
|
||||
effect=condition_attack(SpecialConditions.PARALYZED, flip=True),
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,27 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, is_pokemon_v
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.pokemon import energy_provides_type
|
||||
|
||||
|
||||
async def psy_purge(ctx):
|
||||
energies = [
|
||||
e for p in ctx.my_pokemon_in_play() for e in ctx.attached_energies(p)
|
||||
if energy_provides_type(e, PokemonTypes.PSYCHIC.value)
|
||||
]
|
||||
picks = await ctx.choose_cards(
|
||||
energies, 3, minimum=0,
|
||||
prompt="Discard up to 3 Psychic Energy from your Pokémon.",
|
||||
)
|
||||
await ctx.discard_cards(picks)
|
||||
if picks:
|
||||
await ctx.deal_damage(90 * len(picks))
|
||||
|
||||
|
||||
async def star_raid(ctx):
|
||||
for pokemon in ctx.opponent_pokemon_in_play():
|
||||
if is_pokemon_v(pokemon.archetype_id):
|
||||
await ctx.deal_damage(120, target=pokemon, apply_modifiers=False)
|
||||
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="87ec5586-6ef2-5aae-a1bc-c7303bcaa2da",
|
||||
@@ -26,13 +48,14 @@ card = PokemonCardDef(
|
||||
cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1},
|
||||
damage=90,
|
||||
damage_operator="x",
|
||||
effect=unimplemented,
|
||||
effect=psy_purge,
|
||||
),
|
||||
Attack(
|
||||
title="Star Raid",
|
||||
game_text="This attack does 120 damage to each of your opponent's Pok\u00e9mon V. This damage isn't affected by Weakness or Resistance. (You can't use more than 1 VSTAR Power in a game.)",
|
||||
cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1},
|
||||
effect=unimplemented,
|
||||
vstar=True,
|
||||
effect=star_raid,
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,27 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, is_pokemon_v
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.pokemon import energy_provides_type
|
||||
|
||||
|
||||
async def psy_purge(ctx):
|
||||
energies = [
|
||||
e for p in ctx.my_pokemon_in_play() for e in ctx.attached_energies(p)
|
||||
if energy_provides_type(e, PokemonTypes.PSYCHIC.value)
|
||||
]
|
||||
picks = await ctx.choose_cards(
|
||||
energies, 3, minimum=0,
|
||||
prompt="Discard up to 3 Psychic Energy from your Pokémon.",
|
||||
)
|
||||
await ctx.discard_cards(picks)
|
||||
if picks:
|
||||
await ctx.deal_damage(90 * len(picks))
|
||||
|
||||
|
||||
async def star_raid(ctx):
|
||||
for pokemon in ctx.opponent_pokemon_in_play():
|
||||
if is_pokemon_v(pokemon.archetype_id):
|
||||
await ctx.deal_damage(120, target=pokemon, apply_modifiers=False)
|
||||
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="761bc299-300a-5c20-92b2-dfccdbf1b307",
|
||||
@@ -26,13 +48,14 @@ card = PokemonCardDef(
|
||||
cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1},
|
||||
damage=90,
|
||||
damage_operator="x",
|
||||
effect=unimplemented,
|
||||
effect=psy_purge,
|
||||
),
|
||||
Attack(
|
||||
title="Star Raid",
|
||||
game_text="This attack does 120 damage to each of your opponent's Pok\u00e9mon V. This damage isn't affected by Weakness or Resistance. (You can't use more than 1 VSTAR Power in a game.)",
|
||||
cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1},
|
||||
effect=unimplemented,
|
||||
vstar=True,
|
||||
effect=star_raid,
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,27 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, is_pokemon_v
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.pokemon import energy_provides_type
|
||||
|
||||
|
||||
async def psy_purge(ctx):
|
||||
energies = [
|
||||
e for p in ctx.my_pokemon_in_play() for e in ctx.attached_energies(p)
|
||||
if energy_provides_type(e, PokemonTypes.PSYCHIC.value)
|
||||
]
|
||||
picks = await ctx.choose_cards(
|
||||
energies, 3, minimum=0,
|
||||
prompt="Discard up to 3 Psychic Energy from your Pokémon.",
|
||||
)
|
||||
await ctx.discard_cards(picks)
|
||||
if picks:
|
||||
await ctx.deal_damage(90 * len(picks))
|
||||
|
||||
|
||||
async def star_raid(ctx):
|
||||
for pokemon in ctx.opponent_pokemon_in_play():
|
||||
if is_pokemon_v(pokemon.archetype_id):
|
||||
await ctx.deal_damage(120, target=pokemon, apply_modifiers=False)
|
||||
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="54612974-e4ee-5bff-86c8-fb5a59135b84",
|
||||
@@ -26,13 +48,14 @@ card = PokemonCardDef(
|
||||
cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1},
|
||||
damage=90,
|
||||
damage_operator="x",
|
||||
effect=unimplemented,
|
||||
effect=psy_purge,
|
||||
),
|
||||
Attack(
|
||||
title="Star Raid",
|
||||
game_text="This attack does 120 damage to each of your opponent's Pok\u00e9mon V. This damage isn't affected by Weakness or Resistance. (You can't use more than 1 VSTAR Power in a game.)",
|
||||
cost={PokemonTypes.PSYCHIC: 1, PokemonTypes.COLORLESS: 1},
|
||||
effect=unimplemented,
|
||||
vstar=True,
|
||||
effect=star_raid,
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,6 +1,23 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
|
||||
|
||||
async def _transfer_break(ctx):
|
||||
await ctx.deal_damage()
|
||||
bench = ctx.my_bench()
|
||||
energies = ctx.attached_energies(ctx.attacker)
|
||||
if not bench or not energies:
|
||||
return
|
||||
picked = await ctx.choose_cards(energies, 1, minimum=1, prompt="Choose an Energy to move")
|
||||
if not picked:
|
||||
return
|
||||
target = await ctx.choose_pokemon(
|
||||
bench, "Choose the Benched Pokémon to move the Energy to"
|
||||
)
|
||||
if target is not None:
|
||||
await ctx.move_energy(picked[0], target)
|
||||
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="09d83eb7-1d28-555c-ba85-a8ad58e6d7e1",
|
||||
key="PGO",
|
||||
@@ -29,7 +46,7 @@ card = PokemonCardDef(
|
||||
game_text="Move an Energy from this Pok\u00e9mon to 1 of your Benched Pok\u00e9mon.",
|
||||
cost={PokemonTypes.PSYCHIC: 2, PokemonTypes.COLORLESS: 1},
|
||||
damage=160,
|
||||
effect=unimplemented,
|
||||
effect=_transfer_break,
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,6 +1,23 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
|
||||
|
||||
async def _transfer_break(ctx):
|
||||
await ctx.deal_damage()
|
||||
bench = ctx.my_bench()
|
||||
energies = ctx.attached_energies(ctx.attacker)
|
||||
if not bench or not energies:
|
||||
return
|
||||
picked = await ctx.choose_cards(energies, 1, minimum=1, prompt="Choose an Energy to move")
|
||||
if not picked:
|
||||
return
|
||||
target = await ctx.choose_pokemon(
|
||||
bench, "Choose the Benched Pokémon to move the Energy to"
|
||||
)
|
||||
if target is not None:
|
||||
await ctx.move_energy(picked[0], target)
|
||||
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="17788c8e-c675-52e8-aef1-79996fb495ed",
|
||||
key="PGO",
|
||||
@@ -29,7 +46,7 @@ card = PokemonCardDef(
|
||||
game_text="Move an Energy from this Pok\u00e9mon to 1 of your Benched Pok\u00e9mon.",
|
||||
cost={PokemonTypes.PSYCHIC: 2, PokemonTypes.COLORLESS: 1},
|
||||
damage=160,
|
||||
effect=unimplemented,
|
||||
effect=_transfer_break,
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,19 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, def_for
|
||||
from spirit.game.attributes import AttrID, PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.session.effects import is_basic_pokemon
|
||||
from spirit.game.card_effects.passives_common import team_damage_boost_passive
|
||||
|
||||
|
||||
def _flare_symbol_attacker(pokemon) -> bool:
|
||||
if not is_basic_pokemon(pokemon):
|
||||
return False
|
||||
types = pokemon.get_attribute(AttrID.POKEMON_TYPES) or []
|
||||
if PokemonTypes.FIRE.value not in types:
|
||||
return False
|
||||
definition = def_for(pokemon.archetype_id)
|
||||
name = getattr(definition, "display_name", None) or ""
|
||||
return name != "Moltres"
|
||||
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="b3cf08b6-fbf4-5c63-ad13-b353e030ec75",
|
||||
@@ -21,7 +35,7 @@ card = PokemonCardDef(
|
||||
Ability(
|
||||
title="Flare Symbol",
|
||||
game_text="Your Basic Fire Pok\u00e9mon's attacks, except any Moltres, do 10 more damage to your opponent's Active Pok\u00e9mon (before applying Weakness and Resistance).",
|
||||
effect=unimplemented,
|
||||
passive=team_damage_boost_passive(10, attacker_pred=_flare_symbol_attacker),
|
||||
),
|
||||
Attack(
|
||||
title="Fire Wing",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.support_common import heal_attack
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="5b3ac0ad-0387-56bf-8874-885dbe59735d",
|
||||
@@ -23,7 +24,7 @@ card = PokemonCardDef(
|
||||
title="Nap",
|
||||
game_text="Heal 20 damage from this Pok\u00e9mon.",
|
||||
cost={PokemonTypes.COLORLESS: 1},
|
||||
effect=unimplemented,
|
||||
effect=heal_attack(20),
|
||||
),
|
||||
Attack(
|
||||
title="Peck",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.attacks_common import flip_bonus
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="4e6cf096-db9c-5189-a3c9-0d1676c55ca7",
|
||||
@@ -29,7 +30,7 @@ card = PokemonCardDef(
|
||||
cost={PokemonTypes.FIRE: 2, PokemonTypes.COLORLESS: 1},
|
||||
damage=50,
|
||||
damage_operator="+",
|
||||
effect=unimplemented,
|
||||
effect=flip_bonus(50),
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,6 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.attacks_common import condition_attack, damage_per, damage_counters_on
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="beff1709-f449-5379-a203-cf1395e94da7",
|
||||
@@ -23,7 +24,7 @@ card = PokemonCardDef(
|
||||
game_text="During your opponent's next turn, the Defending Pok\u00e9mon can't retreat.",
|
||||
cost={PokemonTypes.COLORLESS: 3},
|
||||
damage=50,
|
||||
effect=unimplemented,
|
||||
effect=condition_attack(no_retreat=True),
|
||||
),
|
||||
Attack(
|
||||
title="Raging Swing",
|
||||
@@ -31,7 +32,7 @@ card = PokemonCardDef(
|
||||
cost={PokemonTypes.FIGHTING: 2, PokemonTypes.COLORLESS: 2},
|
||||
damage=50,
|
||||
damage_operator="x",
|
||||
effect=unimplemented,
|
||||
effect=damage_per(damage_counters_on("self"), 50),
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,6 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.passives_common import debuff_defender_attacks
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="c049f814-276b-5712-86e2-e33601c1aaca",
|
||||
@@ -23,7 +24,7 @@ card = PokemonCardDef(
|
||||
title="Growl",
|
||||
game_text="During your opponent's next turn, the Defending Pok\u00e9mon's attacks do 20 less damage (before applying Weakness and Resistance).",
|
||||
cost={PokemonTypes.COLORLESS: 1},
|
||||
effect=unimplemented,
|
||||
effect=debuff_defender_attacks(20),
|
||||
),
|
||||
Attack(
|
||||
title="Flap",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, TrainerType
|
||||
from spirit.game.card_effects.attacks_common import bonus_if
|
||||
|
||||
|
||||
def _played_supporter_this_turn(ctx):
|
||||
return ctx.played_trainer_this_turn(lambda r: r[2] == TrainerType.SUPPORTER.value) > 0
|
||||
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="65db8eef-73af-5ed6-95d9-d3dfc1863e6d",
|
||||
@@ -24,7 +30,7 @@ card = PokemonCardDef(
|
||||
cost={PokemonTypes.LIGHTNING: 1, PokemonTypes.COLORLESS: 2},
|
||||
damage=30,
|
||||
damage_operator="+",
|
||||
effect=unimplemented,
|
||||
effect=bonus_if(_played_supporter_this_turn, 30),
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,4 +1,5 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.card_effects.attacks_common import recoil_attack
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
|
||||
card = PokemonCardDef(
|
||||
@@ -23,7 +24,7 @@ card = PokemonCardDef(
|
||||
game_text="This Pok\u00e9mon also does 30 damage to itself.",
|
||||
cost={PokemonTypes.LIGHTNING: 2, PokemonTypes.COLORLESS: 1},
|
||||
damage=90,
|
||||
effect=unimplemented,
|
||||
effect=recoil_attack(30),
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,27 @@
|
||||
from spirit.game.data_utils import StadiumCardDef, unimplemented
|
||||
from spirit.game.data_utils import Ability, Activations, StadiumCardDef
|
||||
from spirit.game.attributes import Rarities
|
||||
from spirit.game.card_effects.trainers import deck_nonempty, is_item_card
|
||||
|
||||
|
||||
async def pokestop_effect(ctx):
|
||||
"""That player may discard the top 3 of their deck; any Item cards
|
||||
discarded this way go into their hand instead."""
|
||||
top = ctx.deck_top(3)
|
||||
if not top:
|
||||
return
|
||||
await ctx.discard_cards(top)
|
||||
items = [c for c in top if is_item_card(c)]
|
||||
if items:
|
||||
await ctx.put_in_hand(items, reveal=False)
|
||||
|
||||
|
||||
POKESTOP_ABILITY = Ability(
|
||||
title="PokéStop",
|
||||
game_text="Once during each player's turn, that player may discard 3 cards from the top of their deck. If a player discarded any Item cards in this way, they put those Item cards into their hand.",
|
||||
activation=Activations.ONCE_PER_TURN,
|
||||
effect=pokestop_effect,
|
||||
condition=lambda board, player_id, stadium: deck_nonempty(board, player_id),
|
||||
)
|
||||
|
||||
card = StadiumCardDef(
|
||||
guid="57d9c8fc-1ed5-522a-b7de-93f1d280b715",
|
||||
@@ -11,5 +33,5 @@ card = StadiumCardDef(
|
||||
collector_number=68,
|
||||
set_code="PGO",
|
||||
rarity=Rarities.Uncommon,
|
||||
effect=unimplemented
|
||||
ability=POKESTOP_ABILITY,
|
||||
)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.attacks_common import spread_damage
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="3d5b2fb5-e726-5f34-bdd5-0429a30bcf7e",
|
||||
@@ -24,7 +25,7 @@ card = PokemonCardDef(
|
||||
game_text="This attack also does 20 damage to each Benched Pok\u00e9mon (both yours and your opponent's). (Don't apply Weakness and Resistance for Benched Pok\u00e9mon.)",
|
||||
cost={PokemonTypes.COLORLESS: 1},
|
||||
damage=20,
|
||||
effect=unimplemented,
|
||||
effect=spread_damage(20, side="both", also_base=True),
|
||||
),
|
||||
Attack(
|
||||
title="Tackle",
|
||||
|
||||
@@ -1,5 +1,41 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID
|
||||
from spirit.game.card_effects.pokemon import is_energy_card
|
||||
|
||||
|
||||
def _is_water_energy(card):
|
||||
types = card.get_attribute(AttrID.POKEMON_TYPES) or []
|
||||
return is_energy_card(card) and PokemonTypes.WATER.value in types
|
||||
|
||||
|
||||
def pump_shot_condition(board, player_id, pokemon):
|
||||
hand = board.find_player_area(player_id, "hand")
|
||||
if not hand or not any(_is_water_energy(c) for c in hand.children):
|
||||
return False
|
||||
opponent = next((pid for pid in board.player_ids if pid != player_id), None)
|
||||
if opponent is None:
|
||||
return False
|
||||
bench = board.find_player_area(opponent, "bench")
|
||||
return bool(bench) and len(bench.children) > 0
|
||||
|
||||
|
||||
async def pump_shot(ctx):
|
||||
"""Discard a Water Energy from hand; put 2 damage counters on 1 of your opponent's Benched Pokemon."""
|
||||
discarded = await ctx.discard_from_hand(
|
||||
1, predicate=_is_water_energy,
|
||||
prompt="Discard a Water Energy card to use Pump Shot",
|
||||
)
|
||||
if not discarded:
|
||||
return
|
||||
bench = ctx.opponent_bench()
|
||||
if not bench:
|
||||
return
|
||||
target = await ctx.choose_pokemon(
|
||||
bench, "Choose 1 of your opponent's Benched Pokémon"
|
||||
)
|
||||
if target is not None:
|
||||
await ctx.deal_damage(20, target=target, as_counters=True)
|
||||
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="76360f77-185a-5c93-9e8e-b9cc93cc38e9",
|
||||
@@ -21,14 +57,16 @@ card = PokemonCardDef(
|
||||
Ability(
|
||||
title="Pump Shot",
|
||||
game_text="You must discard a Water Energy card from your hand in order to use this Ability. Once during your turn, you may put 2 damage counters on 1 of your opponent's Benched Pok\u00e9mon.",
|
||||
effect=unimplemented,
|
||||
activation=Activations.ONCE_PER_TURN,
|
||||
condition=pump_shot_condition,
|
||||
effect=pump_shot,
|
||||
),
|
||||
Attack(
|
||||
title="Torrential Cannon",
|
||||
game_text="During your next turn, this Pok\u00e9mon can't use Torrential Cannon.",
|
||||
cost={PokemonTypes.WATER: 2, PokemonTypes.COLORLESS: 1},
|
||||
damage=170,
|
||||
effect=unimplemented,
|
||||
locks_next_turn=True,
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,25 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Triggers
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions
|
||||
from spirit.game.card_effects.attacks_common import condition_attack
|
||||
|
||||
|
||||
def _sunny_bloom_ready(board, player_id, pokemon):
|
||||
hand = board.find_player_area(player_id, "hand")
|
||||
deck = board.find_player_area(player_id, "deck")
|
||||
return bool(deck and deck.children) and hand is not None \
|
||||
and len(hand.children) < 4
|
||||
|
||||
|
||||
async def sunny_bloom(ctx):
|
||||
"""End of your turn: you may draw until you have 4 cards in your hand."""
|
||||
if not _sunny_bloom_ready(ctx.board, ctx.player_id, ctx.source):
|
||||
ctx.suppress_announce = True
|
||||
return
|
||||
if not await ctx.ask_yes_no("Use Sunny Bloom? Draw cards until you have "
|
||||
"4 cards in your hand."):
|
||||
ctx.suppress_announce = True
|
||||
return
|
||||
await ctx.draw_until(4)
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="2baf28dd-fef2-52c9-826b-1fbf5017d922",
|
||||
@@ -21,14 +41,17 @@ card = PokemonCardDef(
|
||||
Ability(
|
||||
title="Sunny Bloom",
|
||||
game_text="Once at the end of your turn (after your attack), you may use this Ability. Draw cards until you have 4 cards in your hand.",
|
||||
effect=unimplemented,
|
||||
trigger=Triggers.END_OF_TURN,
|
||||
condition=_sunny_bloom_ready,
|
||||
effect=sunny_bloom,
|
||||
),
|
||||
Attack(
|
||||
title="Pollen Hazard",
|
||||
game_text="Your opponent's Active Pok\u00e9mon is now Burned, Confused, and Poisoned.",
|
||||
cost={PokemonTypes.GRASS: 2, PokemonTypes.COLORLESS: 1},
|
||||
damage=90,
|
||||
effect=unimplemented,
|
||||
effect=condition_attack(SpecialConditions.BURNED, SpecialConditions.CONFUSED,
|
||||
SpecialConditions.POISONED),
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,50 @@
|
||||
from spirit.game.data_utils import ItemCardDef, unimplemented
|
||||
from spirit.game.attributes import Rarities
|
||||
from spirit.game.data_utils import ItemCardDef, evolves_from
|
||||
from spirit.game.attributes import Rarities, AttrID, PokemonStage
|
||||
from spirit.game.session.effects import is_basic_pokemon, is_pokemon_card
|
||||
|
||||
|
||||
def _stage2_matches(hand_cards, logic_name):
|
||||
return [
|
||||
c for c in hand_cards
|
||||
if is_pokemon_card(c)
|
||||
and c.get_attribute(AttrID.STAGE) == PokemonStage.STAGE2.value
|
||||
and evolves_from(c.archetype_id, logic_name)
|
||||
]
|
||||
|
||||
|
||||
def _turn_eligible_basics(board, player_id):
|
||||
turn_state = getattr(board, "turn_state", None)
|
||||
if turn_state is None:
|
||||
return []
|
||||
return [
|
||||
p for p in board.pokemon_in_play(player_id)
|
||||
if is_basic_pokemon(p) and turn_state.may_evolve_target(p.entity_id)
|
||||
]
|
||||
|
||||
|
||||
def _rare_candy_condition(board, player_id):
|
||||
return bool(_turn_eligible_basics(board, player_id))
|
||||
|
||||
|
||||
async def _rare_candy(ctx):
|
||||
"""Choose a Basic Pokemon in play; if you have a Stage 2 in hand that evolves from it, put that card onto it, skipping the Stage 1."""
|
||||
candidates = _turn_eligible_basics(ctx.board, ctx.player_id)
|
||||
if not candidates:
|
||||
return
|
||||
target = await ctx.choose_pokemon(candidates, "Choose a Basic Pokémon in play")
|
||||
if target is None:
|
||||
return
|
||||
logic_name = target.get_attribute(AttrID.EVOLUTION_LOGIC_NAME)
|
||||
stage2_hand = _stage2_matches(ctx.hand(), logic_name) if logic_name else []
|
||||
if not stage2_hand:
|
||||
return
|
||||
picks = await ctx.choose_cards(
|
||||
stage2_hand, 1, prompt="Choose a Stage 2 Pokémon to evolve into",
|
||||
)
|
||||
if not picks:
|
||||
return
|
||||
await ctx.evolve_pokemon(target, picks[0])
|
||||
|
||||
|
||||
card = ItemCardDef(
|
||||
guid="32e87eba-71ac-50f5-9e8e-46186f5d339b",
|
||||
@@ -11,5 +56,6 @@ card = ItemCardDef(
|
||||
collector_number=69,
|
||||
set_code="PGO",
|
||||
rarity=Rarities.Uncommon,
|
||||
effect=unimplemented
|
||||
effect=_rare_candy,
|
||||
condition=_rare_candy_condition,
|
||||
)
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
|
||||
|
||||
def _prizes_remaining(board, player_id):
|
||||
area = board.find_player_area(player_id, "prizePile")
|
||||
return len(area.children) if area else 0
|
||||
|
||||
|
||||
def _kinda_lazy_condition(board, player_id, pokemon):
|
||||
return _prizes_remaining(board, player_id) not in (2, 4, 6)
|
||||
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="92236790-2c73-56ed-b409-acf2bb56e82e",
|
||||
key="PGO",
|
||||
@@ -21,12 +31,12 @@ card = PokemonCardDef(
|
||||
Ability(
|
||||
title="Kinda Lazy",
|
||||
game_text="If you have exactly 2, 4, or 6 Prize cards remaining, this Pok\u00e9mon can't attack.",
|
||||
effect=unimplemented,
|
||||
),
|
||||
Attack(
|
||||
title="Heavy Impact",
|
||||
cost={PokemonTypes.COLORLESS: 4},
|
||||
damage=260,
|
||||
condition=_kinda_lazy_condition,
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,6 +1,16 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
|
||||
|
||||
def _prizes_remaining(board, player_id):
|
||||
area = board.find_player_area(player_id, "prizePile")
|
||||
return len(area.children) if area else 0
|
||||
|
||||
|
||||
def _kinda_lazy_condition(board, player_id, pokemon):
|
||||
return _prizes_remaining(board, player_id) not in (2, 4, 6)
|
||||
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="2fa3076d-77d0-50cb-afb3-6c3c81cbdc46",
|
||||
key="PGO",
|
||||
@@ -21,12 +31,12 @@ card = PokemonCardDef(
|
||||
Ability(
|
||||
title="Kinda Lazy",
|
||||
game_text="If you have exactly 2, 4, or 6 Prize cards remaining, this Pok\u00e9mon can't attack.",
|
||||
effect=unimplemented,
|
||||
),
|
||||
Attack(
|
||||
title="Heavy Impact",
|
||||
cost={PokemonTypes.COLORLESS: 4},
|
||||
damage=260,
|
||||
condition=_kinda_lazy_condition,
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,19 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions
|
||||
from spirit.game.card_effects.attacks_common import condition_attack
|
||||
|
||||
|
||||
def _opponent_one_prize_left(board, player_id, pokemon):
|
||||
opponent = next((p for p in board.player_ids if p != player_id), None)
|
||||
if not opponent:
|
||||
return False
|
||||
area = board.find_player_area(opponent, "prizePile")
|
||||
return bool(area) and len(area.children) == 1
|
||||
|
||||
|
||||
async def twilight_inspiration(ctx):
|
||||
"""Take 2 Prize cards (usable only when the opponent has exactly 1 left)."""
|
||||
await ctx.take_prizes(2)
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="12412fc3-817b-507d-8705-64286986f951",
|
||||
@@ -24,13 +38,14 @@ card = PokemonCardDef(
|
||||
game_text="Both Active Pok\u00e9mon are now Asleep.",
|
||||
cost={PokemonTypes.COLORLESS: 1},
|
||||
damage=20,
|
||||
effect=unimplemented,
|
||||
effect=condition_attack(SpecialConditions.ASLEEP, both_actives=True),
|
||||
),
|
||||
Attack(
|
||||
title="Twilight Inspiration",
|
||||
game_text="You can use this attack only if your opponent has exactly 1 Prize card remaining. Take 2 Prize cards.",
|
||||
cost={PokemonTypes.COLORLESS: 2},
|
||||
effect=unimplemented,
|
||||
effect=twilight_inspiration,
|
||||
condition=_opponent_one_prize_left,
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,14 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.card_effects.support_common import heal_attack, recover_from_discard, requires_discard
|
||||
from spirit.game.session.effects import is_item_card
|
||||
|
||||
hold_still = heal_attack(30, target="self")
|
||||
|
||||
ideal_fishing_day = recover_from_discard(
|
||||
predicate=is_item_card, count=1, minimum=1, reveal=False, to="hand",
|
||||
prompt="Choose an Item card to put into your hand.",
|
||||
)
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="e0fda57c-5575-5f1b-8484-6d4c3260ec86",
|
||||
@@ -22,13 +31,14 @@ card = PokemonCardDef(
|
||||
title="Hold Still",
|
||||
game_text="Heal 30 damage from this Pok\u00e9mon.",
|
||||
cost={PokemonTypes.COLORLESS: 1},
|
||||
effect=unimplemented,
|
||||
effect=hold_still,
|
||||
),
|
||||
Attack(
|
||||
title="Ideal Fishing Day",
|
||||
game_text="Put an Item card from your discard pile into your hand.",
|
||||
cost={PokemonTypes.WATER: 1},
|
||||
effect=unimplemented,
|
||||
effect=ideal_fishing_day,
|
||||
condition=requires_discard(is_item_card),
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,9 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, SpecialConditions
|
||||
from spirit.game.card_effects.attacks_common import condition_attack
|
||||
from spirit.game.card_effects.passives_common import (
|
||||
no_retreat_passive, is_in_active_spot, opposing_active,
|
||||
)
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="5796a3e5-68f2-5556-aee0-2216f67f5f11",
|
||||
@@ -21,14 +25,16 @@ card = PokemonCardDef(
|
||||
Ability(
|
||||
title="Block",
|
||||
game_text="As long as this Pok\u00e9mon is in the Active Spot, your opponent's Active Pok\u00e9mon can't retreat.",
|
||||
effect=unimplemented,
|
||||
passive=no_retreat_passive(
|
||||
lambda p, c: opposing_active(p, c) and is_in_active_spot(c)
|
||||
),
|
||||
),
|
||||
Attack(
|
||||
title="Collapse",
|
||||
game_text="This Pok\u00e9mon is now Asleep.",
|
||||
cost={PokemonTypes.COLORLESS: 4},
|
||||
damage=150,
|
||||
effect=unimplemented,
|
||||
effect=condition_attack(self_conditions=(SpecialConditions.ASLEEP,)),
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,44 @@
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, unimplemented
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities
|
||||
from spirit.game.data_utils import PokemonCardDef, Attack, Ability, Activations
|
||||
from spirit.game.attributes import PokemonTypes, PokemonStage, Rarities, AttrID, CardType
|
||||
|
||||
LUNATONE_NAME = "com.direwolfdigital.cake.data.archetypes.pokemon.Lunatone.Name"
|
||||
|
||||
|
||||
def _is_psychic_energy_card(card):
|
||||
types = card.get_attribute(AttrID.POKEMON_TYPES) or []
|
||||
return card.get_attribute(AttrID.CARD_TYPE) == CardType.ENERGY.value \
|
||||
and PokemonTypes.PSYCHIC.value in types
|
||||
|
||||
|
||||
def _my_lunatones(pokemon_list):
|
||||
return [p for p in pokemon_list
|
||||
if p.get_attribute(AttrID.EVOLUTION_LOGIC_NAME) == LUNATONE_NAME]
|
||||
|
||||
|
||||
def sun_energy_condition(board, player_id, pokemon):
|
||||
if not _my_lunatones(board.pokemon_in_play(player_id)):
|
||||
return False
|
||||
discard = board.find_player_area(player_id, "discard")
|
||||
return bool(discard) and any(_is_psychic_energy_card(c) for c in discard.children)
|
||||
|
||||
|
||||
async def sun_energy(ctx):
|
||||
lunatones = _my_lunatones(ctx.my_pokemon_in_play())
|
||||
cards = [c for c in ctx.discard_pile() if _is_psychic_energy_card(c)]
|
||||
if not lunatones or not cards:
|
||||
return
|
||||
if not await ctx.ask_yes_no(
|
||||
"Attach a Psychic Energy card from your discard pile to 1 of your Lunatone?"
|
||||
):
|
||||
return
|
||||
picks = await ctx.choose_cards(cards, 1, prompt="Choose a Psychic Energy card to attach")
|
||||
if not picks:
|
||||
return
|
||||
target = await ctx.choose_pokemon(
|
||||
lunatones, "Choose a Lunatone to attach the Energy to"
|
||||
) or lunatones[0]
|
||||
await ctx.attach_energy(picks[0], target)
|
||||
|
||||
|
||||
card = PokemonCardDef(
|
||||
guid="2470d314-35d8-54be-b8c4-3f0f504f0765",
|
||||
@@ -21,7 +60,9 @@ card = PokemonCardDef(
|
||||
Ability(
|
||||
title="Sun Energy",
|
||||
game_text="Once during your turn, you may attach a Psychic Energy card from your discard pile to 1 of your Lunatone.",
|
||||
effect=unimplemented,
|
||||
activation=Activations.ONCE_PER_TURN,
|
||||
condition=sun_energy_condition,
|
||||
effect=sun_energy,
|
||||
),
|
||||
Attack(
|
||||
title="Spinning Attack",
|
||||
|
||||
@@ -1,5 +1,38 @@
|
||||
from spirit.game.data_utils import SupporterCardDef, unimplemented
|
||||
from spirit.game.attributes import Rarities
|
||||
from spirit.game.data_utils import SupporterCardDef
|
||||
from spirit.game.attributes import Rarities, PokemonTypes, AttrID
|
||||
from spirit.game.card_effects.trainers import is_basic_energy_card
|
||||
|
||||
|
||||
def _is_lightning_energy_card(card):
|
||||
if not is_basic_energy_card(card):
|
||||
return False
|
||||
types = card.get_attribute(AttrID.POKEMON_TYPES) or []
|
||||
return PokemonTypes.LIGHTNING.value in types
|
||||
|
||||
|
||||
async def _spark(ctx):
|
||||
drawn = await ctx.draw_cards(2)
|
||||
if drawn <= 0:
|
||||
return
|
||||
heads = (await ctx.flip_coins(1, "Spark"))[0]
|
||||
if not heads:
|
||||
return
|
||||
bench = ctx.my_bench()
|
||||
if not bench:
|
||||
return
|
||||
energies = [c for c in ctx.discard_pile() if _is_lightning_energy_card(c)]
|
||||
if not energies:
|
||||
return
|
||||
picks = await ctx.choose_cards(
|
||||
energies, 1, minimum=1, prompt="Choose a Lightning Energy card from your discard pile.",
|
||||
)
|
||||
if not picks:
|
||||
return
|
||||
target = await ctx.choose_pokemon(bench, "Choose 1 of your Benched Pokémon")
|
||||
if target is None:
|
||||
return
|
||||
await ctx.attach_energy(picks[0], target)
|
||||
|
||||
|
||||
card = SupporterCardDef(
|
||||
guid="332538c4-6a38-586d-a9f3-d4c321203813",
|
||||
@@ -11,5 +44,5 @@ card = SupporterCardDef(
|
||||
collector_number=70,
|
||||
set_code="PGO",
|
||||
rarity=Rarities.Uncommon,
|
||||
effect=unimplemented
|
||||
effect=_spark,
|
||||
)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user