Skip to main content

companions.py

"""Companion character database: 20x3★, 10x4★, 5x5★."""
from __future__ import annotations
import random
from data_types import *
from character import Character, JOB_BASE_STATS, JOB_GROWTH
from skills_db import SKILL_DB, JOB_SKILL_POOL


def _make_companion(name, job, weapon, element, rarity,
                    stat_mult=1.0, growth_mult=1.0) -> Character:
    base = JOB_BASE_STATS[job].copy()
    g = JOB_GROWTH[job]

    # Apply rarity scaling
    base.hp   = int(base.hp   * stat_mult)
    base.mp   = int(base.mp   * stat_mult)
    base.patk = int(base.patk * stat_mult)
    base.matk = int(base.matk * stat_mult)
    base.pdef = int(base.pdef * stat_mult)
    base.mdef = int(base.mdef * stat_mult)
    base.spd  = int(base.spd  * stat_mult)

    # Scale growth rates
    scaled_g = GrowthRates(
        hp   = min(0.99, g.hp   * growth_mult),
        mp   = min(0.99, g.mp   * growth_mult),
        patk = min(0.99, g.patk * growth_mult),
        matk = min(0.99, g.matk * growth_mult),
        pdef = min(0.99, g.pdef * growth_mult),
        mdef = min(0.99, g.mdef * growth_mult),
        spd  = min(0.99, g.spd  * growth_mult),
        eva  = min(0.99, g.eva  * growth_mult),
        acc  = min(0.99, g.acc  * growth_mult),
        crit = min(0.99, g.crit * growth_mult),
    )

    pool = JOB_SKILL_POOL[job]
    basics = [sid for sid in pool if SKILL_DB[sid].tier == SkillTier.BASIC][:4]
    inters = [sid for sid in pool if SKILL_DB[sid].tier == SkillTier.INTERMEDIATE][:4]
    ultims = [sid for sid in pool if SKILL_DB[sid].tier == SkillTier.ULTIMATE][:2]

    chosen_basic = random.sample(basics, min(2, len(basics)))
    chosen_inter = random.sample(inters, min(2, len(inters)))
    chosen_ultim = random.sample(ultims, min(1, len(ultims)))

    skill_pool = chosen_basic + chosen_inter + chosen_ultim
    unlocked = chosen_basic[:]

    return Character(
        name=name, job=job, weapon=weapon, element=element, rarity=rarity,
        base_stats=base, growth=scaled_g,
        skill_pool=skill_pool, unlocked_skills=unlocked
    )


# ══════════════════════════════════════════════════════════════════════════
# COMPANION TEMPLATES (factories — called fresh each game)
# ══════════════════════════════════════════════════════════════════════════

# stat_mult: 3★=0.90~0.95, 4★=1.10~1.20, 5★=1.35~1.50
# growth_mult: 3★=0.85~0.90, 4★=1.05~1.15, 5★=1.25~1.40

THREE_STAR_TEMPLATES = [
    # name, job, weapon, element, stat_mult, growth_mult
    ("Rook",    JobClass.WARRIOR,    WeaponType.SWORD,  Element.FIRE,      0.90, 0.88),
    ("Gara",    JobClass.KNIGHT,     WeaponType.SWORD,  Element.EARTH,     0.92, 0.87),
    ("Tifa",    JobClass.MONK,       WeaponType.AXE,    Element.WIND,      0.91, 0.88),
    ("Sera",    JobClass.CLERIC,     WeaponType.STAFF,  Element.LIGHT,     0.93, 0.88),
    ("Rex",     JobClass.RANGER,     WeaponType.BOW,    Element.ICE,       0.90, 0.87),
    ("Bram",    JobClass.BERSERKER,  WeaponType.AXE,    Element.FIRE,      0.89, 0.86),
    ("Nira",    JobClass.WIND_MAGE,  WeaponType.STAFF,  Element.WIND,      0.92, 0.87),
    ("Dag",     JobClass.SPEARMAN,   WeaponType.SPEAR,  Element.LIGHTNING, 0.91, 0.88),
    ("Ella",    JobClass.FIRE_MAGE,  WeaponType.STAFF,  Element.FIRE,      0.90, 0.87),
    ("Wynn",    JobClass.HUNTER,     WeaponType.BOW,    Element.EARTH,     0.93, 0.88),
    ("Bart",    JobClass.EARTH_MAGE, WeaponType.STAFF,  Element.EARTH,     0.91, 0.86),
    ("Yuna",    JobClass.PRIEST,     WeaponType.STAFF,  Element.LIGHT,     0.92, 0.87),
    ("Kage",    JobClass.ASSASSIN,   WeaponType.DAGGER, Element.DARK,      0.90, 0.86),
    ("Coral",   JobClass.ICE_MAGE,   WeaponType.STAFF,  Element.ICE,       0.91, 0.87),
    ("Miles",   JobClass.WARRIOR,    WeaponType.AXE,    Element.WIND,      0.90, 0.86),
    ("Dora",    JobClass.CLERIC,     WeaponType.STAFF,  Element.FIRE,      0.92, 0.87),
    ("Oryn",    JobClass.STORM_MAGE, WeaponType.STAFF,  Element.LIGHTNING, 0.90, 0.86),
    ("Fenn",    JobClass.PALADIN,    WeaponType.SWORD,  Element.LIGHT,     0.93, 0.88),
    ("Zell",    JobClass.DRAGOON,    WeaponType.SPEAR,  Element.WIND,      0.91, 0.87),
    ("Mira",    JobClass.DARK_MAGE,  WeaponType.STAFF,  Element.DARK,      0.90, 0.86),
]

