core/agent-proxy.js

import { Agent } from "./agent.js";
import { NONE } from "./color.js";

/**
 * Abstract base class for agent decorators (proxies).
 *
 * Wraps another agent and forwards all behaviour to it, with the exception of
 * `onTurn` which is intentionally suppressed so that creature AI is disabled
 * for the duration of any proxy effect (giving paralysis for free for
 * non-player agents).
 *
 * Subclasses can override individual callbacks to add effect-specific behaviour.
 * The wrapped agent is accessible as `this.agent`.
 *
 * Mirrors Java's `AgentProxy` abstract class.
 */
export class AgentProxy extends Agent {
  /**
   * @param {Agent} agent  The agent being wrapped.
   * @param {number} flags  Additional flags added by this proxy.
   */
  constructor(agent, flags) {
    super(
      agent?.name ?? "Unknown",
      flags,
      agent?.color ?? NONE,
      agent?.symbol ?? null,
    );
    /** @type {Agent} */
    this.agent = agent;
    /**
     * Marker used by Cell.setAgent / _clearAgent to detect proxy-wrapped
     * players without creating a circular import to the AgentProxy class.
     */
    this.isAgentProxy = true;
  }

  /**
   * Returns true if either the proxy's own flags OR the wrapped agent has the
   * given flag. This is critical so that `PLAYER` propagates through
   * a `Paralyzed` wrapper.
   */
  is(flag) {
    return this.agent?.is(flag) || (this.flags & flag) === flag;
  }

  /** Returns true only if neither the proxy flags nor the wrapped agent has the given flag. */
  not(flag) {
    return this.agent?.not(flag) && (this.flags & flag) !== flag;
  }

  changeHealth(value) {
    return this.agent.changeHealth(value);
  }

  canEnter(direction, from, to) {
    return this.agent.canEnter(direction, from, to);
  }

  onDie(event, cell) {
    this.agent.onDie(event, cell);
  }

  onHit(event, attackerCell, agentLoc, agent) {
    this.agent.onHit(event, attackerCell, agentLoc, agent);
  }

  onHitBy(event, agentLoc, agent, dir) {
    this.agent.onHitBy(event, agentLoc, agent, dir);
  }

  onHitByItem(event, itemLoc, item, dir) {
    this.agent.onHitByItem(event, itemLoc, item, dir);
  }

  /** Delegates to the wrapped agent's onFrame if it defines one. */
  onFrame(event, cell, frame) {
    if (this.agent?.onFrame) {
      this.agent.onFrame(event, cell, frame);
    }
  }

  // onTurn is intentionally NOT delegated — suppresses creature AI while any
  // proxy effect is active.
}