Newer
Older
rpg / apps / game / src / systems / combat / PlayerCombat.ts
import { Cooldown } from '@rpg/engine';
import { PLAYER_COMBAT } from './stats';

/**
 * Боевое состояние героя: HP, неуязвимость, кулдауны, заряд резонанса.
 * Чистая логика (без Pixi) — тестируется в Vitest.
 */
export class PlayerCombat {
    hp: number;
    /** Секунды удержания атаки (заряд низкого резонанса). */
    chargeTime = 0;
    private invulnTimer = 0;
    /** Кулдаун короткого удара. */
    readonly attackCd = new Cooldown(PLAYER_COMBAT.coneCooldown);
    /** Кулдаун низкого резонанса. */
    readonly resonanceCd = new Cooldown(PLAYER_COMBAT.resonanceCooldown);

    constructor(hp: number = PLAYER_COMBAT.maxHp) {
        this.hp = Math.min(hp, PLAYER_COMBAT.maxHp);
    }

    update(dt: number): void {
        if (this.invulnTimer > 0) this.invulnTimer = Math.max(0, this.invulnTimer - dt);
        this.attackCd.update(dt);
        this.resonanceCd.update(dt);
        if (this.isCharging()) this.chargeTime += dt;
    }

    /** Атака удерживается (Space зажат) — копим заряд резонанса. */
    startCharge(): void {
        if (this.chargeTime === 0) this.chargeTime = 0.0001; // старт отсчёта
    }

    isCharging(): boolean {
        return this.chargeTime > 0;
    }

    /**
     * Отпустил: если зарядился до резонанса и кулдаун готов — вернуть 'resonance',
     * иначе — обычный удар (если готов). Кулдауны триггерятся здесь.
     */
    release(): 'resonance' | 'attack' | null {
        const charged = this.chargeTime >= PLAYER_COMBAT.resonanceCharge;
        this.chargeTime = 0;
        if (charged) {
            if (this.resonanceCd.trigger()) return 'resonance';
            return null; // резонанс ещё перезаряжается — молча
        }
        return this.attackCd.trigger() ? 'attack' : null;
    }

    /**
     * Получить урон. Возвращает фактически применённый урон (0 — неуязвим).
     */
    takeDamage(damage: number): number {
        if (this.invulnTimer > 0 || this.hp <= 0) return 0;
        const applied = Math.min(damage, this.hp);
        this.hp -= applied;
        this.invulnTimer = PLAYER_COMBAT.invulnTime;
        return applied;
    }

    /** Неуязвим (мигание спрайта в этот период). */
    get invuln(): boolean {
        return this.invulnTimer > 0;
    }

    get dead(): boolean {
        return this.hp <= 0;
    }

    /** Восстановление после смерти (респаун). */
    revive(): void {
        this.hp = PLAYER_COMBAT.maxHp;
        this.invulnTimer = PLAYER_COMBAT.invulnTime;
        this.chargeTime = 0;
        this.attackCd.reset();
        this.resonanceCd.reset();
    }
}