FOUR_STAR_TEMPLATES = [
    ("Cael",    JobClass.KNIGHT,     WeaponType.SWORD,  Element.LIGHT,     1.12, 1.10),
    ("Lyra",    JobClass.ARCANE_SAGE,WeaponType.STAFF,  Element.LIGHTNING, 1.15, 1.12),
    ("Vance",   JobClass.BERSERKER,  WeaponType.AXE,    Element.DARK,      1.18, 1.14),
    ("Shira",   JobClass.ASSASSIN,   WeaponType.DAGGER, Element.ICE,       1.12, 1.10),
    ("Bael",    JobClass.DRAGOON,    WeaponType.SPEAR,  Element.FIRE,      1.14, 1.11),
    ("Noel",    JobClass.PRIEST,     WeaponType.STAFF,  Element.LIGHT,     1.15, 1.12),
    ("Kira",    JobClass.STORM_MAGE, WeaponType.STAFF,  Element.LIGHTNING, 1.16, 1.13),
    ("Roan",    JobClass.PALADIN,    WeaponType.SWORD,  Element.LIGHT,     1.14, 1.10),
    ("Zara",    JobClass.HUNTER,     WeaponType.BOW,    Element.WIND,      1.12, 1.10),
    ("Drax",    JobClass.MONK,       WeaponType.AXE,    Element.EARTH,     1.18, 1.14),
]

FIVE_STAR_TEMPLATES = [
    ("Seraph",  JobClass.LIGHT_MAGE, WeaponType.STAFF,  Element.LIGHT,     1.45, 1.38),
    ("Abyss",   JobClass.DARK_MAGE,  WeaponType.STAFF,  Element.DARK,      1.42, 1.35),
    ("Titan",   JobClass.BERSERKER,  WeaponType.AXE,    Element.EARTH,     1.50, 1.40),
    ("Oracle",  JobClass.ARCANE_SAGE,WeaponType.STAFF,  Element.LIGHTNING, 1.45, 1.40),
    ("Valkyrie",JobClass.PALADIN,    WeaponType.SWORD,  Element.LIGHT,     1.48, 1.38),
]

COMPANION_GACHA_POOL = {
    3: THREE_STAR_TEMPLATES,
    4: FOUR_STAR_TEMPLATES,
    5: FIVE_STAR_TEMPLATES,
}

GACHA_RATES = {3: 0.65, 4: 0.30, 5: 0.05}


def summon_companion(target_level: int = 2) -> Character:
    """Roll a random companion via gacha, leveled to target_level."""
    roll = random.random()
    cumulative = 0.0
    rarity = 3
    for r, rate in GACHA_RATES.items():
        cumulative += rate
        if roll < cumulative:
            rarity = r
            break

    templates = COMPANION_GACHA_POOL[rarity]
    tmpl = random.choice(templates)
    name, job, weapon, element, stat_mult, growth_mult = tmpl

    char = _make_companion(name, job, weapon, element, rarity,
                           stat_mult, growth_mult)

    # Level the character up silently
    for _ in range(1, target_level):
        char._level_up()
    char.level = target_level
    char.current_hp = char.max_hp
    char.current_mp = char.max_mp

    return char