core/effect.ts

import { Color } from "./color.ts";
import { Piece } from "./piece.ts";
import { Sym } from "./sym.ts";
import { TRANSIENT } from "./flags.ts";

/**
 * Base class for all effect types.
 *
 * Effects are transient pieces (not saved with the board) used to animate
 * thrown items, projectiles, fire, clouds, etc. Unlike terrain/items/agents,
 * effects are NOT immutable — they maintain frame state as they animate.
 *
 * Effects implement `onFrame(event, cell, frame)` and are registered with the
 * board's AnimationProxy list when added to a cell (mirrors Java's EffectBag
 * registering Animated effects via board.addAnimated). Each effect gets its
 * own proxy with a frame counter starting at 0 (randomSeed = false).
 *
 * Effects self-remove by calling cell.removeEffect(this) inside onFrame when
 * their animation is complete, matching Java's cell.getEffects().remove(this).
 */
export class Effect extends Piece {
  frames: Sym[];
  frameIndex: number;
  done: boolean;
  constructor(name: string, frames: Sym[], color: Color) {
    if (frames.length === 0) {
      throw new Error("Effect must have at least one frame");
    }
    // Effects always have the TRANSIENT flag
    super(name, TRANSIENT, color, frames[0]);
    this.frames = frames;
    this.frameIndex = 0;
    this.done = false;
  }

  /**
   * Effects always start at frame 0 (not a random seed). Mirrors Java where
   * all effects return randomSeed() = false.
   */
  randomSeed(): boolean {
    return false;
  }

  /** Current animation frame symbol. */
  get currentSymbol(): Sym {
    return this.frames[this.frameIndex];
  }

  /** True if this effect has finished animating and should be removed. */
  get isExpired(): boolean {
    return this.done;
  }

  /**
   * Should this effect render above an agent that occupies the same cell?
   * Override to return true for effects that should appear on top.
   */
  isAboveAgent(): boolean {
    return false;
  }
}