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) {}
}