from __future__ import annotations import copy from typing import Dict from .growth import compute_stats from .skills import ( S_ATTACK_DOWN, S_ATTACK_UP, S_CRIT_DOWN, S_DEFENSE_DOWN, S_DEFENSE_UP, S_DENTING, S_HEAL_REDUCTION, S_ICICLES, S_INTOXICATE, S_MADNESS, S_OBLIVION, S_SLEEP, S_SNOWFLAKES, S_SPEED_DOWN, S_TETHER, S_VULNERABLE, EFF_DOT, EFF_HEAL, EFF_HOT, EFF_SHIELD, EFF_STATUS, EFF_WILT, Effect, Skill, TARGET_ALL_ALLIES, TARGET_ALL_ENEMIES, TARGET_ALLY, TARGET_ANY, TARGET_ENEMY, TARGET_SELF, TARGET_TWO_ENEMIES, ) from .types import TYPE_GARDEN, TYPE_ICE, TYPE_NOCTURNAL, TYPE_RADIANT, TYPE_WILDFLOWER from .unit import ROLE_ATTACKER, ROLE_SUPPORT, ROLE_TANK, TEAM_ENEMY, TEAM_PLAYER, Unit def _sk(name, power=0, target=TARGET_ENEMY, cd=0, hits=1, kind="damage", effects=None, desc="", priority=4, mechanic="", power_alt=0, extra_action=False, form=""): return Skill(name=name, power=power, cooldown=cd, target=target, kind=kind, hits=hits, effects=effects or [], description=desc, priority=priority, mechanic=mechanic, power_alt=power_alt, extra_action=extra_action, requires_form=form) def _st(key, value, dur=0): return Effect(EFF_STATUS, value=value, duration=dur, key=key) def _wilt(pct): return Effect(EFF_WILT, value=pct) def _dot(dmg, turns): return Effect(EFF_DOT, value=dmg, duration=turns) def _hot(amount, turns): return Effect(EFF_HOT, value=amount, duration=turns) def _shield(value): return Effect(EFF_SHIELD, value=value) POOL: Dict[str, dict] = { "Zinnia": dict(role=ROLE_ATTACKER, type=TYPE_RADIANT, stats=(63, 68, 51, 48), passive="zinnia_solar", skills=[ _sk("Blinding Slash", 20, hits=2, desc="20 dmg, 2 hits"), _sk("Glitter", 10, hits=5, cd=3, mechanic="hits_per_effect", priority=1, desc="10 dmg, 5 hits, +1 per effect on target"), _sk("Glimmer", 10, hits=5, cd=3, mechanic="hits_per_self_effect", priority=2, desc="10 dmg, 5 hits, +1 per effect on self"), ]), "Sunflower": dict(role=ROLE_SUPPORT, type=TYPE_RADIANT, stats=(57, 68, 52, 46), passive="sunflower_incandescent", passive_value=0.7, skills=[ _sk("Beam", 50, desc="50 dmg"), _sk("Face The Sun", kind="utility", target=TARGET_ALL_ALLIES, cd=3, priority=1, effects=[_hot(35, 3)], desc="Heal all allies 35/turn for 3 turns"), _sk("Daybreak", kind="utility", target=TARGET_ALLY, cd=3, mechanic="reset_cooldowns", priority=2, desc="Reset an ally's cooldowns"), ]), "Daisy": dict(role=ROLE_SUPPORT, type=TYPE_RADIANT, stats=(57, 72, 48, 42), passive="daisy_gentle", skills=[ _sk("Flutter", 40, mechanic="heal_random_ally", desc="40 dmg, heals a random ally"), _sk("Sunkissed Glow", power=50, kind="heal", target=TARGET_ALL_ALLIES, cd=2, priority=1, desc="Heal all allies for 50"), _sk("Motivate", kind="heal", target=TARGET_ALLY, cd=4, mechanic="full_heal", priority=2, desc="Heal an ally to full health"), ]), "Marigold": dict(role=ROLE_TANK, type=TYPE_RADIANT, stats=(43, 50, 68, 73), passive="marigold_golden", skills=[ _sk("Rustle", 25, desc="25 dmg"), _sk("Puff Up", kind="utility", target=TARGET_SELF, cd=3, mechanic="puff_up", priority=1, desc="Heal 30% max HP, cleanse self"), _sk("Absorb", kind="utility", target=TARGET_SELF, cd=3, mechanic="absorb", priority=2, desc="Take 20% less damage until next turn"), ]), "Daffodil": dict(role=ROLE_TANK, type=TYPE_RADIANT, stats=(55, 47, 64, 70), passive="daffodil_toxic", skills=[ _sk("Consume", 45, mechanic="consume_wilt", desc="45 dmg, heal for damage dealt if target Wilted"), _sk("Poison Ravish", 50, target=TARGET_ALL_ENEMIES, cd=3, mechanic="wilt_boost", power_alt=75, priority=1, desc="50/75 dmg to all, more if Wilted"), _sk("Deteriorate", 50, target=TARGET_ALL_ENEMIES, cd=3, effects=[_wilt(50)], priority=2, desc="50 dmg to all, Wilt for 50% of damage"), ]), "Rose": dict(role=ROLE_ATTACKER, type=TYPE_GARDEN, stats=(74, 66, 52, 49), passive="rose_briars", skills=[ _sk("Thorned Strike", 60, effects=[_wilt(20)], desc="60 dmg, Wilt 20%"), _sk("Petal Kiss", 90, cd=3, mechanic="ignore_defense", power_alt=0.3, priority=1, desc="90 dmg, ignores 30% defense"), _sk("Brambles", 75, target=TARGET_ALL_ENEMIES, cd=3, effects=[_wilt(15), _st(S_HEAL_REDUCTION, 40, 2)], priority=2, desc="75 dmg to all, Wilt 15%, -40% healing 2 turns"), ]), "Peony": dict(role=ROLE_ATTACKER, type=TYPE_GARDEN, stats=(77, 58, 61, 61), passive="peony_robust", skills=[ _sk("Petal Punch", 55, desc="55 dmg"), _sk("Snap", 75, cd=3, mechanic="ignore_defense", power_alt=0.15, priority=1, effects=[_st(S_DEFENSE_DOWN, 20, 3)], desc="75 dmg, -20% defense 3 turns"), _sk("Denting Blows", 65, target=TARGET_TWO_ENEMIES, cd=3, effects=[_st(S_DENTING, 1)], priority=2, desc="65 dmg to 2, permanent +3% damage taken (stacks)"), ]), "Magnolia": dict(role=ROLE_ATTACKER, type=TYPE_GARDEN, stats=(73, 64, 55, 58), passive="magnolia_nobility", skills=[ _sk("Royal Smack", 50, effects=[_st(S_DEFENSE_DOWN, 20, 1)], desc="50 dmg, -20% defense 1 turn"), _sk("Crown", 55, cd=3, mechanic="strip_buffs", priority=1, desc="55 dmg, removes target buffs"), _sk("Regal Order", 90, cd=3, mechanic="shield_lifesteal", power_alt=15, priority=2, effects=[_st("stun", 1)], desc="90 dmg, stun 1 turn, shield self 15% of damage"), ]), "Hydrangea": dict(role=ROLE_ATTACKER, type=TYPE_GARDEN, stats=(68, 70, 52, 54), passive="hydrangea_ph", skills=[ _sk("Cloud", 45, mechanic="ph_cloud", desc="45 dmg; Acidic: Wilt 30%, Alkaline: heal allies 30%"), _sk("Alkaline Blossom", kind="utility", target=TARGET_SELF, cd=3, form="alkaline", mechanic="extend_timers", priority=1, extra_action=True, desc="Extend buffs/debuffs by 1 turn (extra action)"), _sk("Acidic Blossom", 80, target=TARGET_ALL_ENEMIES, cd=3, form="acidic", effects=[_wilt(40)], priority=2, extra_action=True, desc="80 dmg to all, Wilt 40% (extra action)"), ]), "Sweet Pea": dict(role=ROLE_ATTACKER, type=TYPE_GARDEN, stats=(71, 71, 36, 45), passive="sweetpea_scent", skills=[ _sk("Vine Whip", 40, mechanic="tether_lowest", desc="40 dmg, tethers lowest-HP ally for 1 turn"), _sk("Sugar Bloom", 70, target=TARGET_ALL_ENEMIES, cd=3, priority=1, desc="70 dmg to all enemies"), _sk("Fragrant Bond", kind="utility", target=TARGET_ALL_ALLIES, cd=3, effects=[_st(S_TETHER, 1, 1)], priority=2, desc="Tether all allies; tethered allies heal from Sweet Pea's damage"), ]), "Chrysanthemum": dict(role=ROLE_TANK, type=TYPE_GARDEN, stats=(38, 41, 70, 76), passive="chrys_longevity", skills=[ _sk("Imperial Smite", 45, mechanic="def_bonus", desc="45 dmg + bonus from defense"), _sk("Autumn Shield", power=50, kind="shield", target=TARGET_ALL_ALLIES, cd=2, priority=1, effects=[_st(S_DEFENSE_UP, 8, 1)], desc="Shield all allies (scales with Attack), +defense 1 turn"), _sk("Golden Dynasty", kind="utility", target=TARGET_ALL_ALLIES, cd=4, mechanic="perma_hp_def", priority=2, desc="Permanently +15% HP and +10% defense for all allies"), ]), "Lily": dict(role=ROLE_ATTACKER, type=TYPE_WILDFLOWER, stats=(79, 65, 41, 36), passive="lily_passionate", passive_value=0.5, skills=[ _sk("Flora Blast", 30, target=TARGET_ALL_ENEMIES, desc="30 dmg to all enemies"), _sk("Fervent Blossom", 50, target=TARGET_ALL_ENEMIES, cd=2, mechanic="low_hp_boost", power_alt=80, priority=1, desc="50/80 dmg to all, more under 40% HP"), _sk("Whirlwind", 95, target=TARGET_ALL_ENEMIES, cd=4, mechanic="high_hp_boost", power_alt=120, priority=2, desc="95/120 dmg to all, more above 80% HP"), ]), "Hibiscus": dict(role=ROLE_ATTACKER, type=TYPE_WILDFLOWER, stats=(72, 67, 49, 55), passive="hibiscus_intensifying", skills=[ _sk("Tropical Burst", 50, desc="50 dmg"), _sk("Crimson Strike", 70, cd=3, mechanic="thresh_boost", power_alt=90, priority=1, desc="70/90 dmg, more if target is high or low HP"), _sk("Crimson Brew", 65, target=TARGET_ALL_ENEMIES, cd=3, mechanic="crimson_brew", priority=2, desc="65 dmg to all, +attack to self for 2 turns"), ]), "Poppy": dict(role=ROLE_ATTACKER, type=TYPE_WILDFLOWER, stats=(76, 63, 59, 55), passive="poppy_hypnotic", skills=[ _sk("Scarlet Lash", 55, desc="55 dmg"), _sk("Satin Smite", 70, cd=3, effects=[_st(S_INTOXICATE, 1, 2)], priority=1, desc="70 dmg, Intoxicate 2 turns"), _sk("Narcosis", 70, target=TARGET_TWO_ENEMIES, cd=3, effects=[_st(S_CRIT_DOWN, 20, 2), _st(S_OBLIVION, 1, 2)], priority=2, desc="70 dmg to 2, -20% crit chance and Oblivion 2 turns"), ]), "Tulip": dict(role=ROLE_ATTACKER, type=TYPE_WILDFLOWER, stats=(69, 65, 53, 57), passive="tulip_equinox", skills=[ _sk("Bulb Blast", 40, target=TARGET_TWO_ENEMIES, desc="40 dmg to 2 enemies"), _sk("Spring Rain", 55, target=TARGET_ALL_ENEMIES, cd=3, effects=[_st(S_ATTACK_DOWN, 10, 2)], priority=1, desc="55 dmg to all, -attack 2 turns"), _sk("Vibrant Bells", power=80, kind="shield", target=TARGET_ALL_ALLIES, cd=3, priority=2, effects=[_st("bulb_reflect", 1)], desc="Shield all allies (scales with Attack); reflects Bulb Blast"), ]), "Lotus": dict(role=ROLE_SUPPORT, type=TYPE_WILDFLOWER, stats=(59, 75, 44, 53), passive="lotus_rebirth", skills=[ _sk("Revitalize", power=40, kind="heal", target=TARGET_ALLY, mechanic="scale_heal", desc="Heal an ally (scales with Attack)"), _sk("Mud Resilience", kind="utility", target=TARGET_ALL_ALLIES, cd=2, effects=[_st(S_ATTACK_UP, 10, 2)], priority=1, desc="+attack for all allies 2 turns"), _sk("Purify", kind="utility", target=TARGET_ALL_ALLIES, cd=4, mechanic="clear_debuffs_allies", priority=2, extra_action=True, desc="Remove all debuffs from all allies (extra action)"), ]), "Dandelion": dict(role=ROLE_SUPPORT, type=TYPE_WILDFLOWER, stats=(62, 74, 45, 37), passive="dandelion_cover", skills=[ _sk("Drifting Seed", 40, effects=[_st(S_ATTACK_DOWN, 10, 1)], desc="40 dmg, -attack 1 turn"), _sk("Bloom", power=90, kind="shield", target=TARGET_ALLY, cd=2, priority=1, desc="Shield an ally (scales with Attack)"), _sk("Make A Wish", power=15, kind="shield", target=TARGET_ALL_ALLIES, cd=4, effects=[_hot(20, 3)], priority=2, desc="Shield all allies (scales with Attack), heal 20/turn for 3 turns"), ]), "Nightshade": dict(role=ROLE_ATTACKER, type=TYPE_NOCTURNAL, stats=(76, 69, 45, 53), passive="nightshade_corruption", skills=[ _sk("Astra", 60, desc="60 dmg"), _sk("Falling Star", 25, hits=4, cd=3, priority=1, desc="25 dmg, 4 hits"), _sk("Dark Corrosion", kind="utility", target=TARGET_ALL_ENEMIES, cd=3, effects=[_dot(70, 3)], priority=2, desc="70 dmg/turn to all for 3 turns (stacks)"), ]), "Moonflower": dict(role=ROLE_ATTACKER, type=TYPE_NOCTURNAL, stats=(77, 66, 54, 43), passive="moonflower_night", skills=[ _sk("Lunar Beam", 45, mechanic="ramping", desc="45 dmg, +5 every use"), _sk("Night Fog", kind="utility", target=TARGET_ALL_ENEMIES, cd=3, effects=[_st(S_HEAL_REDUCTION, 30, 2), _st(S_VULNERABLE, 1)], priority=1, desc="-healing 2 turns, Vulnerable 1 on all"), _sk("Glimmering Moon", 65, target=TARGET_ALL_ENEMIES, cd=3, mechanic="moon_double", priority=2, desc="65 dmg to all, chance to hit twice"), ]), "Orchid": dict(role=ROLE_ATTACKER, type=TYPE_NOCTURNAL, stats=(74, 68, 47, 64), passive="orchid_epiphytic", passive_value=0.4, skills=[ _sk("Branch", 60, desc="60 dmg"), _sk("Sow", kind="utility", target=TARGET_ENEMY, cd=3, mechanic="sow", priority=1, desc="Plant a seed that erupts for 115 dmg in 2 turns"), _sk("Sweeping Elegance", 70, target=TARGET_ALL_ENEMIES, cd=3, mechanic="strip_buffs_aoe", priority=2, desc="70 dmg to all, removes a buff each"), ]), "Lavender": dict(role=ROLE_ATTACKER, type=TYPE_NOCTURNAL, stats=(72, 77, 36, 43), passive="lavender_soporific", skills=[ _sk("Tranquilize", 30, effects=[_st(S_SPEED_DOWN, 3, 1), _st(S_VULNERABLE, 1)], desc="30 dmg, -speed 1 turn, Vulnerable 1"), _sk("Soothing Lullaby", 50, target=TARGET_ALL_ENEMIES, cd=2, effects=[_st(S_CRIT_DOWN, 10, 2)], priority=1, desc="50 dmg to all, -10% crit 2 turns"), _sk("Sedate", 60, cd=4, effects=[_st(S_SLEEP, 2)], priority=2, desc="60 dmg, Sleep"), ]), "Venus Flytrap": dict(role=ROLE_TANK, type=TYPE_NOCTURNAL, stats=(55, 44, 73, 68), passive="flytrap_feed", skills=[ _sk("Devour", 45, desc="45 dmg"), _sk("Lying In Wait", kind="utility", target=TARGET_SELF, cd=3, mechanic="lying_wait", priority=1, desc="Enemy attacks charge Dissolve"), _sk("Dissolve", 25, hits=2, cd=3, mechanic="dissolve", priority=2, desc="25 dmg twice, +10 per charge"), ]), "Snowdrop": dict(role=ROLE_ATTACKER, type=TYPE_ICE, stats=(73, 78, 52, 38), passive="snowdrop_winter", skills=[ _sk("Snow Pierce", 50, effects=[_st(S_DEFENSE_DOWN, 20, 1)], desc="50 dmg, -20% defense 1 turn"), _sk("Chill", kind="utility", target=TARGET_ENEMY, cd=2, effects=[_dot(45, 2)], priority=1, desc="45 dmg/turn for 2 turns"), _sk("Whiteout", 10, target=TARGET_ALL_ENEMIES, cd=4, mechanic="delay_all", priority=2, desc="10 dmg to all, delays their next turns"), ]), "Primrose": dict(role=ROLE_ATTACKER, type=TYPE_ICE, stats=(77, 73, 36, 42), passive="primrose_coldhearted", skills=[ _sk("Swipe", 40, desc="40 dmg"), _sk("Frostbite", 75, cd=3, effects=[_st(S_SPEED_DOWN, 3, 2), _st(S_HEAL_REDUCTION, 50, 2)], priority=1, desc="75 dmg, -speed and -50% healing 2 turns"), _sk("Icy Glare", 60, cd=3, effects=[_wilt(40)], priority=2, desc="60 dmg, Wilt 40%"), ]), "Winter Jasmine": dict(role=ROLE_ATTACKER, type=TYPE_ICE, stats=(67, 73, 38, 55), passive="jasmine_janus", skills=[ _sk("Solstice Spark", 45, desc="45 dmg"), _sk("Thaw", kind="utility", target=TARGET_ALLY, cd=3, mechanic="vigil", priority=1, desc="Remove debuffs, shield (scales with Attack); when hit, counter Solstice Spark"), _sk("Catching Snowflakes", kind="utility", target=TARGET_ALL_ALLIES, cd=3, mechanic="snowflakes", priority=2, extra_action=True, desc="All allies' hits +10 (stacks). Extra action"), ]), "Hellebore": dict(role=ROLE_SUPPORT, type=TYPE_ICE, stats=(76, 71, 33, 48), passive="hellebore_toxin", skills=[ _sk("Pacify", power=45, kind="heal", target=TARGET_ALLY, mechanic="pacify", desc="Heal an ally; removes Madness if below 40% HP"), _sk("Madness", kind="utility", target=TARGET_ANY, cd=3, effects=[_st(S_MADNESS, 1)], priority=1, desc="Apply Madness to a plant"), _sk("Gradual Annihilation", 40, cd=3, effects=[_wilt(200)], priority=2, desc="40 dmg, Wilt for 200% of damage"), ]), "Camelia": dict(role=ROLE_TANK, type=TYPE_ICE, stats=(60, 49, 68, 71), passive="camelia_frigid", skills=[ _sk("Hail", 55, effects=[_st(S_ICICLES, 1)], desc="55 dmg, gain 1 Icicle"), _sk("Igloo", power=70, kind="shield", target=TARGET_SELF, cd=2, priority=1, desc="Shield self (scales with Attack); gain 2 Icicles per hit while shielded"), _sk("Blizzard", kind="utility", target=TARGET_ALL_ENEMIES, cd=4, mechanic="shatter_icicles", priority=2, desc="Shatter Icicles to damage all"), ]), # Common critters: each exists in all five types (the sprite is shared). # They're always weaker than real plants and carry no passive. "Pinwheel": dict(role=ROLE_ATTACKER, type=TYPE_GARDEN, stats=(30, 35, 28, 42), skills=[ _sk("Spin Slash", 25, desc="25 dmg to one enemy"), ]), "Butterfly": dict(role=ROLE_SUPPORT, type=TYPE_GARDEN, stats=(30, 40, 26, 40), skills=[ _sk("Nectar", power=15, kind="heal", target=TARGET_ALLY, cd=2, desc="Heal an ally (scales with Attack)"), _sk("Cocoon", power=25, kind="shield", target=TARGET_ALLY, cd=2, desc="Shield an ally (scales with Attack)"), ]), "Toadstool": dict(role=ROLE_TANK, type=TYPE_GARDEN, stats=(30, 30, 42, 54), skills=[ _sk("Spore Bash", 22, mechanic="spore_siphon", power_alt=25, desc="22 dmg, heal self for 25% of damage dealt"), ]), # __MORE_PLANTS__ } HP_MULT = 2.5 # balance: defense is now a % reduction, so HP needs less inflation RARITY = { # common critters "Pinwheel": "common", "Butterfly": "common", "Toadstool": "common", # super super rare (was legendary) "Sunflower": "super_super_rare", "Hydrangea": "super_super_rare", "Poppy": "super_super_rare", "Lavender": "super_super_rare", "Snowdrop": "super_super_rare", "Hellebore": "super_super_rare", "Zinnia": "super_super_rare", # super rare (was epic) "Daisy": "super_rare", "Magnolia": "super_rare", "Sweet Pea": "super_rare", "Chrysanthemum": "super_rare", "Lotus": "super_rare", "Nightshade": "super_rare", "Moonflower": "super_rare", "Winter Jasmine": "super_rare", "Camelia": "super_rare", "Tulip": "super_rare", "Rose": "super_rare", "Lily": "super_rare", "Orchid": "super_rare", # rare (was common + rare) "Hibiscus": "rare", "Dandelion": "rare", "Venus Flytrap": "rare", "Marigold": "rare", "Daffodil": "rare", "Peony": "rare", "Primrose": "rare", } # Plant rarities. "common" (Golems) and "ultra_rare" (Ubers/Fruits) have no plants yet. RARITY_ORDER = { "common": 0, "rare": 1, "super_rare": 2, "super_super_rare": 3, "ultra_rare": 4, } RARITY_LABELS = { "common": "Common", "rare": "Rare", "super_rare": "Super Rare", "super_super_rare": "Super Super Rare", "ultra_rare": "Ultra Rare", } RARITY_COLORS = { "common": (215, 215, 220), # White "rare": (110, 170, 235), # Blue "super_rare": (170, 120, 220), # Purple "super_super_rare": (245, 180, 70), # Gold "ultra_rare": (235, 75, 75), # Red "epic": (170, 120, 220), # relics (legacy) "legendary": (245, 180, 70), # relics (legacy) } # Critters that share one sprite across all five types; the type is assigned # per unit (and shown with type icons in the collection). VARIABLE_TYPE_UNITS = ("Pinwheel", "Butterfly", "Toadstool") PASSIVES = { "zinnia_solar": ("Solar Decay", "Enemies Zinnia attacks deal 1% less damage per hit, up to 30%."), "sunflower_incandescent": ("Incandescent", "Heal the ally with the lowest health 70% of Attack each turn."), "daisy_gentle": ("Gentle Encouragement", "Healing an ally removes a debuff and boosts their speed."), "marigold_golden": ("Golden Gleam", "The first hit each turn is reduced by 30%."), "daffodil_toxic": ("Toxic Sap", "When attacked, Wilt the attacker for 50%; drain their Wilt to heal instead."), "rose_briars": ("Briars", "Reflect 40% of damage taken back to the attacker as Wilt."), "peony_robust": ("Robust", "Attacks ignore 20% of defense."), "magnolia_nobility": ("Nobility", "Immune to stuns; periodic debuffs last 1 turn less."), "hydrangea_ph": ("pH Shift", "Switches between Acidic and Alkaline each turn."), "sweetpea_scent": ("Sweet Scent", "Tethered allies take 10% less damage and heal 30% more."), "chrys_longevity": ("Longevity", "Gain 3% defense when hit."), "lily_passionate": ("Passionate", "+Attack and +Speed while enemies are below 50% health."), "hibiscus_intensifying": ("Intensifying", "Deal more damage to enemies with more max health."), "poppy_hypnotic": ("Hypnotic", "10% chance per attack to Intoxicate (1 turn) and Oblivion (1 turn) an enemy. Intoxicate: half the victim's damage splashes onto one of their own allies. Oblivion: they may only use their weakest skill."), "tulip_equinox": ("Verdant Equinox", "Allies deal 10% more damage while shielded."), "lotus_rebirth": ("Rebirth", "Revive the first fallen ally at 50% health."), "dandelion_cover": ("Cover", "The first time an ally drops below 40% HP, shield them for 50% of max HP."), "nightshade_corruption": ("Corruption", "When an enemy dies with Dark Corrosion, it spreads to the next most vulnerable enemy. Dark Corrosion also applies a random stat debuff to the target for 2 turns."), "moonflower_night": ("Everlasting Night", "Damage all enemies each turn, growing stronger; Glimmering Moon's double-hit chance rises 10% each turn."), "orchid_epiphytic": ("Epiphytic", "+Speed and +Attack while enemies are below 40% health."), "lavender_soporific": ("Soporific", "+Speed and +Attack while an enemy is asleep."), "flytrap_feed": ("Feed", "Heal for 20% of damage dealt."), "snowdrop_winter": ("Winter Breeze", "Slow all enemies at start; Chill the attacker of a fallen ally."), "primrose_coldhearted": ("Coldhearted", "The first skill bypasses tanks; afterwards only Frostbite does."), "jasmine_janus": ("Janus Bloom", "Reduce the first damage each ally takes by 80%."), "hellebore_toxin": ("Toxin", "Madness allies deal +50% and take +30%; enemies deal +30% and take +50%."), "camelia_frigid": ("Frigid", "Gain an Icicle when hit."), } def plant_rarity(name: str) -> str: return RARITY.get(name, "common") def _apply_injection(name: str, team: int, skills): """Replace a plant's skill with its persisted injected ability, if any. Only the player's own plants carry injections (enemies never inherit them). Returns (injected_index, injected_name) or (-1, "").""" if team != TEAM_PLAYER: return -1, "" from . import collection # lazy: collection imports plants at module level from .skills import skill_from_dict inj = collection.injection_of(name) idx = inj.get("index", 1) if inj else -1 if inj and 0 < idx < len(skills): skills[idx] = skill_from_dict(inj["skill"]) return idx, inj["skill"]["name"] return -1, "" def make(name: str, team: int, ptype: str = None) -> Unit: spec = POOL[name] atk, spd, dfn, hp = spec["stats"] skills = [copy.deepcopy(s) for s in spec["skills"]] inj_index, inj_name = _apply_injection(name, team, skills) # Display order = list order (basic -> 2nd -> 3rd). Selection priority is # position-based: the LAST skill is the strongest, so it is preselected first. for i, s in enumerate(skills): s.priority = len(skills) - i unit_type = ptype if ptype is not None or name in VARIABLE_TYPE_UNITS else spec["type"] unit = Unit(name, team, unit_type, max_hp=int(hp * HP_MULT), attack=atk, speed=spd, defense=dfn, skills=skills, role=spec["role"], passive=spec.get("passive", ""), passive_value=spec.get("passive_value", 0)) unit.injected_index = inj_index unit.injected_name = inj_name _apply_runes(name, team, unit) return unit def _apply_runes(name: str, team: int, unit: Unit) -> None: """Load the player's equipped runes for `name` onto the unit as (effect, pct) pairs. Only the player's own plants carry runes (enemies never inherit them).""" if team != TEAM_PLAYER: return from . import collection # lazy: collection imports plants at module level from . import runes equipped = collection.equipped_runes(name) if not equipped: return by_id = {r["id"]: r for r in collection.rune_inventory()} effects = [] for rune_id in equipped: rune = by_id.get(rune_id) if rune and rune["color"] in runes.RUNE_EFFECTS: effects.append((runes.effect_for(rune["color"]), rune.get("pct", 30) / 100.0)) unit.runes = effects def make_leveled(name: str, team: int, level: int = 1, stage: int = 1, ptype: str = None) -> Unit: u = make(name, team, ptype) if level > 1 or stage > 1: atk, spd, dfn, hp = compute_stats(spec := POOL[name]["stats"], level, stage) u.attack = atk u.speed = spd u.defense = dfn u.max_hp = int(hp * HP_MULT) u.hp = u.max_hp u.level = level u.stage = stage return u def build_pool() -> Dict[str, Unit]: return {name: make(name, TEAM_PLAYER) for name in POOL}