import { Piece } from "./piece.ts";
import {
TRAVERSABLE,
AQUATIC,
LAVITIC,
WATER_RESISTANT,
FIRE_RESISTANT,
FLIER,
PENETRABLE,
PLAYER,
} from "./flags.ts";
import { Agent } from "./agent.ts";
import { Cell } from "./cell.ts";
import { Direction } from "./direction.ts";
import { GameEvent } from "./game-event.ts";
import { Player } from "./player.ts";
import { InFlightItem } from "../pieces/effects/effects.ts";
import { Item } from "./item.ts";
import { Color } from "./color.ts";
/**
* 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?
* Mirrors Java AbstractTerrain.canEnter, plus a Raft bypass: crossing onto
* AQUATIC terrain from a cell currently occupied by a Raft always succeeds
* (the raft floats the agent across). WATER_RESISTANT/FIRE_RESISTANT are
* scoped to the player only — Java handles that bypass in onEnter (which
* this port's canEnter absorbs, since canEnter here is also consulted for
* the player's own physical traversability check).
* Return false to unconditionally block movement — no side effects.
*/
canEnter(agent: Agent, cell: Cell, direction: Direction): boolean {
if (this.is(AQUATIC)) {
const from = cell?.getAdjacentCell(direction.reverse);
if (from?.terrain?.name === "Raft") {
return true;
}
if (agent.is(PLAYER) && agent.is(WATER_RESISTANT)) return true;
}
if (this.is(LAVITIC) && agent.is(PLAYER) && agent.is(FIRE_RESISTANT)) {
return true;
}
return (
this.is(TRAVERSABLE) ||
(this.is(PENETRABLE) && agent.is(FLIER)) ||
(this.is(LAVITIC) && agent.is(LAVITIC)) ||
(this.is(AQUATIC) && agent.is(AQUATIC))
);
}
/**
* Can a non-player agent exit this terrain in the given direction?
* Mirrors Java AbstractTerrain.canExit exactly (no resistance/raft bypass —
* those only apply to entering).
*/
canExit(agent: Agent, cell: Cell, direction: Direction): boolean {
return (
this.is(TRAVERSABLE) ||
(this.is(PENETRABLE) && agent.is(FLIER)) ||
(this.is(LAVITIC) && agent.is(LAVITIC)) ||
(this.is(AQUATIC) && agent.is(AQUATIC))
);
}
/**
* The player is entering this terrain.
* Cancel event to prevent the move.
*/
onEnter(event: GameEvent, player: Player, cell: Cell, dir: Direction) { }
/**
* The player is exiting this terrain.
* Cancel event to prevent the move.
*/
onExit(event: GameEvent, player: Player, cell: Cell, dir: Direction) { }
/**
* A non-player agent is entering this terrain.
* Cancel event to prevent the move.
*/
onAgentEnter(event: GameEvent, agent: Agent, cell: Cell, dir: Direction) {
if (!this.canEnter(agent, cell, dir)) {
event.cancel();
}
}
/**
* A non-player agent is exiting this terrain.
*/
onAgentExit(event: GameEvent, agent: Agent, cell: Cell, dir: Direction) { }
/**
* An in-flight item is leaving this terrain (about to enter the next cell).
* Override to destroy or absorb the item as it leaves (e.g., Waterfall).
* @param {InFlightItem} flier
*/
onFlyOut(event: GameEvent, _cell: Cell, _flier: InFlightItem) { }
/**
* An in-flight item is passing over this terrain.
* Cancel event to make the item land here instead of continuing.
*/
onFlyOver(event: GameEvent, _cell: Cell, _flier: InFlightItem) {
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: GameEvent, cell: Cell, item: Item) { }
/**
* An agent is picking up an item from this terrain.
* Cancel event to prevent the pickup.
*/
onPickup(event: GameEvent, cell: Cell, agent: Agent, item: Item) { }
/**
* This terrain is now adjacent to the player. Called after each move.
* Use this to reveal secret passages, trigger proximity effects, etc.
*/
onAdjacentTo(event: GameEvent, cell: Cell) { }
/**
* This terrain is no longer adjacent to the player.
*/
onNotAdjacentTo(event: GameEvent, cell: 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.
*/
onColorEvent(event: GameEvent, color: Color, cell: Cell) { }
}