From 5cb29c8f7d1cc7f16a2e739a2ce686ee715127f5 Mon Sep 17 00:00:00 2001 From: "Eevee (Evelyn Woods)" Date: Mon, 23 Nov 2020 23:41:32 -0700 Subject: [PATCH] Overhaul collision Collision now uses bits and masks. The main upshot is that ghost and ice/directional blocks collide much more correctly, now. And turtles block fireballs. Also, monsters can now move over "no" signs, and can trample the player if she's standing on top of an item. While I was at it, I finished implementing the "bestowal bow", an item mod (same layer as the "no" sign) that allows any actor to pick up the item in that tile. --- js/defs.js | 32 +++ js/game.js | 39 ++-- js/main-editor.js | 2 +- js/tiletypes.js | 532 ++++++++++++++++++++++------------------------ 4 files changed, 313 insertions(+), 292 deletions(-) diff --git a/js/defs.js b/js/defs.js index 582c6d9..a524e5d 100644 --- a/js/defs.js +++ b/js/defs.js @@ -34,3 +34,35 @@ export const DIRECTIONS = { opposite: 'west', }, }; + +// TODO cc2 order is: swivel, thinwalls, canopy (and yes you can have them all in the same tile) +export const DRAW_LAYERS = { + terrain: 0, + item: 1, + item_mod: 2, + actor: 3, + overlay: 4, + MAX: 5, +}; + +export const COLLISION = { + player1: 0x0001, + player2: 0x0002, + player: 0x0003, + + block_cc1: 0x0004, + block_cc2: 0x0008, // ice + directional + + // NOTE: "monster" does NOT include ghost, because it so rarely overlaps anything else + monster: 0x0100, + // Some monsters also have their own extra flag because of weird behavior + fireball: 0x0200, + bug: 0x0400, + rover: 0x1000, + ghost: 0x8000, + + // Combo masks used for matching + all_but_ghost: 0xffff & ~0x8000, + all_but_player: 0xffff & ~0x0003, + all: 0xffff, +}; diff --git a/js/game.js b/js/game.js index 7b2d29c..80638b3 100644 --- a/js/game.js +++ b/js/game.js @@ -45,20 +45,20 @@ export class Tile { } blocks(other, direction, level) { - if (this.type.blocks_all) + // Extremely awkward special case: items don't block monsters if the cell also contains an + // item modifier (i.e. "no" sign) or a player + // TODO would love to get this outta here + if (this.type.is_item && + this.cell.some(tile => tile.type.item_modifier || tile.type.is_player)) + return false; + + if (this.type.blocks_collision & other.type.collision_mask) return true; if (this.type.thin_walls && this.type.thin_walls.has(DIRECTIONS[direction].opposite)) return true; - if (other.type.is_player && this.type.blocks_players) - return true; - if (other.type.is_monster && this.type.blocks_monsters) - return true; - if (other.type.is_block && this.type.blocks_blocks) - return true; - if (this.type.blocks) return this.type.blocks(this, level, other); @@ -157,6 +157,14 @@ export class Cell extends Array { return null; } + get_item_mod() { + for (let tile of this) { + if (tile.type.item_modifier) + return tile; + } + return null; + } + blocks_leaving(actor, direction) { for (let tile of this) { if (tile === actor) @@ -1025,9 +1033,9 @@ export class Level { if (actor.ignores(tile.type.name)) continue; - // TODO some actors can pick up some items... if (tile.type.is_item && - (actor.type.is_player || cell.some(t => t.allows_all_pickup)) && + (actor.type.has_inventory || + cell.some(t => t.type.item_modifier === 'pickup')) && this.attempt_take(actor, tile)) { if (tile.type.is_key) { @@ -1545,10 +1553,15 @@ export class Level { // Have an actor try to pick up a particular tile; it's prevented if there's a no sign, and the // tile is removed if successful attempt_take(actor, tile) { - if (! tile.cell.some(t => t.type.disables_pickup) && - this.give_actor(actor, tile.type.name)) - { + let mod = tile.cell.get_item_mod(); + if (mod && mod.type.item_modifier === 'ignore') + return false; + + if (this.give_actor(actor, tile.type.name)) { this.remove_tile(tile); + if (mod && mod.type.item_modifier === 'pickup') { + this.remove_tile(mod); + } return true; } return false; diff --git a/js/main-editor.js b/js/main-editor.js index 9b7823f..05e4110 100644 --- a/js/main-editor.js +++ b/js/main-editor.js @@ -500,7 +500,7 @@ const EDITOR_PALETTE = [{ tiles: [ 'key_blue', 'key_red', 'key_yellow', 'key_green', 'flippers', 'fire_boots', 'cleats', 'suction_boots', - 'no_sign', // 'bestowal_bow', + 'no_sign', 'bestowal_bow', ], }, { title: "Creatures", diff --git a/js/tiletypes.js b/js/tiletypes.js index ac7cfad..8c95116 100644 --- a/js/tiletypes.js +++ b/js/tiletypes.js @@ -1,14 +1,6 @@ -import { DIRECTIONS } from './defs.js'; +import { COLLISION, DIRECTIONS, DRAW_LAYERS } from './defs.js'; import { random_choice } from './util.js'; -// Draw layers -const LAYER_TERRAIN = 0; -const LAYER_ITEM = 1; -const LAYER_ITEM_MOD = 2; -const LAYER_ACTOR = 3; -const LAYER_OVERLAY = 4; -// TODO cc2 order is: swivel, thinwalls, canopy (and yes you can have them all in the same tile) - function on_ready_force_floor(me, level) { // At the start of the level, if there's an actor on a force floor: // - use on_arrive to set the actor's direction @@ -91,51 +83,56 @@ function player_visual_state(me) { const TILE_TYPES = { // Floors and walls floor: { - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, }, floor_letter: { - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, }, + // TODO possibly this should be a single tile floor_custom_green: { - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, + blocks_collision: COLLISION.ghost, }, floor_custom_pink: { - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, + blocks_collision: COLLISION.ghost, }, floor_custom_yellow: { - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, + blocks_collision: COLLISION.ghost, }, floor_custom_blue: { - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, + blocks_collision: COLLISION.ghost, }, wall: { - draw_layer: LAYER_TERRAIN, - blocks_all: true, + draw_layer: DRAW_LAYERS.terrain, + blocks_collision: COLLISION.all_but_ghost, }, wall_custom_green: { - draw_layer: LAYER_TERRAIN, - blocks_all: true, + draw_layer: DRAW_LAYERS.terrain, + blocks_collision: COLLISION.all, }, wall_custom_pink: { - draw_layer: LAYER_TERRAIN, - blocks_all: true, + draw_layer: DRAW_LAYERS.terrain, + blocks_collision: COLLISION.all, }, wall_custom_yellow: { - draw_layer: LAYER_TERRAIN, - blocks_all: true, + draw_layer: DRAW_LAYERS.terrain, + blocks_collision: COLLISION.all, }, wall_custom_blue: { - draw_layer: LAYER_TERRAIN, - blocks_all: true, + draw_layer: DRAW_LAYERS.terrain, + blocks_collision: COLLISION.all, }, wall_invisible: { - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, // FIXME cc2 seems to make these flicker briefly - blocks_all: true, + blocks_collision: COLLISION.all_but_ghost, }, wall_appearing: { - draw_layer: LAYER_TERRAIN, - blocks_all: true, + draw_layer: DRAW_LAYERS.terrain, + blocks_collision: COLLISION.all_but_ghost, on_bump(me, level, other) { if (other.type.can_reveal_walls) { level.transmute_tile(me, 'wall'); @@ -143,9 +140,8 @@ const TILE_TYPES = { }, }, popwall: { - draw_layer: LAYER_TERRAIN, - blocks_monsters: true, - blocks_blocks: true, + draw_layer: DRAW_LAYERS.terrain, + blocks_collision: COLLISION.block_cc1 | COLLISION.monster, on_ready(me, level) { if (level.compat.auto_convert_ccl_popwalls && me.cell.some(tile => tile.type.is_actor)) @@ -162,42 +158,41 @@ const TILE_TYPES = { // LL specific tile that can only be stepped on /twice/, originally used to repair differences // with popwall behavior between Lynx and Steam popwall2: { - draw_layer: LAYER_TERRAIN, - blocks_monsters: true, - blocks_blocks: true, + draw_layer: DRAW_LAYERS.terrain, + blocks_collision: COLLISION.block_cc1 | COLLISION.monster, on_depart(me, level, other) { level.transmute_tile(me, 'popwall'); }, }, // FIXME in a cc1 tileset, these tiles are opaque >:S thinwall_n: { - draw_layer: LAYER_OVERLAY, + draw_layer: DRAW_LAYERS.overlay, thin_walls: new Set(['north']), blocks_leaving: blocks_leaving_thin_walls, }, thinwall_s: { - draw_layer: LAYER_OVERLAY, + draw_layer: DRAW_LAYERS.overlay, thin_walls: new Set(['south']), blocks_leaving: blocks_leaving_thin_walls, }, thinwall_e: { - draw_layer: LAYER_OVERLAY, + draw_layer: DRAW_LAYERS.overlay, thin_walls: new Set(['east']), blocks_leaving: blocks_leaving_thin_walls, }, thinwall_w: { - draw_layer: LAYER_OVERLAY, + draw_layer: DRAW_LAYERS.overlay, thin_walls: new Set(['west']), blocks_leaving: blocks_leaving_thin_walls, }, thinwall_se: { - draw_layer: LAYER_OVERLAY, + draw_layer: DRAW_LAYERS.overlay, thin_walls: new Set(['south', 'east']), blocks_leaving: blocks_leaving_thin_walls, }, fake_wall: { - draw_layer: LAYER_TERRAIN, - blocks_all: true, + draw_layer: DRAW_LAYERS.terrain, + blocks_collision: COLLISION.all_but_ghost, on_ready(me, level) { if (level.compat.auto_convert_ccl_blue_walls && me.cell.some(tile => tile.type.is_actor)) @@ -214,9 +209,8 @@ const TILE_TYPES = { }, }, fake_floor: { - draw_layer: LAYER_TERRAIN, - blocks_monsters: true, - blocks_blocks: true, + draw_layer: DRAW_LAYERS.terrain, + blocks_collision: COLLISION.block_cc1 | COLLISION.monster, on_bump(me, level, other) { if (other.type.can_reveal_walls) { level.transmute_tile(me, 'floor'); @@ -224,12 +218,12 @@ const TILE_TYPES = { }, }, popdown_wall: { - draw_layer: LAYER_TERRAIN, - blocks_all: true, + draw_layer: DRAW_LAYERS.terrain, + blocks_collision: COLLISION.all_but_ghost, }, popdown_floor: { - draw_layer: LAYER_TERRAIN, - blocks_blocks: true, + draw_layer: DRAW_LAYERS.terrain, + blocks_collision: COLLISION.block_cc1 | COLLISION.block_cc2, // FIXME should be on_approach on_arrive(me, level, other) { // FIXME could probably do this with state? or, eh @@ -237,8 +231,8 @@ const TILE_TYPES = { }, }, popdown_floor_visible: { - draw_layer: LAYER_TERRAIN, - blocks_blocks: true, + draw_layer: DRAW_LAYERS.terrain, + blocks_collision: COLLISION.block_cc1 | COLLISION.block_cc2, on_depart(me, level, other) { // FIXME possibly changes back too fast, not even visible for a tic for me (much like stepping on a button oops) level.transmute_tile(me, 'popdown_floor'); @@ -246,31 +240,29 @@ const TILE_TYPES = { }, // TODO these also block the corresponding mirror actors no_player1_sign: { - draw_layer: LAYER_TERRAIN, - blocks(me, level, other) { - return (other.type.name === 'player'); - }, + draw_layer: DRAW_LAYERS.terrain, + blocks_collision: COLLISION.player1, }, no_player2_sign: { - draw_layer: LAYER_TERRAIN, - blocks(me, level, other) { - return (other.type.name === 'player2'); - }, + draw_layer: DRAW_LAYERS.terrain, + blocks_collision: COLLISION.player2, }, steel: { - draw_layer: LAYER_TERRAIN, - blocks_all: true, + draw_layer: DRAW_LAYERS.terrain, + blocks_collision: COLLISION.all, }, canopy: { - draw_layer: LAYER_OVERLAY, + draw_layer: DRAW_LAYERS.overlay, + // TODO augh, blobs will specifically not move from one canopy to another + blocks_collision: COLLISION.bug | COLLISION.rover, }, // Swivel doors swivel_floor: { - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, }, swivel_ne: { - draw_layer: LAYER_OVERLAY, + draw_layer: DRAW_LAYERS.overlay, thin_walls: new Set(['north', 'east']), on_depart(me, level, other) { if (other.direction === 'north') { @@ -282,7 +274,7 @@ const TILE_TYPES = { }, }, swivel_se: { - draw_layer: LAYER_OVERLAY, + draw_layer: DRAW_LAYERS.overlay, thin_walls: new Set(['south', 'east']), on_depart(me, level, other) { if (other.direction === 'south') { @@ -294,7 +286,7 @@ const TILE_TYPES = { }, }, swivel_sw: { - draw_layer: LAYER_OVERLAY, + draw_layer: DRAW_LAYERS.overlay, thin_walls: new Set(['south', 'west']), on_depart(me, level, other) { if (other.direction === 'south') { @@ -306,7 +298,7 @@ const TILE_TYPES = { }, }, swivel_nw: { - draw_layer: LAYER_OVERLAY, + draw_layer: DRAW_LAYERS.overlay, thin_walls: new Set(['north', 'west']), on_depart(me, level, other) { if (other.direction === 'north') { @@ -320,13 +312,13 @@ const TILE_TYPES = { // Railroad railroad: { - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, // TODO a lot!! }, // Locked doors door_red: { - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, blocks(me, level, other) { // TODO not quite sure if this one is right; there are complex interactions with monsters, e.g. most monsters can eat blue keys but can't actually use them return ! (other.type.has_inventory && other.has_item('key_red')); @@ -339,7 +331,7 @@ const TILE_TYPES = { }, }, door_blue: { - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, blocks(me, level, other) { return ! (other.type.has_inventory && other.has_item('key_blue')); }, @@ -351,7 +343,7 @@ const TILE_TYPES = { }, }, door_yellow: { - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, blocks(me, level, other) { return ! (other.type.has_inventory && other.has_item('key_yellow')); }, @@ -363,7 +355,7 @@ const TILE_TYPES = { }, }, door_green: { - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, blocks(me, level, other) { return ! (other.type.has_inventory && other.has_item('key_green')); }, @@ -377,9 +369,8 @@ const TILE_TYPES = { // Terrain dirt: { - draw_layer: LAYER_TERRAIN, - blocks_monsters: true, - blocks_blocks: true, + draw_layer: DRAW_LAYERS.terrain, + blocks_collision: COLLISION.block_cc1 | COLLISION.monster, blocks(me, level, other) { return (other.type.name === 'player2' && ! other.has_item('hiking_boots')); }, @@ -388,8 +379,8 @@ const TILE_TYPES = { }, }, gravel: { - draw_layer: LAYER_TERRAIN, - blocks_monsters: true, + draw_layer: DRAW_LAYERS.terrain, + blocks_collision: COLLISION.monster, blocks(me, level, other) { return (other.type.name === 'player2' && ! other.has_item('hiking_boots')); }, @@ -397,10 +388,8 @@ const TILE_TYPES = { // Hazards fire: { - draw_layer: LAYER_TERRAIN, - blocks(me, level, other) { - return (other.type.is_monster && other.type.name !== 'fireball'); - }, + draw_layer: DRAW_LAYERS.terrain, + blocks_collision: COLLISION.monster & ~COLLISION.fireball, on_arrive(me, level, other) { if (other.type.name === 'ice_block') { level.remove_tile(other); @@ -415,7 +404,8 @@ const TILE_TYPES = { }, }, water: { - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, + blocks_collision: COLLISION.ghost, on_arrive(me, level, other) { // TODO cc1 allows items under water, i think; water was on the upper layer level.sfx.play_once('splash', me.cell); @@ -440,7 +430,8 @@ const TILE_TYPES = { }, }, turtle: { - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, + blocks_collision: COLLISION.ghost | COLLISION.fireball, on_depart(me, level, other) { level.transmute_tile(me, 'water'); // TODO feels like we should spawn water underneath us, then transmute ourselves into the splash? @@ -448,11 +439,11 @@ const TILE_TYPES = { }, }, ice: { - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, slide_mode: 'ice', }, ice_sw: { - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, thin_walls: new Set(['south', 'west']), slide_mode: 'ice', blocks_leaving: blocks_leaving_thin_walls, @@ -466,7 +457,7 @@ const TILE_TYPES = { }, }, ice_nw: { - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, thin_walls: new Set(['north', 'west']), slide_mode: 'ice', blocks_leaving: blocks_leaving_thin_walls, @@ -480,7 +471,7 @@ const TILE_TYPES = { }, }, ice_ne: { - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, thin_walls: new Set(['north', 'east']), slide_mode: 'ice', blocks_leaving: blocks_leaving_thin_walls, @@ -494,7 +485,7 @@ const TILE_TYPES = { }, }, ice_se: { - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, thin_walls: new Set(['south', 'east']), slide_mode: 'ice', blocks_leaving: blocks_leaving_thin_walls, @@ -508,7 +499,7 @@ const TILE_TYPES = { }, }, force_floor_n: { - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, slide_mode: 'force', on_ready: on_ready_force_floor, on_arrive(me, level, other) { @@ -516,7 +507,7 @@ const TILE_TYPES = { }, }, force_floor_e: { - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, slide_mode: 'force', on_ready: on_ready_force_floor, on_arrive(me, level, other) { @@ -524,7 +515,7 @@ const TILE_TYPES = { }, }, force_floor_s: { - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, slide_mode: 'force', on_ready: on_ready_force_floor, on_arrive(me, level, other) { @@ -532,7 +523,7 @@ const TILE_TYPES = { }, }, force_floor_w: { - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, slide_mode: 'force', on_ready: on_ready_force_floor, on_arrive(me, level, other) { @@ -540,7 +531,7 @@ const TILE_TYPES = { }, }, force_floor_all: { - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, slide_mode: 'force', on_ready: on_ready_force_floor, // TODO ms: this is random, and an acting wall to monsters (!) @@ -550,7 +541,7 @@ const TILE_TYPES = { }, }, slime: { - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, // FIXME kills everything except ghosts, blobs, blocks // FIXME blobs spread slime onto floor tiles, even destroying wiring on_arrive(me, level, other) { @@ -560,7 +551,7 @@ const TILE_TYPES = { }, }, bomb: { - draw_layer: LAYER_ITEM, + draw_layer: DRAW_LAYERS.item, on_arrive(me, level, other) { level.remove_tile(me); if (other.type.is_player) { @@ -573,9 +564,8 @@ const TILE_TYPES = { }, }, thief_tools: { - draw_layer: LAYER_TERRAIN, - blocks_monsters: true, - blocks_blocks: true, + draw_layer: DRAW_LAYERS.terrain, + blocks_collision: COLLISION.block_cc1 | COLLISION.monster, on_arrive(me, level, other) { level.sfx.play_once('thief', me.cell); level.take_all_tools_from_actor(other); @@ -585,9 +575,8 @@ const TILE_TYPES = { }, }, thief_keys: { - draw_layer: LAYER_TERRAIN, - blocks_monsters: true, - blocks_blocks: true, + draw_layer: DRAW_LAYERS.terrain, + blocks_collision: COLLISION.block_cc1 | COLLISION.monster, on_arrive(me, level, other) { level.sfx.play_once('thief', me.cell); level.take_all_keys_from_actor(other); @@ -597,8 +586,9 @@ const TILE_TYPES = { }, }, no_sign: { - draw_layer: LAYER_ITEM_MOD, - disables_pickup: true, + draw_layer: DRAW_LAYERS.item_mod, + item_modifier: 'ignore', + collision_allow: COLLISION.monster, blocks(me, level, other) { let item; for (let tile of me.cell) { @@ -611,22 +601,24 @@ const TILE_TYPES = { }, }, bestowal_bow: { - draw_layer: LAYER_ITEM_MOD, - allows_all_pickup: true, + draw_layer: DRAW_LAYERS.item_mod, + item_modifier: 'pickup', }, // Mechanisms dirt_block: { - draw_layer: LAYER_ACTOR, - blocks_all: true, + draw_layer: DRAW_LAYERS.actor, + collision_mask: COLLISION.block_cc1, + blocks_collision: COLLISION.all, is_actor: true, is_block: true, ignores: new Set(['fire', 'flame_jet_on']), movement_speed: 4, }, ice_block: { - draw_layer: LAYER_ACTOR, - blocks_all: true, + draw_layer: DRAW_LAYERS.actor, + collision_mask: COLLISION.block_cc2, + blocks_collision: COLLISION.all, is_actor: true, is_block: true, can_reveal_walls: true, @@ -641,8 +633,9 @@ const TILE_TYPES = { // TODO floor in water // TODO destroyed in slime // TODO rotate on train tracks - draw_layer: LAYER_ACTOR, - blocks_all: true, + draw_layer: DRAW_LAYERS.actor, + collision_mask: COLLISION.block_cc2, + blocks_collision: COLLISION.all, is_actor: true, is_block: true, can_reveal_walls: true, @@ -657,24 +650,23 @@ const TILE_TYPES = { }, }, green_floor: { - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, on_gray_button(me, level) { level.transmute_tile(me, 'green_wall'); }, }, green_wall: { - draw_layer: LAYER_TERRAIN, - blocks_all: true, + draw_layer: DRAW_LAYERS.terrain, + blocks_collision: COLLISION.all_but_ghost, on_gray_button(me, level) { level.transmute_tile(me, 'green_floor'); }, }, green_chip: { - draw_layer: LAYER_ITEM, + draw_layer: DRAW_LAYERS.item, is_chip: true, is_required_chip: true, - blocks_monsters: true, - blocks_blocks: true, + blocks_collision: COLLISION.block_cc1 | COLLISION.monster, on_arrive(me, level, other) { if (other.type.is_player) { level.collect_chip(); @@ -684,7 +676,7 @@ const TILE_TYPES = { // Not affected by gray buttons }, green_bomb: { - draw_layer: LAYER_ITEM, + draw_layer: DRAW_LAYERS.item, is_required_chip: true, on_arrive(me, level, other) { level.remove_tile(me); @@ -699,7 +691,7 @@ const TILE_TYPES = { // Not affected by gray buttons }, purple_floor: { - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, on_gray_button(me, level) { level.transmute_tile(me, 'purple_wall'); }, @@ -711,8 +703,8 @@ const TILE_TYPES = { }, }, purple_wall: { - draw_layer: LAYER_TERRAIN, - blocks_all: true, + draw_layer: DRAW_LAYERS.terrain, + blocks_collision: COLLISION.all_but_ghost, on_gray_button(me, level) { level.transmute_tile(me, 'purple_floor'); }, @@ -724,9 +716,9 @@ const TILE_TYPES = { }, }, cloner: { - draw_layer: LAYER_TERRAIN, - // TODO not the case for an empty one in cc2, apparently - blocks_all: true, + draw_layer: DRAW_LAYERS.terrain, + // FIXME can also catch bowling balls + blocks_collision: COLLISION.player | COLLISION.block_cc1 | COLLISION.monster, traps(me, actor) { return ! actor._clone_release; }, @@ -761,7 +753,7 @@ const TILE_TYPES = { }, }, trap: { - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, add_press_ready(me, level, other) { // Same as below, but without ejection me.presses = (me.presses ?? 0) + 1; @@ -783,6 +775,7 @@ const TILE_TYPES = { remove_press(me, level) { level._set_tile_prop(me, 'presses', me.presses - 1); }, + // FIXME also doesn't trap ghosts, is that a special case??? traps(me, actor) { return ! me.presses; }, @@ -803,7 +796,7 @@ const TILE_TYPES = { }, }, transmogrifier: { - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, _mogrifications: { player: 'player2', player2: 'player', @@ -840,13 +833,13 @@ const TILE_TYPES = { }, }, teleport_blue: { - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, teleport_dest_order(me, level, other) { return level.iter_tiles_in_reading_order(me.cell, 'teleport_blue', true); }, }, teleport_red: { - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, teleport_try_all_directions: true, teleport_allow_override: true, teleport_dest_order(me, level, other) { @@ -854,7 +847,7 @@ const TILE_TYPES = { }, }, teleport_green: { - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, teleport_try_all_directions: true, teleport_dest_order(me, level, other) { let all = Array.from(level.iter_tiles_in_reading_order(me.cell, 'teleport_green')); @@ -873,7 +866,7 @@ const TILE_TYPES = { }, }, teleport_yellow: { - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, teleport_allow_override: true, teleport_dest_order(me, level, other) { // FIXME special pickup behavior; NOT an item though, does not combine with no sign @@ -885,7 +878,7 @@ const TILE_TYPES = { // - Multiple such inputs cancel each other out // - Gray button toggles it permanently flame_jet_off: { - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, activate(me, level) { level.transmute_tile(me, 'flame_jet_on'); }, @@ -897,7 +890,7 @@ const TILE_TYPES = { }, }, flame_jet_on: { - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, activate(me, level) { level.transmute_tile(me, 'flame_jet_off'); }, @@ -925,7 +918,7 @@ const TILE_TYPES = { }, // Buttons button_blue: { - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, on_arrive(me, level, other) { level.sfx.play_once('button-press', me.cell); @@ -942,7 +935,7 @@ const TILE_TYPES = { }, }, button_yellow: { - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, on_arrive(me, level, other) { level.sfx.play_once('button-press', me.cell); @@ -960,7 +953,7 @@ const TILE_TYPES = { }, }, button_green: { - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, on_arrive(me, level, other) { level.sfx.play_once('button-press', me.cell); @@ -990,7 +983,7 @@ const TILE_TYPES = { }, }, button_brown: { - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, connects_to: 'trap', connect_order: 'forward', on_ready(me, level) { @@ -1023,7 +1016,7 @@ const TILE_TYPES = { }, }, button_red: { - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, connects_to: 'cloner', connect_order: 'forward', on_arrive(me, level, other) { @@ -1039,7 +1032,7 @@ const TILE_TYPES = { }, }, button_orange: { - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, connects_to: new Set(['flame_jet_off', 'flame_jet_on']), connect_order: 'diamond', // Both stepping on and leaving the button have the same effect: toggle the state of the @@ -1062,7 +1055,7 @@ const TILE_TYPES = { }, }, button_pink: { - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, is_power_source: true, get_emitting_edges(me, level) { // We emit current as long as there's an actor fully on us @@ -1082,7 +1075,7 @@ const TILE_TYPES = { }, button_black: { // TODO not implemented - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, is_power_source: true, get_emitting_edges(me, level) { // We emit current as long as there's NOT an actor fully on us @@ -1096,7 +1089,7 @@ const TILE_TYPES = { }, button_gray: { // TODO only partially implemented - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, on_arrive(me, level, other) { level.sfx.play_once('button-press', me.cell); @@ -1131,7 +1124,7 @@ const TILE_TYPES = { 'latch-cw': [], 'latch-ccw': [], }, - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, is_power_source: true, get_emitting_edges(me, level) { if (me.gate_type === 'and') { @@ -1167,9 +1160,8 @@ const TILE_TYPES = { // Time alteration stopwatch_bonus: { - draw_layer: LAYER_ITEM, - blocks_monsters: true, - blocks_blocks: true, + draw_layer: DRAW_LAYERS.item, + blocks_collision: COLLISION.block_cc1 | COLLISION.monster, on_arrive(me, level, other) { if (other.type.is_player) { level.remove_tile(me); @@ -1178,9 +1170,8 @@ const TILE_TYPES = { }, }, stopwatch_penalty: { - draw_layer: LAYER_ITEM, - blocks_monsters: true, - blocks_blocks: true, + draw_layer: DRAW_LAYERS.item, + blocks_collision: COLLISION.block_cc1 | COLLISION.monster, on_arrive(me, level, other) { if (other.type.is_player) { level.remove_tile(me); @@ -1189,9 +1180,8 @@ const TILE_TYPES = { }, }, stopwatch_toggle: { - draw_layer: LAYER_ITEM, - blocks_monsters: true, - blocks_blocks: true, + draw_layer: DRAW_LAYERS.item, + blocks_collision: COLLISION.block_cc1 | COLLISION.monster, on_arrive(me, level, other) { if (other.type.is_player) { level.pause_timer(); @@ -1201,56 +1191,56 @@ const TILE_TYPES = { // Critters bug: { - draw_layer: LAYER_ACTOR, + draw_layer: DRAW_LAYERS.actor, is_actor: true, is_monster: true, - blocks_monsters: true, - blocks_blocks: true, + collision_mask: COLLISION.monster | COLLISION.bug, + blocks_collision: COLLISION.all_but_player, movement_mode: 'follow-left', movement_speed: 4, }, paramecium: { - draw_layer: LAYER_ACTOR, + draw_layer: DRAW_LAYERS.actor, is_actor: true, is_monster: true, - blocks_monsters: true, - blocks_blocks: true, + collision_mask: COLLISION.monster, + blocks_collision: COLLISION.all_but_player, movement_mode: 'follow-right', movement_speed: 4, }, ball: { - draw_layer: LAYER_ACTOR, + draw_layer: DRAW_LAYERS.actor, is_actor: true, is_monster: true, - blocks_monsters: true, - blocks_blocks: true, + collision_mask: COLLISION.monster, + blocks_collision: COLLISION.all_but_player, movement_mode: 'bounce', movement_speed: 4, }, walker: { - draw_layer: LAYER_ACTOR, + draw_layer: DRAW_LAYERS.actor, is_actor: true, is_monster: true, - blocks_monsters: true, - blocks_blocks: true, + collision_mask: COLLISION.monster, + blocks_collision: COLLISION.all_but_player, movement_mode: 'bounce-random', movement_speed: 4, }, tank_blue: { - draw_layer: LAYER_ACTOR, + draw_layer: DRAW_LAYERS.actor, is_actor: true, is_monster: true, - blocks_monsters: true, - blocks_blocks: true, + collision_mask: COLLISION.monster, + blocks_collision: COLLISION.all_but_player, movement_mode: 'forward', movement_speed: 4, }, tank_yellow: { - draw_layer: LAYER_ACTOR, + draw_layer: DRAW_LAYERS.actor, is_actor: true, is_monster: true, - blocks_monsters: true, - blocks_blocks: true, + collision_mask: COLLISION.monster, + blocks_collision: COLLISION.all_but_player, pushes: { dirt_block: true, ice_block: true, @@ -1259,60 +1249,61 @@ const TILE_TYPES = { movement_speed: 4, }, blob: { - draw_layer: LAYER_ACTOR, + draw_layer: DRAW_LAYERS.actor, is_actor: true, is_monster: true, - blocks_monsters: true, - blocks_blocks: true, + collision_mask: COLLISION.monster, + blocks_collision: COLLISION.all_but_player, movement_mode: 'random', movement_speed: 8, }, teeth: { - draw_layer: LAYER_ACTOR, + draw_layer: DRAW_LAYERS.actor, is_actor: true, is_monster: true, - blocks_monsters: true, - blocks_blocks: true, + collision_mask: COLLISION.monster, + blocks_collision: COLLISION.all_but_player, movement_mode: 'pursue', movement_speed: 4, uses_teeth_hesitation: true, }, fireball: { - draw_layer: LAYER_ACTOR, + draw_layer: DRAW_LAYERS.actor, is_actor: true, is_monster: true, - blocks_monsters: true, - blocks_blocks: true, + collision_mask: COLLISION.monster | COLLISION.fireball, + blocks_collision: COLLISION.all_but_player, movement_mode: 'turn-right', movement_speed: 4, ignores: new Set(['fire', 'flame_jet_on']), }, glider: { - draw_layer: LAYER_ACTOR, + draw_layer: DRAW_LAYERS.actor, is_actor: true, is_monster: true, - blocks_monsters: true, - blocks_blocks: true, + collision_mask: COLLISION.monster, + blocks_collision: COLLISION.all_but_player, movement_mode: 'turn-left', movement_speed: 4, ignores: new Set(['water']), }, ghost: { - draw_layer: LAYER_ACTOR, + draw_layer: DRAW_LAYERS.actor, is_actor: true, is_monster: true, - blocks_monsters: true, - blocks_blocks: true, + collision_mask: COLLISION.ghost, + blocks_collision: COLLISION.all_but_player, + has_inventory: true, movement_mode: 'turn-right', movement_speed: 4, // TODO ignores /most/ walls. collision is basically completely different. has a regular inventory, except red key. good grief }, floor_mimic: { - draw_layer: LAYER_ACTOR, + draw_layer: DRAW_LAYERS.actor, is_actor: true, is_monster: true, - blocks_monsters: true, - blocks_blocks: true, + collision_mask: COLLISION.monster, + blocks_collision: COLLISION.all_but_player, // TODO not like teeth; always pursues // TODO takes 3 turns off! movement_mode: 'pursue', @@ -1321,11 +1312,11 @@ const TILE_TYPES = { rover: { // TODO this guy is a nightmare // TODO pushes blocks apparently?? - draw_layer: LAYER_ACTOR, + draw_layer: DRAW_LAYERS.actor, is_actor: true, is_monster: true, - blocks_monsters: true, - blocks_blocks: true, + collision_mask: COLLISION.monster | COLLISION.rover, + blocks_collision: COLLISION.all_but_player, can_reveal_walls: true, movement_mode: 'random', movement_speed: 4, @@ -1335,14 +1326,14 @@ const TILE_TYPES = { key_red: { // TODO Red key can ONLY be picked up by players (and doppelgangers), no other actor that // has an inventory - draw_layer: LAYER_ITEM, + draw_layer: DRAW_LAYERS.item, is_item: true, is_key: true, }, key_blue: { // Blue key is picked up by dirt blocks and all monsters, including those that don't have an // inventory normally - draw_layer: LAYER_ITEM, + draw_layer: DRAW_LAYERS.item, is_item: true, is_key: true, on_arrive(me, level, other) { @@ -1357,35 +1348,31 @@ const TILE_TYPES = { }, }, key_yellow: { - draw_layer: LAYER_ITEM, + draw_layer: DRAW_LAYERS.item, is_item: true, is_key: true, - blocks_monsters: true, - blocks_blocks: true, + blocks_collision: COLLISION.block_cc1 | COLLISION.monster, }, key_green: { - draw_layer: LAYER_ITEM, + draw_layer: DRAW_LAYERS.item, is_item: true, is_key: true, - blocks_monsters: true, - blocks_blocks: true, + blocks_collision: COLLISION.block_cc1 | COLLISION.monster, }, // Boots // TODO note: ms allows blocks to pass over tools cleats: { - draw_layer: LAYER_ITEM, + draw_layer: DRAW_LAYERS.item, is_item: true, is_tool: true, - blocks_monsters: true, - blocks_blocks: true, + blocks_collision: COLLISION.block_cc1 | COLLISION.monster, item_ignores: new Set(['ice', 'ice_nw', 'ice_ne', 'ice_sw', 'ice_se']), }, suction_boots: { - draw_layer: LAYER_ITEM, + draw_layer: DRAW_LAYERS.item, is_item: true, is_tool: true, - blocks_monsters: true, - blocks_blocks: true, + blocks_collision: COLLISION.block_cc1 | COLLISION.monster, item_ignores: new Set([ 'force_floor_n', 'force_floor_s', @@ -1395,93 +1382,84 @@ const TILE_TYPES = { ]), }, fire_boots: { - draw_layer: LAYER_ITEM, + draw_layer: DRAW_LAYERS.item, is_item: true, is_tool: true, - blocks_monsters: true, - blocks_blocks: true, + blocks_collision: COLLISION.block_cc1 | COLLISION.monster, item_ignores: new Set(['fire', 'flame_jet_on']), }, flippers: { - draw_layer: LAYER_ITEM, + draw_layer: DRAW_LAYERS.item, is_item: true, is_tool: true, - blocks_monsters: true, - blocks_blocks: true, + blocks_collision: COLLISION.block_cc1 | COLLISION.monster, item_ignores: new Set(['water']), }, hiking_boots: { - draw_layer: LAYER_ITEM, + draw_layer: DRAW_LAYERS.item, is_item: true, is_tool: true, - blocks_monsters: true, - blocks_blocks: true, + blocks_collision: COLLISION.block_cc1 | COLLISION.monster, // FIXME uhh these "ignore" that dirt and gravel block us, but they don't ignore the on_arrive, so, uhhhh }, // Other tools dynamite: { - draw_layer: LAYER_ITEM, + draw_layer: DRAW_LAYERS.item, is_item: true, is_tool: true, - blocks_monsters: true, - blocks_blocks: true, + blocks_collision: COLLISION.block_cc1 | COLLISION.monster, // FIXME does a thing when dropped, but that isn't implemented at all yet }, bowling_ball: { // TODO not implemented, rolls when dropped, has an inventory, yadda yadda - draw_layer: LAYER_ITEM, + draw_layer: DRAW_LAYERS.item, is_item: true, is_tool: true, - blocks_monsters: true, - blocks_blocks: true, + blocks_collision: COLLISION.block_cc1 | COLLISION.monster, }, xray_eye: { // TODO not implemented - draw_layer: LAYER_ITEM, + draw_layer: DRAW_LAYERS.item, is_item: true, is_tool: true, - blocks_monsters: true, - blocks_blocks: true, + blocks_collision: COLLISION.block_cc1 | COLLISION.monster, }, helmet: { // TODO not implemented - draw_layer: LAYER_ITEM, + draw_layer: DRAW_LAYERS.item, is_item: true, is_tool: true, - blocks_monsters: true, - blocks_blocks: true, + blocks_collision: COLLISION.block_cc1 | COLLISION.monster, }, railroad_sign: { - draw_layer: LAYER_ITEM, + draw_layer: DRAW_LAYERS.item, is_item: true, is_tool: true, - blocks_monsters: true, - blocks_blocks: true, + blocks_collision: COLLISION.block_cc1 | COLLISION.monster, // FIXME this doesn't work any more, need to put it in railroad blocks impl item_ignores: new Set(['railroad']), }, foil: { // TODO not implemented - draw_layer: LAYER_ITEM, + draw_layer: DRAW_LAYERS.item, is_item: true, is_tool: true, - blocks_monsters: true, - blocks_blocks: true, + blocks_collision: COLLISION.block_cc1 | COLLISION.monster, }, lightning_bolt: { // TODO not implemented - draw_layer: LAYER_ITEM, + draw_layer: DRAW_LAYERS.item, is_item: true, is_tool: true, - blocks_monsters: true, - blocks_blocks: true, + blocks_collision: COLLISION.block_cc1 | COLLISION.monster, }, // Progression player: { - draw_layer: LAYER_ACTOR, + draw_layer: DRAW_LAYERS.actor, is_actor: true, is_player: true, + collision_mask: COLLISION.player1, has_inventory: true, can_reveal_walls: true, movement_speed: 4, @@ -1496,9 +1474,10 @@ const TILE_TYPES = { visual_state: player_visual_state, }, player2: { - draw_layer: LAYER_ACTOR, + draw_layer: DRAW_LAYERS.actor, is_actor: true, is_player: true, + collision_mask: COLLISION.player2, has_inventory: true, can_reveal_walls: true, movement_speed: 4, @@ -1514,11 +1493,10 @@ const TILE_TYPES = { visual_state: player_visual_state, }, chip: { - draw_layer: LAYER_ITEM, + draw_layer: DRAW_LAYERS.item, is_chip: true, is_required_chip: true, - blocks_monsters: true, - blocks_blocks: true, + blocks_collision: COLLISION.block_cc1 | COLLISION.monster, on_arrive(me, level, other) { if (other.type.is_player) { level.collect_chip(); @@ -1527,10 +1505,9 @@ const TILE_TYPES = { }, }, chip_extra: { - draw_layer: LAYER_ITEM, + draw_layer: DRAW_LAYERS.item, is_chip: true, - blocks_monsters: true, - blocks_blocks: true, + blocks_collision: COLLISION.block_cc1 | COLLISION.monster, on_arrive(me, level, other) { if (other.type.is_player) { level.collect_chip(); @@ -1539,9 +1516,8 @@ const TILE_TYPES = { }, }, score_10: { - draw_layer: LAYER_ITEM, - blocks_monsters: true, - blocks_blocks: true, + draw_layer: DRAW_LAYERS.item, + blocks_collision: COLLISION.block_cc1 | COLLISION.monster, on_arrive(me, level, other) { if (other.type.is_player) { level.adjust_bonus(10); @@ -1550,9 +1526,8 @@ const TILE_TYPES = { }, }, score_100: { - draw_layer: LAYER_ITEM, - blocks_monsters: true, - blocks_blocks: true, + draw_layer: DRAW_LAYERS.item, + blocks_collision: COLLISION.block_cc1 | COLLISION.monster, on_arrive(me, level, other) { if (other.type.is_player) { level.adjust_bonus(100); @@ -1561,9 +1536,8 @@ const TILE_TYPES = { }, }, score_1000: { - draw_layer: LAYER_ITEM, - blocks_monsters: true, - blocks_blocks: true, + draw_layer: DRAW_LAYERS.item, + blocks_collision: COLLISION.block_cc1 | COLLISION.monster, on_arrive(me, level, other) { if (other.type.is_player) { level.adjust_bonus(1000); @@ -1572,9 +1546,8 @@ const TILE_TYPES = { }, }, score_2x: { - draw_layer: LAYER_ITEM, - blocks_monsters: true, - blocks_blocks: true, + draw_layer: DRAW_LAYERS.item, + blocks_collision: COLLISION.block_cc1 | COLLISION.monster, on_arrive(me, level, other) { if (other.type.is_player) { level.adjust_bonus(0, 2); @@ -1584,15 +1557,13 @@ const TILE_TYPES = { }, hint: { - draw_layer: LAYER_TERRAIN, + draw_layer: DRAW_LAYERS.terrain, is_hint: true, - blocks_monsters: true, - blocks_blocks: true, + blocks_collision: COLLISION.block_cc1 | COLLISION.monster, }, socket: { - draw_layer: LAYER_TERRAIN, - blocks_monsters: true, - blocks_blocks: true, + draw_layer: DRAW_LAYERS.terrain, + blocks_collision: COLLISION.block_cc1 | COLLISION.block_cc2 | COLLISION.monster, blocks(me, level, other) { return (level.chips_remaining > 0); }, @@ -1604,9 +1575,8 @@ const TILE_TYPES = { }, }, exit: { - draw_layer: LAYER_TERRAIN, - blocks_monsters: true, - blocks_blocks: true, + draw_layer: DRAW_LAYERS.terrain, + blocks_collision: COLLISION.block_cc1 | COLLISION.block_cc2 | COLLISION.monster, on_arrive(me, level, other) { if (other.type.is_player) { level.win(); @@ -1616,39 +1586,41 @@ const TILE_TYPES = { // VFX splash: { - draw_layer: LAYER_OVERLAY, + draw_layer: DRAW_LAYERS.overlay, is_actor: true, - blocks_players: true, + collision_mask: 0, + blocks_collision: COLLISION.player, ttl: 6, }, explosion: { - draw_layer: LAYER_OVERLAY, + draw_layer: DRAW_LAYERS.overlay, is_actor: true, - blocks_players: true, + collision_mask: 0, + blocks_collision: COLLISION.player, ttl: 6, }, // Invalid tiles that appear in some CCL levels because community level // designers love to make nonsense bogus_player_win: { - draw_layer: LAYER_TERRAIN, - blocks_all: true, + draw_layer: DRAW_LAYERS.terrain, + blocks_collision: COLLISION.all, }, bogus_player_swimming: { - draw_layer: LAYER_TERRAIN, - blocks_all: true, + draw_layer: DRAW_LAYERS.terrain, + blocks_collision: COLLISION.all, }, bogus_player_drowned: { - draw_layer: LAYER_TERRAIN, - blocks_all: true, + draw_layer: DRAW_LAYERS.terrain, + blocks_collision: COLLISION.all, }, bogus_player_burned_fire: { - draw_layer: LAYER_TERRAIN, - blocks_all: true, + draw_layer: DRAW_LAYERS.terrain, + blocks_collision: COLLISION.all, }, bogus_player_burned: { - draw_layer: LAYER_TERRAIN, - blocks_all: true, + draw_layer: DRAW_LAYERS.terrain, + blocks_collision: COLLISION.all, }, }; @@ -1658,10 +1630,14 @@ for (let [name, type] of Object.entries(TILE_TYPES)) { if (type.draw_layer === undefined || type.draw_layer !== Math.floor(type.draw_layer) || - type.draw_layer >= 5) + type.draw_layer >= DRAW_LAYERS.MAX) { console.error(`Tile type ${name} has a bad draw layer`); } + + if (type.is_actor && type.collision_mask === undefined) { + console.error(`Tile type ${name} is an actor but has no collision mask`); + } } export default TILE_TYPES;