Newer
Older
rpg / packages / engine / src / render / fx / FxRing.ts
import { Graphics } from 'pixi.js';
import type { Updatable } from '../../core/Updatables';
import { sampleRing, ringDuration, type FxRingSpec, type FxRingSample } from './fxSim';

/**
 * Кольцо/волна: расходящаяся или сходящаяся окружность (или ромб — «footprint»
 * зоны). repeat > 1 — телеграф зоны (проходы с паузами); repeat: Infinity —
 * зацикленный индикатор, гасится только cancel(). Перерисовка каждый тик
 * (clear + один stroke) — тот же приём, что у рукописных колец боевых вьюх.
 */
export class FxRing extends Graphics implements Updatable {
    private t = 0;
    private cancelled = false;

    constructor(private readonly spec: FxRingSpec) {
        super();
        this.draw(sampleRing(0, spec));
    }

    /** Досрочно погасить (смена телеграфа, exit сцены). */
    cancel(): void {
        if (this.cancelled) return;
        this.cancelled = true;
        this.destroy();
    }

    /** true, когда все проходы сыграны (или кольцо погашено). */
    get done(): boolean {
        return this.cancelled || this.t >= ringDuration(this.spec) - 1e-6;
    }

    update(dt: number): void {
        if (this.destroyed) return;
        this.t += dt;
        this.draw(sampleRing(this.t, this.spec));
        if (this.done) this.destroy();
    }

    private draw(s: FxRingSample): void {
        this.clear();
        if (this.spec.fill) {
            this.shapePath(s.radius).fill({
                color: this.spec.fill,
                alpha: (this.spec.fillAlpha ?? 0.25) * s.alpha
            });
        }
        this.shapePath(s.radius).stroke({
            color: this.spec.color,
            width: this.spec.width ?? 1,
            alpha: s.alpha
        });
    }

    /** Контур зоны текущего радиуса: круг или ромб (квадрат, повёрнутый на 45°). */
    private shapePath(r: number): this {
        if (this.spec.shape === 'diamond') {
            return this.poly([0, -r, r, 0, 0, r, -r, 0]);
        }
        return this.circle(0, 0, r);
    }
}