core/terrain.js

import { Piece } from "./piece.js";
import {
  TRAVERSABLE,
  AQUATIC,
  LAVITIC,
  ETHEREAL,
  WATER_RESISTANT,
  FIRE_RESISTANT,
  FLIER,
  PENETRABLE,
} from "./flags.js";

/**
 * Base class for all terrain types.
 *
 * Terrain occupies one cell and controls whether agents can enter/exit, and
 * what happens when they do. All callback methods are no-ops by default;
 * subclasses override only what they need.
 *
 * The Java Terrain interface is an event-based protocol: each callback receives
 * a GameEvent that can be cancelled to prevent the default action.
 */
export class Terrain extends Piece {
  /**
   * Can an agent (or player) enter this terrain from the given direction?
   * Enforces flag-based traversability:
   *   - TRAVERSABLE  → any agent can enter
   *   - AQUATIC      → only AQUATIC agents or those with WATER_RESISTANT
   *   - LAVITIC      → only LAVITIC agents or those with FIRE_RESISTANT
   *   - ETHEREAL     → only FLIER agents
   *   - (none)       → impassable (Wall, Waterfall, etc.)
   * Return false to unconditionally block movement — no side effects.
   * @param {Agent} agent
   * @param {Cell} cell    - destination cell
   * @param {Direction} direction
   * @returns {boolean}
   */
  canEnter(agent, cell, direction) {
    // AQUATIC and LAVITIC are more specific than ETHEREAL — check them first.
    // (AQUATIC and LAVITIC both include the ETHEREAL bit.)
    if (this.is(TRAVERSABLE)) {
      return true;
    } else if (this.is(AQUATIC)) {
      return agent.is(AQUATIC) || agent.is(WATER_RESISTANT);
    } else if (this.is(LAVITIC)) {
      return agent.is(LAVITIC) || agent.is(FIRE_RESISTANT);
    } else if (this.is(ETHEREAL)) {
      return agent.is(FLIER);
    }
    return false;
  }

  /**
   * Can a non-player agent exit this terrain in the given direction?
   * @param {Agent} agent
   * @param {Cell} cell    - current cell
   * @param {Direction} direction
   * @returns {boolean}
   */
  canExit(agent, cell, direction) {
    return true;
  }

  /**
   * The player is entering this terrain.
   * Cancel event to prevent the move.
   * @param {GameEvent} event
   * @param {Player} player
   * @param {Cell} cell    - destination cell
   * @param {Direction} dir
   */
  onEnter(event, player, cell, dir) {}

  /**
   * The player is exiting this terrain.
   * Cancel event to prevent the move.
   */
  onExit(event, player, cell, dir) {}

  /**
   * A non-player agent is entering this terrain.
   * Cancel event to prevent the move.
   */
  onAgentEnter(event, agent, cell, dir) {}

  /**
   * A non-player agent is exiting this terrain.
   */
  onAgentExit(event, agent, cell, dir) {}

  /**
   * An in-flight item is passing over this terrain.
   * Cancel event to make the item land here instead of continuing.
   * @param {GameEvent} event
   * @param {Cell} cell
   * @param {InFlightItem} flier
   */
  onFlyOver(event, _cell, _flier) {
    if (!this.is(PENETRABLE)) event.cancel();
  }

  /**
   * An item is being dropped onto this terrain.
   * Cancel event to make the item disappear (e.g., dropping into lava).
   */
  onDrop(event, cell, item) {}

  /**
   * An agent is picking up an item from this terrain.
   * Cancel event to prevent the pickup.
   */
  onPickup(event, cell, agent, item) {}

  /**
   * This terrain is now adjacent to the player. Called after each move.
   * Use this to reveal secret passages, trigger proximity effects, etc.
   * @param {GameEvent} event
   * @param {Cell} cell
   */
  onAdjacentTo(event, cell) {}

  /**
   * This terrain is no longer adjacent to the player.
   */
  onNotAdjacentTo(event, cell) {}

  /**
   * A color event is being broadcast on this board.
   * Implementations compare `this.color` to the event `color` parameter
   * and react only when they match.
   * @param {GameEvent} event
   * @param {string} color   CSS color string of the event
   * @param {Cell} cell   the cell holding this terrain
   */
  onColorEvent(event, color, cell) {}
}