import { DIRECTIONS } from './defs.js'; const TILE_TYPES = { // Floors and walls floor: { }, floor_letter: { load(me, template) { me.ascii_code = template.modifier; }, }, wall: { blocks: true, }, wall_invisible: { // TODO cc2 seems to make these flicker briefly blocks: true, }, wall_appearing: { blocks: true, on_bump(me, level, other) { level.transmute_tile(me, 'wall'); }, }, popwall: { blocks_monsters: true, blocks_blocks: true, on_depart(me, level, other) { level.transmute_tile(me, 'wall'); }, }, thinwall_n: { thin_walls: new Set(['north']), }, thinwall_s: { thin_walls: new Set(['south']), }, thinwall_e: { thin_walls: new Set(['east']), }, thinwall_w: { thin_walls: new Set(['west']), }, thinwall_se: { thin_walls: new Set(['south', 'east']), }, fake_wall: { blocks: true, on_bump(me, level, other) { level.transmute_tile(me, 'wall'); }, }, fake_floor: { blocks: true, on_bump(me, level, other) { level.transmute_tile(me, 'floor'); }, }, // Swivel doors swivel_floor: {}, swivel_ne: { thin_walls: new Set(['north', 'east']), }, swivel_se: { thin_walls: new Set(['south', 'east']), }, swivel_sw: { thin_walls: new Set(['south', 'west']), }, swivel_nw: { thin_walls: new Set(['north', 'west']), }, // Locked doors door_red: { blocks: true, on_bump(me, level, other) { if (other.type.has_inventory && other.take_item('key_red')) { me.type = TILE_TYPES.floor; } }, }, door_blue: { blocks: true, on_bump(me, level, other) { if (other.type.has_inventory && other.take_item('key_blue')) { me.type = TILE_TYPES.floor; } }, }, door_yellow: { blocks: true, on_bump(me, level, other) { if (other.type.has_inventory && other.take_item('key_yellow')) { me.type = TILE_TYPES.floor; } }, }, door_green: { blocks: true, on_bump(me, level, other) { if (other.type.has_inventory && other.take_item('key_green')) { me.type = TILE_TYPES.floor; } }, }, // Terrain dirt: { blocks_monsters: true, blocks_blocks: true, // TODO block melinda only without the hiking boots; can't use ignore because then she wouldn't step on it :S also ignore doesn't apply to blocks anyway. on_arrive(me, level, other) { level.transmute_tile(me, 'floor'); }, }, gravel: { blocks_monsters: true, }, // Hazards fire: { on_arrive(me, level, other) { if (other.type.is_player) { level.fail("Oops! You can't walk on fire without fire boots!"); level.transmute_tile(other, 'player_burned'); } else { level.remove_tile(other); } }, }, water: { on_arrive(me, level, other) { // TODO cc1 allows items under water, i think; water was on the upper layer if (other.type.name == 'dirt_block' || other.type.name == 'clone_block') { level.remove_tile(other); level.transmute_tile(me, 'dirt'); } else if (other.type.is_player) { level.fail("swimming with the fishes"); level.transmute_tile(other, 'player_drowned'); } else { level.remove_tile(other); } }, }, turtle: { }, ice: { on_arrive(me, level, other) { level.make_slide(other, 'ice'); }, }, ice_sw: { thin_walls: new Set(['south', 'west']), on_arrive(me, level, other) { if (other.direction === 'south') { other.direction = 'east'; } else { other.direction = 'north'; } level.make_slide(other, 'ice'); }, }, ice_nw: { thin_walls: new Set(['north', 'west']), on_arrive(me, level, other) { if (other.direction === 'north') { other.direction = 'east'; } else { other.direction = 'south'; } level.make_slide(other, 'ice'); }, }, ice_ne: { thin_walls: new Set(['north', 'east']), on_arrive(me, level, other) { if (other.direction === 'north') { other.direction = 'west'; } else { other.direction = 'south'; } level.make_slide(other, 'ice'); }, }, ice_se: { thin_walls: new Set(['south', 'east']), on_arrive(me, level, other) { if (other.direction === 'south') { other.direction = 'west'; } else { other.direction = 'north'; } level.make_slide(other, 'ice'); }, }, force_floor_n: { on_arrive(me, level, other) { other.direction = 'north'; level.make_slide(other, 'force'); }, }, force_floor_e: { on_arrive(me, level, other) { other.direction = 'east'; level.make_slide(other, 'force'); }, }, force_floor_s: { on_arrive(me, level, other) { other.direction = 'south'; level.make_slide(other, 'force'); }, }, force_floor_w: { on_arrive(me, level, other) { other.direction = 'west'; level.make_slide(other, 'force'); }, }, force_floor_all: { // TODO ms: this is random, and an acting wall to monsters (!) on_arrive(me, level, other) { other.direction = level.get_force_floor_direction(); level.make_slide(other, 'force'); }, }, bomb: { // TODO explode on_arrive(me, level, other) { level.remove_tile(me); level.remove_tile(other); if (other.type.is_player) { level.fail("watch where you step"); } }, }, thief_tools: { on_arrive(me, level, other) { if (other.inventory) { for (let [name, count] of Object.entries(other.inventory)) { if (count > 0 && TILE_TYPES[name].is_tool) { other.take_item(name, count); } } } }, }, thief_keys: { on_arrive(me, level, other) { if (other.inventory) { for (let [name, count] of Object.entries(other.inventory)) { if (count > 0 && TILE_TYPES[name].is_key) { other.take_item(name, count); } } } }, }, forbidden: { }, // Mechanisms dirt_block: { blocks: true, is_object: true, is_actor: true, is_block: true, ignores: new Set(['fire']), }, clone_block: { // TODO is this in any way distinct from dirt block blocks: true, is_object: true, is_actor: true, is_block: true, ignores: new Set(['fire']), }, green_floor: {}, green_wall: { blocks: true, }, cloner: { blocks: true, activate(me, level) { let cell = me.cell; // Copy, so we don't end up repeatedly cloning the same object for (let tile of Array.from(cell)) { if (tile !== me && tile.type.is_actor) { // Copy this stuff in case the movement changes it let type = tile.type; let direction = tile.direction; // Unstick and try to move the actor; if it's blocked, // abort the clone level.set_actor_stuck(tile, false); if (level.attempt_step(tile, direction)) { level.actors.push(tile); // FIXME add this underneath, just above the cloner level.add_tile(new tile.constructor(type, direction), cell); } else { level.set_actor_stuck(tile, true); } } } }, }, trap: { on_arrive(me, level, other) { if (! me.open) { level.set_actor_stuck(other, true); } }, }, teleport_blue: { connects_to: 'teleport_blue', connect_order: 'backward', is_teleporter: true, // TODO implement 'backward' // TODO to make this work, i need to be able to check if a spot is blocked /ahead of time/ }, // Buttons button_blue: { on_arrive(me, level, other) { // Flip direction of all tanks for (let actor of level.actors) { // TODO generify somehow?? if (actor.type.name === 'tank_blue') { level.set_actor_direction(actor, DIRECTIONS[actor.direction].opposite); } } }, }, button_green: { on_arrive(me, level, other) { // Swap green floors and walls // TODO could probably make this more compact for undo purposes for (let row of level.cells) { for (let cell of row) { for (let tile of cell) { if (tile.type.name === 'green_floor') { level.transmute_tile(tile, 'green_wall'); } else if (tile.type.name === 'green_wall') { level.transmute_tile(tile, 'green_floor'); } else if (tile.type.name === 'green_chip') { level.transmute_tile(tile, 'green_bomb'); } else if (tile.type.name === 'green_bomb') { level.transmute_tile(tile, 'green_chip'); } } } } }, }, button_brown: { connects_to: 'trap', connect_order: 'forward', on_arrive(me, level, other) { if (me.connection && me.connection.cell) { let trap = me.connection; trap.open = true; for (let tile of trap.cell) { if (tile.stuck) { level.set_actor_stuck(tile, false); } } } }, on_depart(me, level, other) { if (me.connection && me.connection.cell) { let trap = me.connection; trap.open = false; for (let tile of trap.cell) { if (tile.is_actor) { level.set_actor_stuck(tile, true); } } } }, }, button_red: { connects_to: 'cloner', connect_order: 'forward', on_arrive(me, level, other) { if (me.connection && me.connection.cell) { me.connection.type.activate(me.connection, level); } }, }, // Critters bug: { is_actor: true, is_object: true, is_monster: true, blocks_monsters: true, movement_mode: 'follow-left', movement_speed: 4, }, paramecium: { is_actor: true, is_object: true, is_monster: true, blocks_monsters: true, movement_mode: 'follow-right', movement_speed: 4, }, ball: { is_actor: true, is_object: true, is_monster: true, blocks_monsters: true, movement_mode: 'bounce', movement_speed: 4, }, walker: { is_actor: true, is_object: true, is_monster: true, blocks_monsters: true, movement_mode: 'bounce-random', movement_speed: 4, }, tank_blue: { is_actor: true, is_object: true, is_monster: true, blocks_monsters: true, movement_mode: 'forward', movement_speed: 4, }, blob: { is_actor: true, is_object: true, is_monster: true, blocks_monsters: true, movement_mode: 'random', movement_speed: 8, }, teeth: { is_actor: true, is_object: true, is_monster: true, blocks_monsters: true, movement_mode: 'pursue', // TODO actually 4 with deliberate pauses but i have no way to model that atm movement_speed: 8, }, fireball: { is_actor: true, is_object: true, is_monster: true, blocks_monsters: true, movement_mode: 'turn-right', movement_speed: 4, ignores: new Set(['fire']), }, glider: { is_actor: true, is_object: true, is_monster: true, blocks_monsters: true, movement_mode: 'turn-left', movement_speed: 4, ignores: new Set(['water']), }, // Keys key_red: { is_object: true, is_item: true, is_key: true, }, key_blue: { is_object: true, is_item: true, is_key: true, }, key_yellow: { is_object: true, is_item: true, is_key: true, }, key_green: { is_object: true, is_item: true, is_key: true, }, // Tools cleats: { is_object: true, is_item: true, is_tool: true, item_ignores: new Set(['ice', 'ice_nw', 'ice_ne', 'ice_sw', 'ice_se']), }, suction_boots: { is_object: true, is_item: true, is_tool: true, item_ignores: new Set([ 'force_floor_n', 'force_floor_s', 'force_floor_e', 'force_floor_w', ]), }, fire_boots: { is_object: true, is_item: true, is_tool: true, item_ignores: new Set(['fire']), }, flippers: { is_object: true, is_item: true, is_tool: true, item_ignores: new Set(['water']), }, // Progression player: { is_actor: true, is_player: true, has_inventory: true, is_object: true, movement_speed: 4, pushes: { dirt_block: true, clone_block: true, }, // FIXME this prevents thief from taking green key infinite_items: { key_green: true, }, }, player_drowned: { }, player_burned: { }, chip: { is_object: true, is_chip: true, is_required_chip: true, blocks_monsters: true, blocks_blocks: true, on_arrive(me, level, other) { if (other.type.is_player) { level.collect_chip(); level.remove_tile(me); } }, }, chip_extra: { is_chip: true, is_object: true, }, score_10: { is_object: true, }, score_100: { is_object: true, }, score_1000: { is_object: true, }, score_2x: { is_object: true, }, hint: { is_hint: true, }, socket: { blocks: true, on_bump(me, level, other) { if (other.type.is_player && level.chips_remaining === 0) { me.type = TILE_TYPES.floor; } }, }, exit: { on_arrive(me, level, other) { if (other.type.is_player) { level.win(); } }, }, }; // Tell them all their own names for (let [name, type] of Object.entries(TILE_TYPES)) { type.name = name; } export default TILE_TYPES;