diff --git a/js/defs.js b/js/defs.js index 4226bf6..dbf5be5 100644 --- a/js/defs.js +++ b/js/defs.js @@ -57,7 +57,7 @@ export const INPUT_BITS = { wait: 0x8000, }; -export const DRAW_LAYERS = { +export const LAYERS = { terrain: 0, item: 1, item_mod: 2, @@ -66,6 +66,7 @@ export const DRAW_LAYERS = { swivel: 5, thin_wall: 6, canopy: 7, + MAX: 8, }; diff --git a/js/format-c2g.js b/js/format-c2g.js index 516a3a1..55fee9a 100644 --- a/js/format-c2g.js +++ b/js/format-c2g.js @@ -1,4 +1,4 @@ -import { DIRECTIONS, DIRECTION_ORDER } from './defs.js'; +import { DIRECTIONS, DIRECTION_ORDER, LAYERS } from './defs.js'; import * as format_base from './format-base.js'; import TILE_TYPES from './tiletypes.js'; import * as util from './util.js'; @@ -1438,7 +1438,7 @@ export function synthesize_level(stored_level) { // save it until we reach the terrain layer, and then sub it in instead. // TODO if i follow in tyler's footsteps and give swivel its own layer then i'll need to // complicate this somewhat - if (tile.type.draw_layer === 0 && dummy_terrain_tile) { + if (tile.type.layer === LAYERS.terrain && dummy_terrain_tile) { tile = dummy_terrain_tile; spec = REVERSE_TILE_ENCODING[tile.type.name]; } diff --git a/js/format-dat.js b/js/format-dat.js index 357579d..f33d224 100644 --- a/js/format-dat.js +++ b/js/format-dat.js @@ -1,4 +1,4 @@ -import { DIRECTIONS } from './defs.js'; +import { DIRECTIONS, LAYERS } from './defs.js'; import * as format_base from './format-base.js'; import TILE_TYPES from './tiletypes.js'; import * as util from './util.js'; @@ -266,7 +266,7 @@ function parse_level(bytes, number) { // Fix the "floor/empty" nonsense here by adding floor to any cell with no terrain on bottom for (let cell of level.linear_cells) { - if (cell.length === 0 || cell[0].type.draw_layer !== 0) { + if (cell.length === 0 || cell[0].type.layer !== LAYERS.terrain) { // No terrain; insert a floor cell.unshift({ type: TILE_TYPES['floor'] }); } diff --git a/js/game.js b/js/game.js index bdd9369..45861f3 100644 --- a/js/game.js +++ b/js/game.js @@ -1,5 +1,5 @@ import * as algorithms from './algorithms.js'; -import { DIRECTIONS, DIRECTION_ORDER, INPUT_BITS, TICS_PER_SECOND } from './defs.js'; +import { DIRECTIONS, DIRECTION_ORDER, LAYERS, INPUT_BITS, TICS_PER_SECOND } from './defs.js'; import { LevelInterface } from './format-base.js'; import TILE_TYPES from './tiletypes.js'; @@ -54,9 +54,15 @@ export class Tile { // Extremely awkward special case: items don't block monsters if the cell also contains an // item modifier (i.e. "no" sign) or a real 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_real_player)) - return false; + if (this.type.is_item) { + let item_mod = this.cell.get_item_mod(); + if (item_mod && item_mod.type.item_modifier) + return false; + + let actor = this.cell.get_actor(); + if (actor && actor.type.is_real_player) + return false; + } if (this.type.blocks_collision & other.type.collision_mask) return true; @@ -122,6 +128,7 @@ export class Tile { direction = tile.cell.redirect_exit(tile, direction); // Need to explicitly check this here, otherwise you could /attempt/ to push a block, // which would fail, but it would still change the block's direction + // XXX this expects to take a level but it only matters with push_mode === 'push' return tile.cell.try_leaving(tile, direction); } @@ -142,37 +149,36 @@ Tile.prototype.wire_tunnel_directions = 0; export class Cell extends Array { constructor(x, y) { - super(); + super(LAYERS.MAX); this.x = x; this.y = y; } - _add(tile, index = null) { - if (index === null) { - this.push(tile); - } - else { - this.splice(index, 0, tile); + _add(tile) { + let index = tile.type.layer; + if (this[index]) { + console.error("ATTEMPTING TO ADD", tile, "TO CELL", this, "WHICH ERASES EXISTING TILE", this[index]); + this[index].cell = null; } + this[index] = tile; tile.cell = this; } // DO NOT use me to remove a tile permanently, only to move it! // Should only be called from Level, which handles some bookkeeping! _remove(tile) { - let index = this.indexOf(tile); - if (index < 0) + let index = tile.type.layer; + if (this[index] !== tile) throw new Error("Asked to remove tile that doesn't seem to exist"); - this.splice(index, 1); + this[index] = null; tile.cell = null; - return index; } get_wired_tile() { let ret = null; for (let tile of this) { - if ((tile.wire_directions || tile.wire_tunnel_directions) && ! tile.movement_cooldown) { + if (tile && (tile.wire_directions || tile.wire_tunnel_directions) && ! tile.movement_cooldown) { ret = tile; // Don't break; we want the topmost tile! } @@ -181,52 +187,53 @@ export class Cell extends Array { } get_terrain() { - for (let tile of this) { - if (tile.type.draw_layer === 0) - return tile; - } - return null; + return this[LAYERS.terrain] ?? null; } get_actor() { - for (let tile of this) { - if (tile.type.is_actor) - return tile; - } - return null; + return this[LAYERS.actor] ?? null; } get_item() { - for (let tile of this) { - if (tile.type.is_item) - return tile; - } - return null; + return this[LAYERS.item] ?? null; } get_item_mod() { - for (let tile of this) { - if (tile.type.item_modifier) - return tile; - } - return null; + return this[LAYERS.item_mod] ?? null; } has(name) { - return this.some(tile => tile.type.name === name); + let current = this[TILE_TYPES[name].layer]; + return current && current.type.name === name; } - try_leaving(actor, direction) { - for (let tile of this) { - if (tile === actor) - continue; + // FIXME honestly no longer sure why these two are on Cell, or even separate really + try_leaving(actor, direction, level, push_mode) { + // The only tiles that can trap us are thin walls and terrain, so for perf (this is very hot + // code), only bother checking those) + let terrain = this[LAYERS.terrain]; + let thin_walls = this[LAYERS.thin_wall]; + let blocker; - if (tile.type.traps && tile.type.traps(tile, actor)) - return false; - - if (tile.type.blocks_leaving && tile.type.blocks_leaving(tile, actor, direction)) - return false; + if (thin_walls && thin_walls.type.blocks_leaving && thin_walls.type.blocks_leaving(thin_walls, actor, direction)) { + blocker = thin_walls; } + else if (terrain.type.traps && terrain.type.traps(terrain, actor)) { + blocker = terrain; + } + else if (terrain.type.blocks_leaving && terrain.type.blocks_leaving(terrain, actor, direction)) { + blocker = terrain; + } + + if (blocker) { + if (push_mode === 'push') { + if (actor.type.on_blocked) { + actor.type.on_blocked(actor, level, direction, blocker); + } + } + return false; + } + return true; } @@ -238,7 +245,6 @@ export class Cell extends Array { // - 'push': Fire bump triggers. Attempt to move pushable objects out of the way immediately. try_entering(actor, direction, level, push_mode = null) { let pushable_tiles = []; - let blocked = false; // Subtleties ahoy! This is **EXTREMELY** sensitive to ordering. Consider: // - An actor with foil MUST NOT bump a wall on the other side of a thin wall. // - A ghost with foil MUST bump a wall (even on the other side of a thin wall) and be @@ -252,19 +258,17 @@ export class Cell extends Array { // It seems the order is thus: canopy + thin wall; terrain; actor; item. Which is the usual // ordering from the top down, except that terrain is checked before actors. Really, the // ordering is from "outermost" to "innermost", which makes physical sense. - // FIXME make that work, then. i think i may need to shift to fixed slots unfortunately - // (Note that here, and anywhere else that has any chance of altering the cell's contents, - // we iterate over a copy of the cell to insulate ourselves from tiles appearing or - // disappearing mid-iteration.) - for (let tile of Array.from(this).reverse()) { + for (let layer of [ + LAYERS.canopy, LAYERS.thin_wall, LAYERS.terrain, LAYERS.swivel, + LAYERS.actor, LAYERS.item_mod, LAYERS.item]) + { + let tile = this[layer]; + if (! tile) + continue; + // TODO check ignores here? - // Note that if they can't enter this cell because of a thin wall, then they can't bump - // any of our other tiles either. (This is my best guess at the actual behavior, seeing - // as walls also block everything but players can obviously bump /those/.) - if (! blocked) { - if (tile.type.on_bumped) { - tile.type.on_bumped(tile, level, actor); - } + if (tile.type.on_bumped) { + tile.type.on_bumped(tile, level, actor); } if (! tile.blocks(actor, direction, level)) @@ -274,23 +278,19 @@ export class Cell extends Array { return false; if (! actor.can_push(tile, direction)) { + // It's in our way and we can't push it, so we're done here if (push_mode === 'push') { - // Track this instead of returning immediately, because 'push' mode also bumps - // every tile in the cell - blocked = true; - } - else { - return false; + if (actor.type.on_blocked) { + actor.type.on_blocked(actor, level, direction, tile); + } } + return false; } // Collect pushables for later, so we don't inadvertently push through a wall pushable_tiles.push(tile); } - if (blocked) - return false; - // If we got this far, all that's left is to deal with pushables if (pushable_tiles.length > 0) { // This ends recursive push attempts, which can happen with a row of ice clogged by ice @@ -340,7 +340,7 @@ export class Cell extends Array { // BLOX replay, and right at the end ice blocks spring mine each other. also, the wiki // suggests something about another actor moving away at the same time? if (! (level.compat.emulate_spring_mining && actor.type.is_real_player) && - push_mode === 'push' && this.some(tile => tile.blocks(actor, direction, level))) + push_mode === 'push' && this.some(tile => tile && tile.blocks(actor, direction, level))) return false; } @@ -349,10 +349,9 @@ export class Cell extends Array { // Special railroad ability: change the direction we attempt to leave redirect_exit(actor, direction) { - for (let tile of this) { - if (tile.type.redirect_exit) { - return tile.type.redirect_exit(tile, actor, direction); - } + let terrain = this.get_terrain(); + if (terrain && terrain.type.redirect_exit) { + return terrain.type.redirect_exit(terrain, actor, direction); } return direction; } @@ -455,6 +454,7 @@ export class Level extends LevelInterface { let stored_cell = this.stored_level.linear_cells[n]; n++; + // FIXME give this same treatment to stored cells (otherwise the editor is fucked) for (let template_tile of stored_cell) { let tile = Tile.from_template(template_tile); if (tile.type.is_hint) { @@ -508,7 +508,7 @@ export class Level extends LevelInterface { if (target_cell_n && target_cell_n < this.width * this.height) { let [tx, ty] = this.stored_level.scalar_to_coords(target_cell_n); for (let tile of this.cell(tx, ty)) { - if (goals === tile.type.name) { + if (tile && goals === tile.type.name) { connectable.connection = tile; break; } @@ -522,7 +522,7 @@ export class Level extends LevelInterface { for (let cell of this.iter_cells_in_diamond(connectable.cell)) { let target = null; for (let tile of cell) { - if (goals.has(tile.type.name)) { + if (tile && goals.has(tile.type.name)) { target = tile; break; } @@ -625,7 +625,10 @@ export class Level extends LevelInterface { // Dead end handling (potentially logic gates, etc.) (cell, edge) => { for (let tile of cell) { - if (tile.type.name === 'logic_gate') { + if (! tile) { + continue; + } + else if (tile.type.name === 'logic_gate') { // Logic gates are the one non-wired tile that get attached to circuits, // mostly so blue teleporters can follow them if (! tile.circuits) { @@ -659,6 +662,8 @@ export class Level extends LevelInterface { for (let i = this.linear_cells.length - 1; i >= 0; i--) { let cell = this.linear_cells[i]; for (let tile of cell) { + if (! tile) + continue; if (tile.type.on_ready) { tile.type.on_ready(tile, this); } @@ -737,7 +742,7 @@ export class Level extends LevelInterface { for (let i = this.linear_cells.length - 1; i >= 0; i--) { let cell = this.linear_cells[i]; for (let tile of cell) { - if (tile.type.on_begin) { + if (tile && tile.type.on_begin) { tile.type.on_begin(tile, this); } } @@ -1116,6 +1121,7 @@ export class Level extends LevelInterface { this.commit(); } + // TODO this only has one caller _extract_player_directions(input) { // Extract directions from an input mask let dir1 = null, dir2 = null; @@ -1322,7 +1328,7 @@ export class Level extends LevelInterface { } if (forced_only) return; - if (actor.cell.some(tile => tile.type.traps && tile.type.traps(tile, actor))) { + if (terrain.type.traps && terrain.type.traps(terrain, actor)) { // An actor in a cloner or a closed trap can't turn // TODO because of this, if a tank is trapped when a blue button is pressed, then // when released, it will make one move out of the trap and /then/ turn around and @@ -1366,7 +1372,7 @@ export class Level extends LevelInterface { check_movement(actor, orig_cell, direction, push_mode) { let dest_cell = this.get_neighboring_cell(orig_cell, direction); let success = (dest_cell && - orig_cell.try_leaving(actor, direction) && + orig_cell.try_leaving(actor, direction, this, push_mode) && dest_cell.try_entering(actor, direction, this, push_mode)); // If we have the hook, pull anything behind us, now that we're out of the way. @@ -1439,12 +1445,8 @@ export class Level extends LevelInterface { let speed = actor.type.movement_speed; let move = DIRECTIONS[direction].movement; - if (! this.check_movement(actor, actor.cell, direction, 'push')) { - if (actor.type.on_blocked) { - actor.type.on_blocked(actor, this, direction); - } + if (! this.check_movement(actor, actor.cell, direction, 'push')) return false; - } // We're clear! Compute our speed and move us // FIXME this feels clunky @@ -1497,6 +1499,8 @@ export class Level extends LevelInterface { // XXX that's still not perfect; if actor X is tic-misaligned, like if there's a chain // of 3 or more actors cloning directly onto red buttons for other cloners, then this // cannot possibly work + // TODO now that i have steam-strict mode this is largely pointless, just do what seems + // correct actor.cooldown_delay_hack = 1; return true; } @@ -1513,14 +1517,21 @@ export class Level extends LevelInterface { return; let original_cell = actor.cell; + // Physically remove the actor first, so that it won't get in the way of e.g. a splash + // spawned from stepping off of a lilypad + this.remove_tile(actor); // Announce we're leaving, for the handful of tiles that care about it - for (let tile of Array.from(original_cell)) { + for (let tile of original_cell) { + if (! tile) + continue; if (tile === actor) continue; if (actor.ignores(tile.type.name)) continue; + // FIXME ah, stepping off a lilypad will add a splash but we're still here? but then + // why did the warning not catch it if (tile.type.on_depart) { tile.type.on_depart(tile, this, actor); } @@ -1532,7 +1543,7 @@ export class Level extends LevelInterface { } for (let tile of goal_cell) { // FIXME this could go in on_approach now - if (actor === this.player && tile.type.is_hint) { + if (tile && actor === this.player && tile.type.is_hint) { this.hint_shown = tile.hint_text ?? this.stored_level.hint; } } @@ -1540,7 +1551,9 @@ export class Level extends LevelInterface { // Announce we're approaching. Slide mode is set here, since it's about the tile we're // moving towards and needs to last through our next decision this.make_slide(actor, null); - for (let tile of Array.from(goal_cell)) { + for (let tile of goal_cell) { + if (! tile) + continue; if (tile === actor) continue; if (actor.ignores(tile.type.name)) @@ -1570,10 +1583,17 @@ export class Level extends LevelInterface { } } - // Now physically move the actor; we wait until here in case some of those callbacks handled - // interactions between actors on the same layer (e.g. monsters erasing splashes) - this.remove_tile(actor); - this.add_tile(actor, goal_cell); + // Now add the actor back; we have to wait this long because e.g. monsters erase splashes + if (goal_cell.get_actor()) { + // FIXME a monster or block killing the player will still move into her cell!!! i don't + // know what to do about this, i feel like i tried making monster/player block each + // other before and it did not go well. maybe it was an ordering issue though? + this.add_tile(actor, original_cell); + return; + } + else { + this.add_tile(actor, goal_cell); + } // If we're a monster stepping on the player's tail, that also kills her immediately; the // player and a monster must be strictly more than 4 tics apart @@ -1598,7 +1618,11 @@ export class Level extends LevelInterface { // Step on every tile in a cell we just arrived in step_on_cell(actor, cell) { // Step on topmost things first -- notably, it's safe to step on water with flippers on top - for (let tile of Array.from(cell).reverse()) { + // TODO is there a custom order here similar to collision checking? + for (let layer = LAYERS.MAX - 1; layer >= 0; layer--) { + let tile = cell[layer]; + if (! tile) + continue; if (tile === actor) continue; if (actor.ignores(tile.type.name)) @@ -1607,7 +1631,7 @@ export class Level extends LevelInterface { if (tile.type.is_item && // FIXME implement item priority i'm begging you ((actor.type.has_inventory && ! (tile.type.name === 'key_red' && ! actor.type.is_player)) || - cell.some(t => t.type.item_modifier === 'pickup')) && + (cell.get_item_mod() && cell.get_item_mod().type.item_modifier === 'pickup')) && this.attempt_take(actor, tile)) { if (tile.type.is_key) { @@ -1684,7 +1708,7 @@ export class Level extends LevelInterface { for ([dest, direction] of teleporter.type.teleport_dest_order(teleporter, this, actor)) { // Teleporters already containing an actor are blocked and unusable // FIXME should check collision? otherwise this catches non-blocking vfx... - if (dest.cell.some(tile => tile.type.is_actor && tile !== actor && ! tile.type.ttl)) + if (dest.cell.some(tile => tile && tile.type.is_actor && tile !== actor && ! tile.type.ttl)) continue; // XXX lynx treats this as a slide and does it in a pass in the main loop @@ -1767,7 +1791,7 @@ export class Level extends LevelInterface { // Attempt to place an item in the world, as though dropped by an actor _place_dropped_item(name, cell, dropping_actor) { let type = TILE_TYPES[name]; - if (type.draw_layer === 0) { + if (type.layer === LAYERS.terrain) { // Terrain items (i.e., yellow teleports) can only be dropped on regular floor let terrain = cell.get_terrain(); if (terrain.type.name !== 'floor') @@ -1794,12 +1818,19 @@ export class Level extends LevelInterface { if (type.is_actor) { // This is tricky -- the item has become an actor, but whatever dropped it is // already in this cell's actor layer. But we also know for sure that there's no - // item in this cell, so we'll cheat a little: add it in the item layer, set it - // rolling (which should shift it into the next cell over), then switch it to the - // actor layer. - // TODO do that - this.add_actor(tile); - this.attempt_out_of_turn_step(tile, dropping_actor.direction); + // item in this cell, so we'll cheat a little: remove the dropping actor, set the + // item moving, then put the dropping actor back before anyone notices. + cell._remove(dropping_actor); + this.add_tile(tile, cell); + if (! this.attempt_out_of_turn_step(tile, dropping_actor.direction)) { + // It was unable to move, so there's nothing we can do but destroy it + // TODO maybe blow it up with a nonblocking vfx? in cc2 it just vanishes + this.remove_tile(tile); + } + else { + this.add_actor(tile); + } + cell._add(dropping_actor); } else { this.add_tile(tile, cell); @@ -1946,7 +1977,9 @@ export class Level extends LevelInterface { // Some non-actor tiles still want to act every tic. Note that this should happen AFTER wiring. _do_static_phase() { for (let tile of this.static_on_tic_tiles) { - tile.type.on_tic(tile, this); + if (tile.type.on_tic) { + tile.type.on_tic(tile, this); + } } } @@ -1964,6 +1997,7 @@ export class Level extends LevelInterface { // The starting cell is iterated last. *iter_tiles_in_reading_order(start_cell, name, reverse = false) { let i = this.coords_to_scalar(start_cell.x, start_cell.y); + let index = TILE_TYPES[name].layer; while (true) { if (reverse) { i -= 1; @@ -1977,10 +2011,9 @@ export class Level extends LevelInterface { } let cell = this.linear_cells[i]; - for (let tile of cell) { - if (tile.type.name === name) { - yield tile; - } + let tile = cell[index]; + if (tile && tile.type.name === name) { + yield tile; } if (cell === start_cell) @@ -2228,12 +2261,12 @@ export class Level extends LevelInterface { remove_tile(tile) { let cell = tile.cell; - let index = cell._remove(tile); - this._push_pending_undo(() => cell._add(tile, index)); + cell._remove(tile); + this._push_pending_undo(() => cell._add(tile)); } - add_tile(tile, cell, index = null) { - cell._add(tile, index); + add_tile(tile, cell) { + cell._add(tile); this._push_pending_undo(() => cell._remove(tile)); } @@ -2260,13 +2293,28 @@ export class Level extends LevelInterface { } transmute_tile(tile, name) { - let current = tile.type.name; - this._push_pending_undo(() => tile.type = TILE_TYPES[current]); - tile.type = TILE_TYPES[name]; + let old_type = tile.type; + let new_type = TILE_TYPES[name]; + if (old_type.layer !== new_type.layer) { + // Move it to the right layer! + let cell = tile.cell; + cell._remove(tile); + tile.type = new_type; + cell._add(tile); + this._push_pending_undo(() => { + cell._remove(tile); + tile.type = old_type; + cell._add(tile); + }); + } + else { + tile.type = new_type; + this._push_pending_undo(() => tile.type = old_type); + } // For transmuting into an animation, set up the timer immediately if (tile.type.ttl) { - if (! TILE_TYPES[current].is_actor) { + if (! old_type.is_actor) { console.warn("Transmuting a non-actor into an animation!"); } this._set_tile_prop(tile, 'previous_cell', null); @@ -2293,7 +2341,7 @@ export class Level extends LevelInterface { let dropped_item; if (! tile.type.is_key && actor.toolbelt && actor.toolbelt.length >= 4) { let oldest_item_type = TILE_TYPES[actor.toolbelt[0]]; - if (oldest_item_type.draw_layer === 0 && cell.get_terrain().type.name !== 'floor') { + if (oldest_item_type.layer === LAYERS.terrain && cell.get_terrain().type.name !== 'floor') { // This is a yellow teleporter, and we are not standing on floor; abort! return false; } @@ -2304,7 +2352,7 @@ export class Level extends LevelInterface { } if (this.give_actor(actor, tile.type.name)) { - if (tile.type.draw_layer === 0) { + if (tile.type.layer === LAYERS.terrain) { // This should only happen for the yellow teleporter this.transmute_tile(tile, 'floor'); } diff --git a/js/main-editor.js b/js/main-editor.js index 112afa7..6a36511 100644 --- a/js/main-editor.js +++ b/js/main-editor.js @@ -1,6 +1,6 @@ import * as fflate from 'https://unpkg.com/fflate/esm/index.mjs'; -import { DIRECTIONS, TICS_PER_SECOND } from './defs.js'; +import { DIRECTIONS, LAYERS, TICS_PER_SECOND } from './defs.js'; import { TILES_WITH_PROPS } from './editor-tile-overlays.js'; import * as format_base from './format-base.js'; import * as c2g from './format-c2g.js'; @@ -472,7 +472,7 @@ class PencilOperation extends DrawOperation { let cell = this.cell(x, y); cell.length = 0; let type = this.editor.palette_selection.type; - if (type.draw_layer !== 0) { + if (type.layer !== LAYERS.terrain) { cell.push({type: TILE_TYPES.floor}); } this.editor.place_in_cell(x, y, template); @@ -3111,13 +3111,13 @@ export class Editor extends PrimaryView { return; } - if (cell[i].type.draw_layer === tile.type.draw_layer) { + if (cell[i].type.layer === tile.type.layer) { cell.splice(i, 1); } } cell.push(Object.assign({}, tile)); - cell.sort((a, b) => a.type.draw_layer - b.type.draw_layer); + cell.sort((a, b) => a.type.layer - b.type.layer); } erase_tile(cell, tile = null) { @@ -3127,7 +3127,7 @@ export class Editor extends PrimaryView { tile = this.palette_selection; } - let layer = tile.type.draw_layer; + let layer = tile.type.layer; for (let i = cell.length - 1; i >= 0; i--) { // If we find a tile of the same type as the one being drawn, see if it has custom // combine behavior (only the case if it came from the palette) @@ -3142,7 +3142,7 @@ export class Editor extends PrimaryView { return; } - if (cell[i].type.draw_layer === layer) { + if (cell[i].type.layer === layer) { cell.splice(i, 1); } } diff --git a/js/main.js b/js/main.js index 2861d39..dc23601 100644 --- a/js/main.js +++ b/js/main.js @@ -2622,6 +2622,7 @@ class PackTestDialog extends DialogOverlay { try { stored_level = pack.load_level(i); + console.log(i + 1, stored_level.title); if (! stored_level.has_replay) { record_result('no-replay', "No replay"); continue; diff --git a/js/renderer-canvas.js b/js/renderer-canvas.js index 5e8fded..b74b183 100644 --- a/js/renderer-canvas.js +++ b/js/renderer-canvas.js @@ -1,4 +1,4 @@ -import { DIRECTIONS, DRAW_LAYERS } from './defs.js'; +import { DIRECTIONS, LAYERS } from './defs.js'; import { mk } from './util.js'; import TILE_TYPES from './tiletypes.js'; @@ -157,46 +157,45 @@ export class CanvasRenderer { // FIXME this is a bit inefficient when there are a lot of rarely-used layers; consider // instead drawing everything under actors, then actors, then everything above actors? // (note: will need to first fix the game to ensure everything is stacked correctly!) - for (let layer = 0; layer < DRAW_LAYERS.MAX; layer++) { + for (let layer = 0; layer < LAYERS.MAX; layer++) { for (let x = xf0; x <= x1; x++) { for (let y = yf0; y <= y1; y++) { let cell = this.level.cell(x, y); - for (let tile of cell) { - if (tile.type.draw_layer !== layer) - continue; + let tile = cell[layer]; + if (! tile) + continue; - let vx, vy; - if (tile.type.is_actor && - // FIXME kind of a hack for the editor, which uses bare tile objects - tile.visual_position) - { - // Handle smooth scrolling - [vx, vy] = tile.visual_position(tic_offset); - // Round this to the pixel grid too! - vx = Math.floor(vx * tw + 0.5) / tw; - vy = Math.floor(vy * th + 0.5) / th; - } - else { - // Non-actors can't move - vx = x; - vy = y; - } - - // For actors (i.e., blocks), perception only applies if there's something - // of potential interest underneath - let perception = this.perception; - if (perception !== 'normal' && tile.type.is_actor && - ! cell.some(t => - t.type.draw_layer < layer && - ! (t.type.name === 'floor' && (t.wire_directions | t.wire_tunnel_directions) === 0))) - { - perception = 'normal'; - } - - this.tileset.draw( - tile, tic, perception, - this._make_tileset_blitter(this.ctx, vx - x0, vy - y0)); + let vx, vy; + if (tile.type.is_actor && + // FIXME kind of a hack for the editor, which uses bare tile objects + tile.visual_position) + { + // Handle smooth scrolling + [vx, vy] = tile.visual_position(tic_offset); + // Round this to the pixel grid too! + vx = Math.floor(vx * tw + 0.5) / tw; + vy = Math.floor(vy * th + 0.5) / th; } + else { + // Non-actors can't move + vx = x; + vy = y; + } + + // For actors (i.e., blocks), perception only applies if there's something + // of potential interest underneath + let perception = this.perception; + if (perception !== 'normal' && tile.type.is_actor && + ! cell.some(t => + t && t.type.layer < layer && + ! (t.type.name === 'floor' && (t.wire_directions | t.wire_tunnel_directions) === 0))) + { + perception = 'normal'; + } + + this.tileset.draw( + tile, tic, perception, + this._make_tileset_blitter(this.ctx, vx - x0, vy - y0)); } } } diff --git a/js/tiletypes.js b/js/tiletypes.js index 27614c4..676da4d 100644 --- a/js/tiletypes.js +++ b/js/tiletypes.js @@ -1,4 +1,4 @@ -import { COLLISION, DIRECTIONS, DIRECTION_ORDER, DRAW_LAYERS, TICS_PER_SECOND } from './defs.js'; +import { COLLISION, DIRECTIONS, DIRECTION_ORDER, LAYERS, TICS_PER_SECOND } from './defs.js'; import { random_choice } from './util.js'; function activate_me(me, level) { @@ -48,7 +48,7 @@ function blocks_leaving_thin_walls(me, actor, direction) { function _define_door(key) { return { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, // Doors can be opened by ice blocks, but not dirt blocks blocks_collision: COLLISION.block_cc1, blocks(me, level, other) { @@ -69,7 +69,7 @@ function _define_door(key) { } function _define_gate(key) { return { - draw_layer: DRAW_LAYERS.item, + layer: LAYERS.item, // Doors can be opened by ice blocks, but not dirt blocks blocks_collision: COLLISION.block_cc1, blocks(me, level, other) { @@ -114,7 +114,7 @@ function player_visual_state(me) { else if (me.exited) { return 'exited'; } - else if (me.cell && (me.previous_cell || me.cell).some(t => t.type.name === 'water')) { + else if (me.cell && (me.previous_cell || me.cell).has('water')) { // CC2 shows a swimming pose while still in water, or moving away from water // FIXME this also shows in some cases when we don't have flippers, e.g. when starting in water return 'swimming'; @@ -180,7 +180,7 @@ function pursue_player(me, level) { const TILE_TYPES = { // Floors and walls floor: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, on_approach(me, level, other) { if (other.type.name === 'blob') { // Blobs spread slime onto floor @@ -192,30 +192,30 @@ const TILE_TYPES = { }, }, floor_letter: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, populate_defaults(me) { me.overlaid_glyph = "?"; }, }, // TODO possibly this should be a single tile floor_custom_green: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, blocks_collision: COLLISION.ghost, }, floor_custom_pink: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, blocks_collision: COLLISION.ghost, }, floor_custom_yellow: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, blocks_collision: COLLISION.ghost, }, floor_custom_blue: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, blocks_collision: COLLISION.ghost, }, wall: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, blocks_collision: COLLISION.all_but_ghost, on_bumped(me, level, other) { if (other.has_item('foil')) { @@ -224,23 +224,23 @@ const TILE_TYPES = { }, }, wall_custom_green: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, blocks_collision: COLLISION.all, }, wall_custom_pink: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, blocks_collision: COLLISION.all, }, wall_custom_yellow: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, blocks_collision: COLLISION.all, }, wall_custom_blue: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, blocks_collision: COLLISION.all, }, wall_invisible: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, blocks_collision: COLLISION.all_but_ghost, on_bumped(me, level, other) { if (other.type.can_reveal_walls) { @@ -249,7 +249,7 @@ const TILE_TYPES = { }, }, wall_appearing: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, blocks_collision: COLLISION.all_but_ghost, on_bumped(me, level, other) { if (other.type.can_reveal_walls) { @@ -258,7 +258,7 @@ const TILE_TYPES = { }, }, popwall: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, blocks_collision: COLLISION.block_cc1 | (COLLISION.monster_solid & ~COLLISION.rover), on_ready(me, level) { if (! level.compat.no_auto_convert_ccl_popwalls && @@ -277,7 +277,7 @@ 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: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, blocks_collision: COLLISION.block_cc1 | COLLISION.monster_solid, on_depart(me, level, other) { level.transmute_tile(me, 'popwall'); @@ -285,7 +285,7 @@ const TILE_TYPES = { }, // FIXME in a cc1 tileset, these tiles are opaque >:S thin_walls: { - draw_layer: DRAW_LAYERS.thin_wall, + layer: LAYERS.thin_wall, blocks(me, level, actor, direction) { return ((me.edges & DIRECTIONS[direction].opposite_bit) !== 0) && actor.type.name !== 'ghost'; }, @@ -297,7 +297,7 @@ const TILE_TYPES = { }, }, fake_wall: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, blocks_collision: COLLISION.all_but_ghost, on_ready(me, level) { if (! level.compat.no_auto_convert_ccl_blue_walls && @@ -316,7 +316,7 @@ const TILE_TYPES = { }, }, fake_floor: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, blocks_collision: COLLISION.block_cc1 | (COLLISION.monster_solid & ~COLLISION.rover), on_bumped(me, level, other) { if (other.type.can_reveal_walls) { @@ -325,11 +325,11 @@ const TILE_TYPES = { }, }, popdown_wall: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, blocks_collision: COLLISION.all_but_ghost, }, popdown_floor: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, blocks_collision: COLLISION.block_cc1 | COLLISION.block_cc2, on_approach(me, level, other) { // FIXME could probably do this with state? or, eh @@ -337,7 +337,7 @@ const TILE_TYPES = { }, }, popdown_floor_visible: { - draw_layer: DRAW_LAYERS.terrain, + layer: 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) @@ -345,19 +345,19 @@ const TILE_TYPES = { }, }, no_player1_sign: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, blocks_collision: COLLISION.playerlike1, }, no_player2_sign: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, blocks_collision: COLLISION.playerlike2, }, steel: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, blocks_collision: COLLISION.all, }, canopy: { - draw_layer: DRAW_LAYERS.canopy, + layer: LAYERS.canopy, blocks_collision: COLLISION.bug | COLLISION.rover, blocks(me, level, other, direction) { // Blobs will specifically not move from one canopy to another @@ -368,10 +368,10 @@ const TILE_TYPES = { // Swivel doors swivel_floor: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, }, swivel_ne: { - draw_layer: DRAW_LAYERS.swivel, + layer: LAYERS.swivel, thin_walls: new Set(['north', 'east']), on_depart(me, level, other) { if (other.direction === 'north') { @@ -388,7 +388,7 @@ const TILE_TYPES = { on_power: activate_me, }, swivel_se: { - draw_layer: DRAW_LAYERS.swivel, + layer: LAYERS.swivel, thin_walls: new Set(['south', 'east']), on_depart(me, level, other) { if (other.direction === 'south') { @@ -405,7 +405,7 @@ const TILE_TYPES = { on_power: activate_me, }, swivel_sw: { - draw_layer: DRAW_LAYERS.swivel, + layer: LAYERS.swivel, thin_walls: new Set(['south', 'west']), on_depart(me, level, other) { if (other.direction === 'south') { @@ -422,7 +422,7 @@ const TILE_TYPES = { on_power: activate_me, }, swivel_nw: { - draw_layer: DRAW_LAYERS.swivel, + layer: LAYERS.swivel, thin_walls: new Set(['north', 'west']), on_depart(me, level, other) { if (other.direction === 'north') { @@ -441,7 +441,7 @@ const TILE_TYPES = { // Railroad railroad: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, track_order: [ ['north', 'east'], ['south', 'east'], @@ -578,7 +578,7 @@ const TILE_TYPES = { // Terrain dirt: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, blocks_collision: COLLISION.block_cc1 | (COLLISION.monster_solid & ~COLLISION.rover), blocks(me, level, other) { return ((other.type.name === 'player2' || other.type.name === 'doppelganger2') && @@ -595,7 +595,7 @@ const TILE_TYPES = { }, }, gravel: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, blocks_collision: COLLISION.monster_solid & ~COLLISION.rover, blocks(me, level, other) { return ((other.type.name === 'player2' || other.type.name === 'doppelganger2') && @@ -603,14 +603,14 @@ const TILE_TYPES = { }, }, sand: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, blocks_collision: COLLISION.block_cc1 | COLLISION.block_cc2, speed_factor: 0.5, }, // Hazards fire: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, blocks_collision: COLLISION.monster_solid & ~COLLISION.fireball, on_arrive(me, level, other) { if (other.type.name === 'ghost') { @@ -636,7 +636,7 @@ const TILE_TYPES = { }, }, water: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, blocks(me, level, other) { // Water blocks ghosts... unless they have flippers if (other.type.name === 'ghost' && ! other.has_item('flippers')) @@ -666,7 +666,7 @@ const TILE_TYPES = { }, }, turtle: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, blocks_collision: COLLISION.ghost | COLLISION.fireball, on_depart(me, level, other) { level.transmute_tile(me, 'water'); @@ -675,12 +675,12 @@ const TILE_TYPES = { }, }, ice: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, slide_mode: 'ice', speed_factor: 2, }, ice_sw: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, thin_walls: new Set(['south', 'west']), slide_mode: 'ice', speed_factor: 2, @@ -695,7 +695,7 @@ const TILE_TYPES = { }, }, ice_nw: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, thin_walls: new Set(['north', 'west']), slide_mode: 'ice', speed_factor: 2, @@ -710,7 +710,7 @@ const TILE_TYPES = { }, }, ice_ne: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, thin_walls: new Set(['north', 'east']), slide_mode: 'ice', speed_factor: 2, @@ -725,7 +725,7 @@ const TILE_TYPES = { }, }, ice_se: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, thin_walls: new Set(['south', 'east']), slide_mode: 'ice', speed_factor: 2, @@ -740,7 +740,7 @@ const TILE_TYPES = { }, }, force_floor_n: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, slide_mode: 'force', speed_factor: 2, on_begin: on_begin_force_floor, @@ -763,7 +763,7 @@ const TILE_TYPES = { on_power: activate_me, }, force_floor_e: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, slide_mode: 'force', speed_factor: 2, on_begin: on_begin_force_floor, @@ -784,7 +784,7 @@ const TILE_TYPES = { on_power: activate_me, }, force_floor_s: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, slide_mode: 'force', speed_factor: 2, on_begin: on_begin_force_floor, @@ -805,7 +805,7 @@ const TILE_TYPES = { on_power: activate_me, }, force_floor_w: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, slide_mode: 'force', speed_factor: 2, on_begin: on_begin_force_floor, @@ -826,7 +826,7 @@ const TILE_TYPES = { on_power: activate_me, }, force_floor_all: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, slide_mode: 'force', speed_factor: 2, on_begin: on_begin_force_floor, @@ -836,7 +836,7 @@ const TILE_TYPES = { }, }, slime: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, on_arrive(me, level, other) { if (other.type.name === 'ghost' || other.type.name === 'blob') { // No effect @@ -857,7 +857,7 @@ const TILE_TYPES = { }, }, bomb: { - draw_layer: DRAW_LAYERS.item, + layer: LAYERS.item, on_begin(me, level) { if (level.compat.no_immediate_detonate_bombs) return; @@ -880,7 +880,7 @@ const TILE_TYPES = { }, }, thief_tools: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, blocks_collision: COLLISION.block_cc1 | COLLISION.monster_solid, on_arrive(me, level, other) { if (level.take_tool_from_actor(other, 'bribe')) { @@ -901,7 +901,7 @@ const TILE_TYPES = { }, }, thief_keys: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, blocks_collision: COLLISION.block_cc1 | COLLISION.monster_solid, on_arrive(me, level, other) { if (level.take_tool_from_actor(other, 'bribe')) { @@ -922,26 +922,22 @@ const TILE_TYPES = { }, }, no_sign: { - draw_layer: DRAW_LAYERS.item_mod, + layer: LAYERS.item_mod, item_modifier: 'ignore', collision_allow: COLLISION.monster_solid, blocks(me, level, other) { - for (let tile of me.cell) { - if (tile.type.is_item && other.has_item(tile.type.name)) { - return true; - } - } - return false; + let item = me.cell.get_item(); + return item && other.has_item(item.type.name); }, }, gift_bow: { - draw_layer: DRAW_LAYERS.item_mod, + layer: LAYERS.item_mod, item_modifier: 'pickup', }, // Mechanisms dirt_block: { - draw_layer: DRAW_LAYERS.actor, + layer: LAYERS.actor, collision_mask: COLLISION.block_cc1, blocks_collision: COLLISION.all, is_actor: true, @@ -951,7 +947,7 @@ const TILE_TYPES = { movement_speed: 4, }, ice_block: { - draw_layer: DRAW_LAYERS.actor, + layer: LAYERS.actor, collision_mask: COLLISION.block_cc2, blocks_collision: COLLISION.all, is_actor: true, @@ -977,7 +973,7 @@ const TILE_TYPES = { }, }, frame_block: { - draw_layer: DRAW_LAYERS.actor, + layer: LAYERS.actor, collision_mask: COLLISION.block_cc2, blocks_collision: COLLISION.all, is_actor: true, @@ -1006,7 +1002,7 @@ const TILE_TYPES = { }, }, green_floor: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, on_gray_button(me, level) { level.transmute_tile(me, 'green_wall'); }, @@ -1015,7 +1011,7 @@ const TILE_TYPES = { }, }, green_wall: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, blocks_collision: COLLISION.all_but_ghost, on_gray_button(me, level) { level.transmute_tile(me, 'green_floor'); @@ -1025,7 +1021,7 @@ const TILE_TYPES = { }, }, green_chip: { - draw_layer: DRAW_LAYERS.item, + layer: LAYERS.item, is_chip: true, is_required_chip: true, blocks_collision: COLLISION.block_cc1 | (COLLISION.monster_solid & ~COLLISION.rover), @@ -1038,7 +1034,7 @@ const TILE_TYPES = { // Not affected by gray buttons }, green_bomb: { - draw_layer: DRAW_LAYERS.item, + layer: LAYERS.item, is_required_chip: true, on_arrive(me, level, other) { level.remove_tile(me); @@ -1053,7 +1049,7 @@ const TILE_TYPES = { // Not affected by gray buttons }, purple_floor: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, on_gray_button(me, level) { level.transmute_tile(me, 'purple_wall'); }, @@ -1065,7 +1061,7 @@ const TILE_TYPES = { }, }, purple_wall: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, blocks_collision: COLLISION.all_but_ghost, on_gray_button(me, level) { level.transmute_tile(me, 'purple_floor'); @@ -1078,7 +1074,7 @@ const TILE_TYPES = { }, }, cloner: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, blocks_collision: COLLISION.real_player | COLLISION.block_cc1 | COLLISION.monster_solid, traps(me, actor) { return ! actor._clone_release; @@ -1123,7 +1119,7 @@ const TILE_TYPES = { }, }, trap: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, add_press_ready(me, level, other) { // Same as below, but without ejection me.presses = (me.presses ?? 0) + 1; @@ -1134,13 +1130,12 @@ const TILE_TYPES = { // TODO weird cc2 case that may or may not be a bug: actors aren't ejected if the trap // opened because of wiring if (me.presses === 1 && ! is_wire) { - // Free everything on us, if we went from 0 to 1 presses (i.e. closed to open) - for (let tile of Array.from(me.cell)) { - if (tile.type.is_actor) { - // Forcibly move anything released from a trap, to keep it in sync with - // whatever pushed the button - level.attempt_out_of_turn_step(tile, tile.direction); - } + // Free any actor on us, if we went from 0 to 1 presses (i.e. closed to open) + let actor = me.cell.get_actor(); + if (actor) { + // Forcibly move anything released from a trap, which keeps it in sync with + // whatever pushed the button + level.attempt_out_of_turn_step(actor, actor.direction); } } }, @@ -1171,7 +1166,7 @@ const TILE_TYPES = { }, }, transmogrifier: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, // C2M technically supports wires in transmogrifiers, but they don't do anything wire_propagation_mode: 'none', _mogrifications: { @@ -1225,7 +1220,7 @@ const TILE_TYPES = { }, }, teleport_blue: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, slide_mode: 'teleport', wire_propagation_mode: 'all', *teleport_dest_order(me, level, other) { @@ -1306,7 +1301,7 @@ const TILE_TYPES = { }, }, teleport_red: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, slide_mode: 'teleport', wire_propagation_mode: 'none', teleport_allow_override: true, @@ -1352,7 +1347,7 @@ const TILE_TYPES = { */ }, teleport_green: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, slide_mode: 'teleport', teleport_dest_order(me, level, other) { let all = Array.from(level.iter_tiles_in_reading_order(me.cell, 'teleport_green')); @@ -1379,7 +1374,7 @@ const TILE_TYPES = { }, }, teleport_yellow: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, slide_mode: 'teleport', teleport_allow_override: true, *teleport_dest_order(me, level, other) { @@ -1394,7 +1389,7 @@ const TILE_TYPES = { // - Multiple such inputs cancel each other out // - Gray button toggles it permanently flame_jet_off: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, activate(me, level) { level.transmute_tile(me, 'flame_jet_on'); // Do NOT immediately nuke anything on us, or it'd be impossible to push a block off an @@ -1411,7 +1406,7 @@ const TILE_TYPES = { on_tic() {}, }, flame_jet_on: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, activate(me, level) { level.transmute_tile(me, 'flame_jet_off'); }, @@ -1439,7 +1434,7 @@ const TILE_TYPES = { }, // Buttons button_blue: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, do_button(level) { // Flip direction of all blue tanks for (let actor of level.actors) { @@ -1460,7 +1455,7 @@ const TILE_TYPES = { }, }, button_yellow: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, on_arrive(me, level, other) { level.sfx.play_once('button-press', me.cell); for (let actor of level.actors) { @@ -1474,24 +1469,25 @@ const TILE_TYPES = { }, }, button_green: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, do_button(level) { // Swap green floors and walls // TODO could probably make this more compact for undo purposes for (let cell of level.linear_cells) { - 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'); - } + let terrain = cell.get_terrain(); + if (terrain.type.name === 'green_floor') { + level.transmute_tile(terrain, 'green_wall'); + } + else if (terrain.type.name === 'green_wall') { + level.transmute_tile(terrain, 'green_floor'); + } + + let item = cell.get_item(); + if (item && item.type.name === 'green_chip') { + level.transmute_tile(item, 'green_bomb'); + } + else if (item && item.type.name === 'green_bomb') { + level.transmute_tile(item, 'green_chip'); } } }, @@ -1504,7 +1500,7 @@ const TILE_TYPES = { }, }, button_brown: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, connects_to: 'trap', connect_order: 'forward', on_ready(me, level) { @@ -1513,17 +1509,15 @@ const TILE_TYPES = { if (! (trap && trap.cell)) return; - for (let tile of me.cell) { - if (tile.type.is_actor) { - trap.type.add_press_ready(trap, level); - } + if (me.cell.get_actor()) { + trap.type.add_press_ready(trap, level); } }, on_arrive(me, level, other) { level.sfx.play_once('button-press', me.cell); let trap = me.connection; - if (trap && trap.cell) { + if (trap && trap.cell && trap.type.name === 'trap') { trap.type.add_press(trap, level); } }, @@ -1531,20 +1525,20 @@ const TILE_TYPES = { level.sfx.play_once('button-release', me.cell); let trap = me.connection; - if (trap && trap.cell) { + if (trap && trap.cell && trap.type.name === 'trap') { trap.type.remove_press(trap, level); } }, }, button_red: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, connects_to: 'cloner', connect_order: 'forward', on_arrive(me, level, other) { level.sfx.play_once('button-press', me.cell); let cloner = me.connection; - if (cloner && cloner.cell) { + if (cloner && cloner.cell && cloner.type.name === 'cloner') { cloner.type.activate(cloner, level); } }, @@ -1553,14 +1547,16 @@ const TILE_TYPES = { }, }, button_orange: { - draw_layer: DRAW_LAYERS.terrain, + layer: 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 // connected flame jet _toggle_flame_jet(me, level, other) { let jet = me.connection; - if (jet && jet.cell) { + if (jet && jet.cell && ( + jet.type.name === 'flame_jet_off' || jet.type.name === 'flame_jet_on')) + { jet.type.activate(jet, level); } }, @@ -1576,12 +1572,13 @@ const TILE_TYPES = { }, }, button_pink: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, is_power_source: true, wire_propagation_mode: 'none', get_emitting_edges(me, level) { // We emit current as long as there's an actor fully on us - if (me.cell.some(tile => tile.type.is_actor && tile.movement_cooldown === 0)) { + let actor = me.cell.get_actor(); + if (actor && actor.movement_cooldown === 0) { return me.wire_directions; } else { @@ -1596,16 +1593,17 @@ const TILE_TYPES = { }, }, button_black: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, is_power_source: true, wire_propagation_mode: 'cross', get_emitting_edges(me, level) { // We emit current as long as there's NOT an actor fully on us - if (! me.cell.some(tile => tile.type.is_actor && tile.movement_cooldown === 0)) { - return me.wire_directions; + let actor = me.cell.get_actor(); + if (actor && actor.movement_cooldown === 0) { + return 0; } else { - return 0; + return me.wire_directions; } }, on_arrive(me, level, other) { @@ -1617,7 +1615,7 @@ const TILE_TYPES = { }, button_gray: { // TODO only partially implemented - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, on_arrive(me, level, other) { level.sfx.play_once('button-press', me.cell); @@ -1629,7 +1627,7 @@ const TILE_TYPES = { continue; for (let tile of cell) { - if (tile.type.on_gray_button) { + if (tile && tile.type.on_gray_button) { tile.type.on_gray_button(tile, level); } } @@ -1656,7 +1654,7 @@ const TILE_TYPES = { // inputs: inc, dec; outputs: overflow, underflow counter: ['out1', 'in0', 'in1', 'out0'], }, - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, is_power_source: true, on_ready(me, level) { me.gate_def = me.type._gate_types[me.gate_type]; @@ -1759,7 +1757,7 @@ const TILE_TYPES = { }, // Light switches, kinda like the pink/black buttons but persistent light_switch_off: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, is_power_source: true, get_emitting_edges(me, level) { // TODO this is inconsistent with the pink/black buttons, but cc2 has a single-frame @@ -1778,7 +1776,7 @@ const TILE_TYPES = { }, }, light_switch_on: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, is_power_source: true, get_emitting_edges(me, level) { // TODO this is inconsistent with the pink/black buttons, but cc2 has a single-frame @@ -1797,7 +1795,7 @@ const TILE_TYPES = { }, // LL tile: circuit block, overrides the wiring on the floor below (if any) circuit_block: { - draw_layer: DRAW_LAYERS.actor, + layer: LAYERS.actor, collision_mask: COLLISION.block_cc2, blocks_collision: COLLISION.all, is_actor: true, @@ -1808,7 +1806,7 @@ const TILE_TYPES = { // Time alteration stopwatch_bonus: { - draw_layer: DRAW_LAYERS.item, + layer: LAYERS.item, blocks_collision: COLLISION.block_cc1 | COLLISION.monster_solid, on_arrive(me, level, other) { if (other.type.is_real_player) { @@ -1818,7 +1816,7 @@ const TILE_TYPES = { }, }, stopwatch_penalty: { - draw_layer: DRAW_LAYERS.item, + layer: LAYERS.item, blocks_collision: COLLISION.block_cc1 | COLLISION.monster_solid, on_arrive(me, level, other) { if (other.type.is_real_player) { @@ -1828,7 +1826,7 @@ const TILE_TYPES = { }, }, stopwatch_toggle: { - draw_layer: DRAW_LAYERS.item, + layer: LAYERS.item, blocks_collision: COLLISION.block_cc1 | COLLISION.monster_solid, on_arrive(me, level, other) { if (other.type.is_real_player) { @@ -1839,7 +1837,7 @@ const TILE_TYPES = { // Critters bug: { - draw_layer: DRAW_LAYERS.actor, + layer: LAYERS.actor, is_actor: true, is_monster: true, collision_mask: COLLISION.bug, @@ -1852,7 +1850,7 @@ const TILE_TYPES = { }, }, paramecium: { - draw_layer: DRAW_LAYERS.actor, + layer: LAYERS.actor, is_actor: true, is_monster: true, collision_mask: COLLISION.monster_generic, @@ -1865,7 +1863,7 @@ const TILE_TYPES = { }, }, ball: { - draw_layer: DRAW_LAYERS.actor, + layer: LAYERS.actor, is_actor: true, is_monster: true, collision_mask: COLLISION.monster_generic, @@ -1878,7 +1876,7 @@ const TILE_TYPES = { }, }, walker: { - draw_layer: DRAW_LAYERS.actor, + layer: LAYERS.actor, is_actor: true, is_monster: true, collision_mask: COLLISION.monster_generic, @@ -1901,7 +1899,7 @@ const TILE_TYPES = { }, }, tank_blue: { - draw_layer: DRAW_LAYERS.actor, + layer: LAYERS.actor, is_actor: true, is_monster: true, collision_mask: COLLISION.monster_generic, @@ -1923,7 +1921,7 @@ const TILE_TYPES = { } }, tank_yellow: { - draw_layer: DRAW_LAYERS.actor, + layer: LAYERS.actor, is_actor: true, is_monster: true, collision_mask: COLLISION.monster_generic, @@ -1948,7 +1946,7 @@ const TILE_TYPES = { } }, blob: { - draw_layer: DRAW_LAYERS.actor, + layer: LAYERS.actor, is_actor: true, is_monster: true, collision_mask: COLLISION.monster_generic, @@ -1961,7 +1959,7 @@ const TILE_TYPES = { }, }, teeth: { - draw_layer: DRAW_LAYERS.actor, + layer: LAYERS.actor, is_actor: true, is_monster: true, collision_mask: COLLISION.monster_generic, @@ -1980,7 +1978,7 @@ const TILE_TYPES = { }, }, teeth_timid: { - draw_layer: DRAW_LAYERS.actor, + layer: LAYERS.actor, is_actor: true, is_monster: true, collision_mask: COLLISION.monster_generic, @@ -1999,7 +1997,7 @@ const TILE_TYPES = { }, }, fireball: { - draw_layer: DRAW_LAYERS.actor, + layer: LAYERS.actor, is_actor: true, is_monster: true, collision_mask: COLLISION.fireball, @@ -2014,7 +2012,7 @@ const TILE_TYPES = { }, }, glider: { - draw_layer: DRAW_LAYERS.actor, + layer: LAYERS.actor, is_actor: true, is_monster: true, collision_mask: COLLISION.monster_generic, @@ -2029,7 +2027,7 @@ const TILE_TYPES = { }, }, ghost: { - draw_layer: DRAW_LAYERS.actor, + layer: LAYERS.actor, is_actor: true, is_monster: true, collision_mask: COLLISION.ghost, @@ -2056,7 +2054,7 @@ const TILE_TYPES = { }, }, floor_mimic: { - draw_layer: DRAW_LAYERS.actor, + layer: LAYERS.actor, is_actor: true, is_monster: true, collision_mask: COLLISION.monster_generic, @@ -2067,7 +2065,7 @@ const TILE_TYPES = { }, rover: { // TODO pushes blocks apparently?? - draw_layer: DRAW_LAYERS.actor, + layer: LAYERS.actor, is_actor: true, is_monster: true, has_inventory: true, @@ -2116,14 +2114,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: DRAW_LAYERS.item, + layer: 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: DRAW_LAYERS.item, + layer: LAYERS.item, is_item: true, is_key: true, on_arrive(me, level, other) { @@ -2138,14 +2136,14 @@ const TILE_TYPES = { }, }, key_yellow: { - draw_layer: DRAW_LAYERS.item, + layer: LAYERS.item, is_item: true, is_key: true, // FIXME ok this is ghastly blocks_collision: COLLISION.block_cc1 | (COLLISION.monster_solid & ~COLLISION.rover), }, key_green: { - draw_layer: DRAW_LAYERS.item, + layer: LAYERS.item, is_item: true, is_key: true, blocks_collision: COLLISION.block_cc1 | (COLLISION.monster_solid & ~COLLISION.rover), @@ -2153,14 +2151,14 @@ const TILE_TYPES = { // Boots // TODO note: ms allows blocks to pass over tools cleats: { - draw_layer: DRAW_LAYERS.item, + layer: LAYERS.item, is_item: true, is_tool: true, blocks_collision: COLLISION.block_cc1 | (COLLISION.monster_solid & ~COLLISION.rover), item_ignores: new Set(['ice', 'ice_nw', 'ice_ne', 'ice_sw', 'ice_se']), }, suction_boots: { - draw_layer: DRAW_LAYERS.item, + layer: LAYERS.item, is_item: true, is_tool: true, blocks_collision: COLLISION.block_cc1 | (COLLISION.monster_solid & ~COLLISION.rover), @@ -2173,7 +2171,7 @@ const TILE_TYPES = { ]), }, fire_boots: { - draw_layer: DRAW_LAYERS.item, + layer: LAYERS.item, is_item: true, is_tool: true, blocks_collision: COLLISION.block_cc1 | (COLLISION.monster_solid & ~COLLISION.rover), @@ -2182,14 +2180,14 @@ const TILE_TYPES = { item_ignores: new Set(['flame_jet_on']), }, flippers: { - draw_layer: DRAW_LAYERS.item, + layer: LAYERS.item, is_item: true, is_tool: true, blocks_collision: COLLISION.block_cc1 | (COLLISION.monster_solid & ~COLLISION.rover), item_ignores: new Set(['water']), }, hiking_boots: { - draw_layer: DRAW_LAYERS.item, + layer: LAYERS.item, is_item: true, is_tool: true, blocks_collision: COLLISION.block_cc1 | (COLLISION.monster_solid & ~COLLISION.rover), @@ -2198,7 +2196,7 @@ const TILE_TYPES = { }, // Other tools dynamite: { - draw_layer: DRAW_LAYERS.item, + layer: LAYERS.item, is_item: true, is_tool: true, blocks_collision: COLLISION.block_cc1 | (COLLISION.monster_solid & ~COLLISION.rover), @@ -2213,7 +2211,7 @@ const TILE_TYPES = { }, }, dynamite_lit: { - draw_layer: DRAW_LAYERS.item, + layer: LAYERS.item, is_actor: true, is_monster: true, collision_mask: COLLISION.dropped_item, @@ -2246,21 +2244,16 @@ const TILE_TYPES = { if (! cell) continue; - let tiles = Array.from(cell); - tiles.sort((a, b) => b.type.draw_layer - a.type.draw_layer); - let actor, terrain, removed_anything; - for (let tile of tiles) { - if (tile.type.name === 'canopy') { - // Canopy protects everything else - break; - } - if (tile.type.is_actor) { - actor = tile; - } + let actor = cell.get_actor(); + let terrain = cell.get_terrain(); + let removed_anything; + for (let layer = LAYERS.MAX - 1; layer >= 0; layer--) { + let tile = cell[layer]; + if (! tile) + continue; - if (tile.type.draw_layer === 0) { + if (tile.type.layer === LAYERS.terrain) { // Terrain gets transmuted afterwards - terrain = tile; } else if (tile.type.is_real_player) { // TODO it would be nice if i didn't have to special-case this every @@ -2272,6 +2265,11 @@ const TILE_TYPES = { level.remove_tile(tile); removed_anything = true; } + + if (tile.type.name === 'canopy') { + // Canopy protects everything else + break; + } } if (actor) { @@ -2295,7 +2293,8 @@ const TILE_TYPES = { } } - if (removed_anything) { + // TODO maybe add a vfx nonblocking explosion + if (removed_anything && ! cell.get_actor()) { level.spawn_animation(cell, 'explosion'); } } @@ -2307,7 +2306,7 @@ const TILE_TYPES = { }, }, bowling_ball: { - draw_layer: DRAW_LAYERS.item, + layer: LAYERS.item, is_item: true, is_tool: true, blocks_collision: COLLISION.block_cc1 | (COLLISION.monster_solid & ~COLLISION.rover), @@ -2316,7 +2315,7 @@ const TILE_TYPES = { }, }, rolling_ball: { - draw_layer: DRAW_LAYERS.actor, + layer: LAYERS.actor, is_actor: true, is_monster: true, has_inventory: true, @@ -2342,25 +2341,17 @@ const TILE_TYPES = { level.sfx.play_once('bomb', me.cell); level.transmute_tile(me, 'explosion'); }, - on_blocked(me, level, direction) { + on_blocked(me, level, direction, obstacle) { // Blow up anything we run into - // FIXME if we hit a wall, we should definitely /not/ blow up an actor... but that's - // tricky because on_blocked doesn't tell us what we hit, and on_bump goes top to bottom - // so it hits actors before walls... - let cell = level.get_neighboring_cell(me.cell, direction); - let other; - if (cell) { - other = cell.get_actor(); - if (other) { - if (other.type.is_real_player) { - level.fail(me.type.name); - } - else { - level.transmute_tile(other, 'explosion'); - } + if (obstacle.type.is_actor) { + if (obstacle.type.is_real_player) { + level.fail(me.type.name); + } + else { + level.transmute_tile(obstacle, 'explosion'); } } - if (me.slide_mode && ! other) { + else if (me.slide_mode) { // Sliding bowling balls don't blow up if they hit a regular wall return; } @@ -2372,56 +2363,56 @@ const TILE_TYPES = { }, xray_eye: { // TODO not implemented - draw_layer: DRAW_LAYERS.item, + layer: LAYERS.item, is_item: true, is_tool: true, blocks_collision: COLLISION.block_cc1 | (COLLISION.monster_solid & ~COLLISION.rover), }, helmet: { // TODO not implemented - draw_layer: DRAW_LAYERS.item, + layer: LAYERS.item, is_item: true, is_tool: true, blocks_collision: COLLISION.block_cc1 | (COLLISION.monster_solid & ~COLLISION.rover), }, railroad_sign: { - draw_layer: DRAW_LAYERS.item, + layer: LAYERS.item, is_item: true, is_tool: true, blocks_collision: COLLISION.block_cc1 | (COLLISION.monster_solid & ~COLLISION.rover), }, foil: { - draw_layer: DRAW_LAYERS.item, + layer: LAYERS.item, is_item: true, is_tool: true, blocks_collision: COLLISION.block_cc1 | (COLLISION.monster_solid & ~COLLISION.rover), }, lightning_bolt: { - draw_layer: DRAW_LAYERS.item, + layer: LAYERS.item, is_item: true, is_tool: true, blocks_collision: COLLISION.block_cc1 | (COLLISION.monster_solid & ~COLLISION.rover), }, speed_boots: { - draw_layer: DRAW_LAYERS.item, + layer: LAYERS.item, is_item: true, is_tool: true, blocks_collision: COLLISION.block_cc1 | (COLLISION.monster_solid & ~COLLISION.rover), }, bribe: { - draw_layer: DRAW_LAYERS.item, + layer: LAYERS.item, is_item: true, is_tool: true, blocks_collision: COLLISION.block_cc1 | (COLLISION.monster_solid & ~COLLISION.rover), }, hook: { - draw_layer: DRAW_LAYERS.item, + layer: LAYERS.item, is_item: true, is_tool: true, blocks_collision: COLLISION.block_cc1 | (COLLISION.monster_solid & ~COLLISION.rover), }, skeleton_key: { - draw_layer: DRAW_LAYERS.item, + layer: LAYERS.item, is_item: true, is_tool: true, blocks_collision: COLLISION.block_cc1 | (COLLISION.monster_solid & ~COLLISION.rover), @@ -2429,7 +2420,7 @@ const TILE_TYPES = { // Progression player: { - draw_layer: DRAW_LAYERS.actor, + layer: LAYERS.actor, is_actor: true, is_player: true, is_real_player: true, @@ -2450,7 +2441,7 @@ const TILE_TYPES = { visual_state: player_visual_state, }, player2: { - draw_layer: DRAW_LAYERS.actor, + layer: LAYERS.actor, is_actor: true, is_player: true, is_real_player: true, @@ -2472,7 +2463,7 @@ const TILE_TYPES = { visual_state: player_visual_state, }, doppelganger1: { - draw_layer: DRAW_LAYERS.actor, + layer: LAYERS.actor, is_actor: true, is_player: true, is_monster: true, @@ -2506,7 +2497,7 @@ const TILE_TYPES = { //visual_state: doppelganger_visual_state, }, doppelganger2: { - draw_layer: DRAW_LAYERS.actor, + layer: LAYERS.actor, is_actor: true, is_player: true, is_monster: true, @@ -2541,7 +2532,7 @@ const TILE_TYPES = { //visual_state: doppelganger_visual_state, }, chip: { - draw_layer: DRAW_LAYERS.item, + layer: LAYERS.item, is_chip: true, is_required_chip: true, blocks_collision: COLLISION.block_cc1 | (COLLISION.monster_solid & ~COLLISION.rover), @@ -2553,7 +2544,7 @@ const TILE_TYPES = { }, }, chip_extra: { - draw_layer: DRAW_LAYERS.item, + layer: LAYERS.item, is_chip: true, blocks_collision: COLLISION.block_cc1 | (COLLISION.monster_solid & ~COLLISION.rover), on_arrive(me, level, other) { @@ -2564,7 +2555,7 @@ const TILE_TYPES = { }, }, score_10: { - draw_layer: DRAW_LAYERS.item, + layer: LAYERS.item, blocks_collision: COLLISION.block_cc1 | COLLISION.monster_solid, on_arrive(me, level, other) { if (other.type.is_real_player) { @@ -2578,7 +2569,7 @@ const TILE_TYPES = { }, }, score_100: { - draw_layer: DRAW_LAYERS.item, + layer: LAYERS.item, blocks_collision: COLLISION.block_cc1 | COLLISION.monster_solid, on_arrive(me, level, other) { if (other.type.is_real_player) { @@ -2591,7 +2582,7 @@ const TILE_TYPES = { }, }, score_1000: { - draw_layer: DRAW_LAYERS.item, + layer: LAYERS.item, blocks_collision: COLLISION.block_cc1 | COLLISION.monster_solid, on_arrive(me, level, other) { if (other.type.is_real_player) { @@ -2604,7 +2595,7 @@ const TILE_TYPES = { }, }, score_2x: { - draw_layer: DRAW_LAYERS.item, + layer: LAYERS.item, blocks_collision: COLLISION.block_cc1 | COLLISION.monster_solid, on_arrive(me, level, other) { if (other.type.is_real_player) { @@ -2618,7 +2609,7 @@ const TILE_TYPES = { }, hint: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, is_hint: true, blocks_collision: COLLISION.block_cc1 | COLLISION.monster_solid, populate_defaults(me) { @@ -2626,7 +2617,7 @@ const TILE_TYPES = { }, }, socket: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, blocks_collision: COLLISION.block_cc1 | (COLLISION.monster_solid & ~COLLISION.rover), blocks(me, level, other) { return ! (other.type.name === 'ghost' || level.chips_remaining <= 0); @@ -2639,7 +2630,7 @@ const TILE_TYPES = { }, }, exit: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, blocks_collision: COLLISION.block_cc1 | COLLISION.monster_solid & ~COLLISION.rover, on_arrive(me, level, other) { if (other.type.is_real_player) { @@ -2656,7 +2647,7 @@ const TILE_TYPES = { // VFX splash: { - draw_layer: DRAW_LAYERS.actor, + layer: LAYERS.actor, is_actor: true, collision_mask: 0, blocks_collision: COLLISION.real_player, @@ -2668,7 +2659,7 @@ const TILE_TYPES = { }, }, explosion: { - draw_layer: DRAW_LAYERS.actor, + layer: LAYERS.actor, is_actor: true, collision_mask: 0, blocks_collision: COLLISION.real_player, @@ -2679,7 +2670,7 @@ const TILE_TYPES = { }, // Used as an easy way to show an invisible wall when bumped wall_invisible_revealed: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.vfx, is_actor: true, collision_mask: 0, blocks_collision: 0, @@ -2688,7 +2679,7 @@ const TILE_TYPES = { }, // Custom VFX (identical function, but different aesthetic) splash_slime: { - draw_layer: DRAW_LAYERS.actor, + layer: LAYERS.actor, is_actor: true, collision_mask: 0, blocks_collision: COLLISION.real_player, @@ -2700,25 +2691,25 @@ const TILE_TYPES = { // New VFX (not in CC2, so they don't block to avoid altering gameplay) // TODO would like these to play faster but the first frame is often skipped due to other bugs player1_exit: { - draw_layer: DRAW_LAYERS.vfx, + layer: LAYERS.vfx, is_actor: true, collision_mask: 0, ttl: 8 * 3, }, player2_exit: { - draw_layer: DRAW_LAYERS.vfx, + layer: LAYERS.vfx, is_actor: true, collision_mask: 0, ttl: 8 * 3, }, teleport_flash: { - draw_layer: DRAW_LAYERS.vfx, + layer: LAYERS.vfx, is_actor: true, collision_mask: 0, ttl: 8 * 3, }, transmogrify_flash: { - draw_layer: DRAW_LAYERS.vfx, + layer: LAYERS.vfx, is_actor: true, collision_mask: 0, ttl: 6 * 3, @@ -2727,23 +2718,23 @@ const TILE_TYPES = { // Invalid tiles that appear in some CCL levels because community level // designers love to make nonsense bogus_player_win: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, blocks_collision: COLLISION.all, }, bogus_player_swimming: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, blocks_collision: COLLISION.all, }, bogus_player_drowned: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, blocks_collision: COLLISION.all, }, bogus_player_burned_fire: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, blocks_collision: COLLISION.all, }, bogus_player_burned: { - draw_layer: DRAW_LAYERS.terrain, + layer: LAYERS.terrain, blocks_collision: COLLISION.all, }, }; @@ -2752,11 +2743,11 @@ const TILE_TYPES = { for (let [name, type] of Object.entries(TILE_TYPES)) { type.name = name; - if (type.draw_layer === undefined || - type.draw_layer !== Math.floor(type.draw_layer) || - type.draw_layer >= DRAW_LAYERS.MAX) + if (type.layer === undefined || + type.layer !== Math.floor(type.layer) || + type.layer >= LAYERS.MAX) { - console.error(`Tile type ${name} has a bad draw layer`); + console.error(`Tile type ${name} has a bad layer`); } if (type.is_actor && type.collision_mask === undefined) {