diff --git a/js/main.js b/js/main.js
index 17c5543..6670d96 100644
--- a/js/main.js
+++ b/js/main.js
@@ -69,11 +69,10 @@ async function fetch(url) {
const PAGE_TITLE = "Lexy's Labyrinth";
class Tile {
- constructor(type, x, y, direction = 'south') {
+ constructor(type, direction = 'south') {
this.type = type;
- this.x = x;
- this.y = y;
this.direction = direction;
+ this.cell = null;
this.slide_mode = null;
this.movement_cooldown = 0;
@@ -83,10 +82,10 @@ class Tile {
}
}
- static from_template(tile_template, x, y) {
+ static from_template(tile_template) {
let type = TILE_TYPES[tile_template.name];
if (! type) console.error(tile_template.name);
- let tile = new this(type, x, y, tile_template.direction);
+ let tile = new this(type, tile_template.direction);
if (type.load) {
type.load(tile, tile_template);
}
@@ -129,15 +128,6 @@ class Tile {
return false;
}
- become(name) {
- this.type = TILE_TYPES[name];
- // TODO adjust anything else?
- }
-
- destroy() {
- this.doomed = true;
- }
-
// Inventory stuff
give_item(name) {
this.inventory[name] = (this.inventory[name] ?? 0) + 1;
@@ -162,13 +152,20 @@ class Tile {
}
class Cell extends Array {
- constructor() {
+ constructor(x, y) {
super();
- this.is_dirty = false;
+ this.x = x;
+ this.y = y;
}
- _add(tile) {
- this.push(tile);
+ _add(tile, index = null) {
+ if (index === null) {
+ this.push(tile);
+ }
+ else {
+ this.splice(index, 0, tile);
+ }
+ tile.cell = this;
}
// DO NOT use me to remove a tile permanently, only to move it!
@@ -179,14 +176,15 @@ class Cell extends Array {
throw new Error("Asked to remove tile that doesn't seem to exist");
this.splice(layer, 1);
+ return layer;
}
each(f) {
- for (let i = this.length - 1; i >= 0; i--) {
- if (f(this[i]) === false)
+ let copy = Array.from(this);
+ for (let i = 0, l = copy.length; i < l; i++) {
+ if (f(copy[i]) === false)
break;
}
- this._gc();
}
_gc() {
@@ -237,13 +235,16 @@ class Level {
// TODO in lynx/steam, this carries over between levels; in tile world, you can set it manually
this.force_floor_direction = 'north';
+ this.undo_stack = [];
+ this.pending_undo = [];
+
let n = 0;
let connectables = [];
for (let y = 0; y < this.height; y++) {
let row = [];
this.cells.push(row);
for (let x = 0; x < this.width; x++) {
- let cell = new Cell;
+ let cell = new Cell(x, y);
row.push(cell);
let stored_cell = this.stored_level.linear_cells[n];
@@ -251,7 +252,7 @@ class Level {
let has_cloner, has_forbidden;
for (let template_tile of stored_cell) {
- let tile = Tile.from_template(template_tile, x, y);
+ let tile = Tile.from_template(template_tile);
if (tile.type.is_hint) {
// Copy over the tile-specific hint, if any
tile.specific_hint = template_tile.specific_hint ?? null;
@@ -278,7 +279,7 @@ class Level {
this.actors.push(tile);
}
}
- cell.push(tile);
+ cell._add(tile);
if (tile.type.connects_to) {
connectables.push(tile);
@@ -289,17 +290,15 @@ class Level {
// Connect buttons and teleporters
let num_cells = this.width * this.height;
- console.log(this.stored_level.custom_trap_wiring);
- console.log(this.stored_level.custom_cloner_wiring);
for (let connectable of connectables) {
- let x = connectable.x;
- let y = connectable.y;
+ let cell = connectable.cell;
+ let x = cell.x;
+ let y = cell.y;
let goal = connectable.type.connects_to;
let found = false;
// Check for custom wiring, for MSCC .DAT levels
let n = x + y * this.width;
- console.log(x, y, n);
let target_cell_n = null;
if (goal === 'trap') {
target_cell_n = this.stored_level.custom_trap_wiring[n] ?? null;
@@ -367,7 +366,7 @@ class Level {
continue;
if (actor.movement_cooldown > 0) {
- actor.movement_cooldown -= 1;
+ this._set_prop(actor, 'movement_cooldown', actor.movement_cooldown - 1);
if (actor.movement_cooldown > 0)
continue;
}
@@ -394,19 +393,19 @@ class Level {
actor.last_move_was_force)
{
direction_preference = [player_direction];
- actor.last_move_was_force = false;
+ this._set_prop(actor, 'last_move_was_force', false);
}
else {
direction_preference = [actor.direction];
if (actor === this.player) {
- actor.last_move_was_force = true;
+ this._set_prop(actor, 'last_move_was_force', true);
}
}
}
else if (actor === this.player) {
if (player_direction) {
direction_preference = [player_direction];
- actor.last_move_was_force = false;
+ this._set_prop(actor, 'last_move_was_force', false);
}
}
else if (actor.type.movement_mode === 'forward') {
@@ -452,8 +451,8 @@ class Level {
}
else if (actor.type.movement_mode === 'pursue') {
// teeth behavior: always move towards the player
- let dx = actor.x - this.player.x;
- let dy = actor.y - this.player.y;
+ let dx = actor.cell.x - this.player.cell.x;
+ let dy = actor.cell.y - this.player.cell.y;
// Chooses the furthest direction, vertical wins ties
if (Math.abs(dx) > Math.abs(dy)) {
// Horizontal
@@ -485,7 +484,7 @@ class Level {
let moved = false;
for (let direction of direction_preference) {
- actor.direction = direction;
+ this.set_actor_direction(actor, direction);
if (this.attempt_step(actor, direction)) {
moved = true;
break;
@@ -499,7 +498,7 @@ class Level {
if (actor.slide_mode !== null) {
speed_multiplier = 2;
}
- actor.movement_cooldown = actor.type.movement_speed / speed_multiplier;
+ this._set_prop(actor, 'movement_cooldown', actor.type.movement_speed / speed_multiplier);
}
// TODO do i need to do this more aggressively?
@@ -508,6 +507,13 @@ class Level {
}
if (this.time_remaining !== null) {
+ let tic_counter = this.tic_counter;
+ let time_remaining = this.time_remaining;
+ this.pending_undo.push(() => {
+ this.tic_counter = tic_counter;
+ this.time_remaining = time_remaining;
+ });
+
this.tic_counter++;
while (this.tic_counter > 20) {
this.tic_counter -= 20;
@@ -517,28 +523,22 @@ class Level {
}
}
}
- }
- fail(message) {
- this.state = 'failure';
- this.fail_message = message;
- }
-
- win() {
- this.state = 'success';
+ // Commit the undo state at the end of each tic
+ this.commit();
}
// Try to move the given actor one tile in the given direction and update
// their cooldown. Return true if successful.
attempt_step(actor, direction) {
let move = DIRECTIONS[direction].movement;
- let goal_x = actor.x + move[0];
- let goal_y = actor.y + move[1];
+ let original_cell = actor.cell;
+ let goal_x = original_cell.x + move[0];
+ let goal_y = original_cell.y + move[1];
let blocked;
if (goal_x >= 0 && goal_x < this.width && goal_y >= 0 && goal_y < this.height) {
// Check for a thin wall in our current cell first
- let original_cell = this.cells[actor.y][actor.x];
original_cell.each(tile => {
if (tile !== actor && tile.type.thin_walls &&
tile.type.thin_walls.has(direction))
@@ -594,19 +594,14 @@ class Level {
// tile interactions. Does NOT check for whether the move is actually
// legal; use attempt_step for that!
move_to(actor, x, y) {
- if (x === actor.x && y === actor.y)
+ let original_cell = actor.cell;
+ if (x === original_cell.x && y === original_cell.y)
return;
let goal_cell = this.cells[y][x];
- let original_cell = this.cells[actor.y][actor.x];
- original_cell._remove(actor);
+ this.remove_tile(actor);
actor.slide_mode = null;
- goal_cell._add(actor);
- actor.x = x;
- actor.y = y;
-
- original_cell.is_dirty = true;
- goal_cell.is_dirty = true;
+ this.add_tile(actor, goal_cell);
// Announce we're leaving, for the handful of tiles that care about it
original_cell.each(tile => {
@@ -622,7 +617,7 @@ class Level {
// Step on all the tiles in the new cell
if (actor === this.player) {
- this.hint_shown = null;
+ this._set_prop(this, 'hint_shown', null);
}
let teleporter;
goal_cell.each(tile => {
@@ -632,12 +627,11 @@ class Level {
return;
if (actor === this.player && tile.type.is_hint) {
- this.hint_shown = tile.specific_hint ?? this.stored_level.hint;
+ this._set_prop(this, 'hint_shown', tile.specific_hint ?? this.stored_level.hint);
}
- if (tile.type.is_item && actor.type.has_inventory) {
- actor.give_item(tile.type.name);
- tile.destroy();
+ if (tile.type.is_item && this.give_actor(actor, tile.type.name)) {
+ this.remove_tile(tile);
}
else if (tile.type.is_teleporter) {
teleporter = tile;
@@ -663,12 +657,10 @@ class Level {
// Physically move the actor to the new teleporter
// XXX is this right, compare with tile world? i overhear it's actually implemented as a slide?
// XXX will probably play badly with undo lol
- let tele_cell = this.cells[goal.y][goal.x];
+ let tele_cell = goal.cell;
current_cell._remove(actor);
tele_cell._add(actor);
current_cell = tele_cell;
- actor.x = goal.x;
- actor.y = goal.y;
if (this.attempt_step(actor, actor.direction))
// Success, teleportation complete
break;
@@ -683,17 +675,56 @@ class Level {
}
}
+ // -------------------------------------------------------------------------
+ // Undo handling
+
+ commit() {
+ this.undo_stack.push(this.pending_undo);
+ this.pending_undo = [];
+ }
+
+ undo() {
+ let entry = this.undo_stack.pop();
+ // Undo in reverse order! There's no redo, so it's okay to destroy this
+ entry.reverse();
+ for (let undo of entry) {
+ undo();
+ }
+ }
+
// -------------------------------------------------------------------------
// Level alteration methods. EVERYTHING that changes the state of a level,
// including the state of a single tile, should do it through one of these
// for undo/rewind purposes
+ _set_prop(obj, key, val) {
+ let old_val = obj[key];
+ this.pending_undo.push(() => obj[key] = old_val);
+ obj[key] = val;
+ }
+
collect_chip() {
- if (this.chips_remaining > 0) {
+ let current = this.chips_remaining;
+ if (current > 0) {
+ this.pending_undo.push(() => this.chips_remaining = current);
this.chips_remaining--;
}
}
+ fail(message) {
+ this.pending_undo.push(() => {
+ this.state = 'playing';
+ this.fail_message = null;
+ });
+ this.state = 'failure';
+ this.fail_message = message;
+ }
+
+ win() {
+ this.pending_undo.push(() => this.state = 'playing');
+ this.state = 'success';
+ }
+
// Get the next direction a random force floor will use. They share global
// state and cycle clockwise.
get_force_floor_direction() {
@@ -702,11 +733,55 @@ class Level {
return d;
}
- // TODO make a set of primitives for actually altering the level that also
- // record how to undo themselves
+ // Tile stuff in particular
+
+ remove_tile(tile) {
+ let cell = tile.cell;
+ let layer = cell._remove(tile);
+ this.pending_undo.push(() => cell._add(tile, layer));
+ }
+
+ add_tile(tile, cell, layer = null) {
+ cell._add(tile, layer);
+ this.pending_undo.push(() => cell._remove(tile));
+ }
+
+ transmute_tile(tile, name) {
+ let current = tile.type.name;
+ this.pending_undo.push(() => tile.type = TILE_TYPES[current]);
+ tile.type = TILE_TYPES[name];
+ // TODO adjust anything else?
+ }
+
+ give_actor(actor, name) {
+ if (! actor.type.has_inventory)
+ return false;
+
+ let current = actor.inventory[name];
+ this.pending_undo.push(() => actor.inventory[name] = current);
+ actor.inventory[name] = (current ?? 0) + 1;
+ return true;
+ }
+
+ // Mark an actor as sliding
make_slide(actor, mode) {
actor.slide_mode = mode;
}
+
+ // Change an actor's direction
+ set_actor_direction(actor, direction) {
+ let current = actor.direction;
+ this.pending_undo.push(() => actor.direction = current);
+ actor.direction = direction;
+ }
+
+ set_actor_stuck(actor, is_stuck) {
+ let current = actor.stuck;
+ if (current === is_stuck)
+ return;
+ this.pending_undo.push(() => actor.stuck = current);
+ actor.stuck = is_stuck;
+ }
}
@@ -915,8 +990,8 @@ const GAME_UI_HTML = `
-
-
+
+
Solution demo available
@@ -966,6 +1041,7 @@ class Game {
// TODO obey level options; allow overriding
this.viewport_size_x = 9;
this.viewport_size_y = 9;
+ this.scale = 1;
document.body.innerHTML = GAME_UI_HTML;
this.container = document.body.querySelector('main');
@@ -1026,6 +1102,23 @@ class Game {
}).open();
ev.target.blur();
});
+ this.undo_button = this.container.querySelector('.controls .control-undo');
+ this.undo_button.addEventListener('click', ev => {
+ let player_cell = this.level.player.cell;
+ while (player_cell === this.level.player.cell && this.level.undo_stack.length > 0) {
+ this.level.undo();
+ }
+ if (this.level.undo_stack.length === 0) {
+ this.set_state('waiting');
+ }
+ else {
+ // Be sure to undo any success or failure
+ this.set_state('playing');
+ }
+ this.update_ui();
+ this.redraw();
+ ev.target.blur();
+ });
// Demo playback
this.container.querySelector('.demo .demo-step-1').addEventListener('click', ev => {
this.advance_by(1);
@@ -1050,8 +1143,8 @@ class Game {
return;
let rect = this.level_canvas.getBoundingClientRect();
- let x = Math.floor((ev.clientX - rect.x) / 2 / this.tileset.size_x + this.viewport_x);
- let y = Math.floor((ev.clientY - rect.y) / 2 / this.tileset.size_y + this.viewport_y);
+ let x = Math.floor((ev.clientX - rect.x) / this.scale / this.tileset.size_x + this.viewport_x);
+ let y = Math.floor((ev.clientY - rect.y) / this.scale / this.tileset.size_y + this.viewport_y);
this.level.move_to(this.level.player, x, y);
});
@@ -1261,7 +1354,7 @@ class Game {
if (! this._inventory_tiles[name]) {
// TODO reuse the canvas
let canvas = mk('canvas', {width: this.tileset.size_x, height: this.tileset.size_y});
- this.tileset.draw({type: TILE_TYPES[name]}, canvas.getContext('2d'), 0, 0);
+ this.tileset.draw({type: TILE_TYPES[name]}, null, canvas.getContext('2d'), 0, 0);
this._inventory_tiles[name] = canvas.toDataURL();
}
return this._inventory_tiles[name];
@@ -1348,29 +1441,34 @@ class Game {
}
redraw() {
+ // TODO split this out to a renderer, call it every frame, have the level flag itself as dirty
let ctx = this.level_canvas.getContext('2d');
ctx.clearRect(0, 0, this.level_canvas.width, this.level_canvas.height);
let xmargin = (this.viewport_size_x - 1) / 2;
let ymargin = (this.viewport_size_y - 1) / 2;
- let x0 = this.level.player.x - xmargin;
- let y0 = this.level.player.y - ymargin;
+ let player_cell = this.level.player.cell;
+ let x0 = player_cell.x - xmargin;
+ let y0 = player_cell.y - ymargin;
x0 = Math.max(0, Math.min(this.level.width - this.viewport_size_x, x0));
y0 = Math.max(0, Math.min(this.level.height - this.viewport_size_y, y0));
this.viewport_x = x0;
this.viewport_y = y0;
- for (let dx = 0; dx < this.viewport_size_x; dx++) {
- for (let dy = 0; dy < this.viewport_size_y; dy++) {
- let cell = this.level.cells[dy + y0][dx + x0];
- /*
- if (! cell.is_dirty)
- continue;
- */
- cell.is_dirty = false;
-
- for (let tile of cell) {
- if (! tile.doomed) {
- this.tileset.draw(tile, ctx, dx, dy);
+ // Draw in layers, so animated objects aren't overdrawn by neighboring terrain
+ let any_drawn = true;
+ let i = -1;
+ while (any_drawn) {
+ i++;
+ any_drawn = false;
+ for (let dx = 0; dx < this.viewport_size_x; dx++) {
+ for (let dy = 0; dy < this.viewport_size_y; dy++) {
+ let cell = this.level.cells[dy + y0][dx + x0];
+ let tile = cell[i];
+ if (tile) {
+ any_drawn = true;
+ if (! tile.doomed) {
+ this.tileset.draw(tile, this.level, ctx, dx, dy);
+ }
}
}
}
@@ -1399,7 +1497,9 @@ class Game {
if (scale <= 0) {
scale = 1;
}
- // FIXME this doesn't take into account the inventory, which is also affected by scale
+
+ // FIXME the above logic doesn't take into account the inventory, which is also affected by scale
+ this.scale = scale;
this.container.style.setProperty('--scale', scale);
}
}
diff --git a/js/tileset.js b/js/tileset.js
index d7b21e7..5725bff 100644
--- a/js/tileset.js
+++ b/js/tileset.js
@@ -1,3 +1,5 @@
+import { DIRECTIONS } from './defs.js';
+
// TODO really need to specify this format more concretely, whoof
// XXX special kinds of drawing i know this has for a fact:
// - letter tiles draw from a block of half-tiles onto the center of the base
@@ -129,8 +131,10 @@ export const CC2_TILESET_LAYOUT = {
base: 'purple_floor',
overlay: [8, 9],
},
- // TODO state (10 is closed)
- trap: [9, 9],
+ trap: {
+ closed: [9, 9],
+ open: [10, 9],
+ },
button_gray: [11, 9],
fireball: [[12, 9], [13, 9], [14, 9], [15, 9]],
@@ -427,27 +431,35 @@ export class Tileset {
dx * this.size_x, dy * this.size_y, w, h);
}
- draw(tile, ctx, x, y) {
- this.draw_type(tile.type.name, tile, ctx, x, y);
+ draw(tile, level, ctx, x, y) {
+ this.draw_type(tile.type.name, tile, level, ctx, x, y);
}
// Draws a tile type, given by name. Passing in a tile is optional, but
// without it you'll get defaults.
- draw_type(name, tile, ctx, x, y) {
+ draw_type(name, tile, level, ctx, x, y) {
let drawspec = this.layout[name];
if (! drawspec) {
console.error(`Don't know how to draw tile type ${type.name}!`);
return;
}
+ /*
+ if (tile && tile.movement_cooldown) {
+ let offset = DIRECTIONS[tile.direction].movement;
+ x -= tile.movement_cooldown / tile.type.movement_speed * offset[0];
+ y -= tile.movement_cooldown / tile.type.movement_speed * offset[1];
+ }
+ */
+
if (drawspec.overlay) {
// Goofy overlay thing used for green/purple toggle tiles and
// southeast thin walls. Draw the base (a type name), then draw
// the overlay (either a type name or a regular draw spec).
// TODO chance of infinite recursion here
- this.draw_type(drawspec.base, tile, ctx, x, y);
+ this.draw_type(drawspec.base, tile, level, ctx, x, y);
if (typeof drawspec.overlay === 'string') {
- this.draw_type(drawspec.overlay, tile, ctx, x, y);
+ this.draw_type(drawspec.overlay, tile, level, ctx, x, y);
return;
}
else {
@@ -464,11 +476,26 @@ export class Tileset {
// Unwrap animations etc.
if (!(coords instanceof Array)) {
- // Must be an object of directions
- coords = coords[(tile && tile.direction) ?? 'south'];
+ // Must be an object of either tile-specific state, or directions
+ if (name === 'trap') {
+ if (tile && tile.open) {
+ coords = coords.open;
+ }
+ else {
+ coords = coords.closed;
+ }
+ }
+ else {
+ coords = coords[(tile && tile.direction) ?? 'south'];
+ }
}
if (coords[0] instanceof Array) {
- coords = coords[0];
+ if (level) {
+ coords = coords[Math.floor(level.tic_counter % 5 / 5 * coords.length)];
+ }
+ else {
+ coords = coords[0];
+ }
}
if (drawspec.mask) {
diff --git a/js/tiletypes.js b/js/tiletypes.js
index 29ab723..a6fd202 100644
--- a/js/tiletypes.js
+++ b/js/tiletypes.js
@@ -19,12 +19,14 @@ const TILE_TYPES = {
wall_appearing: {
blocks: true,
on_bump(me, level, other) {
- me.become('wall');
+ level.transmute_tile(me, 'wall');
},
},
popwall: {
+ blocks_monsters: true,
+ blocks_blocks: true,
on_depart(me, level, other) {
- me.become('wall');
+ level.transmute_tile(me, 'wall');
},
},
thinwall_n: {
@@ -45,13 +47,13 @@ const TILE_TYPES = {
fake_wall: {
blocks: true,
on_bump(me, level, other) {
- me.become('wall');
+ level.transmute_tile(me, 'wall');
},
},
fake_floor: {
blocks: true,
on_bump(me, level, other) {
- me.become('floor');
+ level.transmute_tile(me, 'floor');
},
},
@@ -110,7 +112,7 @@ const TILE_TYPES = {
blocks_blocks: true,
// TODO block melinda only without the hiking boots; can't use ignore because then she wouldn't step on it :S also ignore doesn't apply to blocks anyway.
on_arrive(me, level, other) {
- me.become('floor');
+ level.transmute_tile(me, 'floor');
},
},
gravel: {
@@ -122,10 +124,10 @@ const TILE_TYPES = {
on_arrive(me, level, other) {
if (other.type.is_player) {
level.fail("Oops! You can't walk on fire without fire boots!");
- other.become('player_burned');
+ level.transmute_tile(other, 'player_burned');
}
else {
- other.destroy();
+ level.remove_tile(other);
}
},
},
@@ -133,15 +135,15 @@ const TILE_TYPES = {
on_arrive(me, level, other) {
// TODO cc1 allows items under water, i think; water was on the upper layer
if (other.type.name == 'dirt_block' || other.type.name == 'clone_block') {
- other.destroy();
- me.become('dirt');
+ level.remove_tile(other);
+ level.transmute_tile(me, 'dirt');
}
else if (other.type.is_player) {
- level.fail("Oops! You can't swim without flippers!");
- other.become('player_drowned');
+ level.fail("swimming with the fishes");
+ level.transmute_tile(other, 'player_drowned');
}
else {
- other.destroy();
+ level.remove_tile(other);
}
},
},
@@ -234,8 +236,11 @@ const TILE_TYPES = {
bomb: {
// TODO explode
on_arrive(me, level, other) {
- me.destroy();
- other.destroy();
+ level.remove_tile(me);
+ level.remove_tile(other);
+ if (other.type.is_player) {
+ level.fail("watch where you step");
+ }
},
},
thief_tools: {
@@ -267,6 +272,7 @@ const TILE_TYPES = {
dirt_block: {
blocks: true,
is_object: true,
+ is_actor: true,
is_block: true,
ignores: new Set(['fire']),
},
@@ -274,6 +280,7 @@ const TILE_TYPES = {
// TODO is this in any way distinct from dirt block
blocks: true,
is_object: true,
+ is_actor: true,
is_block: true,
ignores: new Set(['fire']),
},
@@ -284,10 +291,9 @@ const TILE_TYPES = {
cloner: {
blocks: true,
activate(me, level) {
- let cell = level.cells[me.y][me.x];
- // Clone so we don't end up repeatedly cloning the same object
- let current_tiles = Array.from(cell);
- for (let tile of current_tiles) {
+ let cell = me.cell;
+ // Copy, so we don't end up repeatedly cloning the same object
+ for (let tile of Array.from(cell)) {
if (tile !== me && tile.type.is_actor) {
// Copy this stuff in case the movement changes it
let type = tile.type;
@@ -295,16 +301,14 @@ const TILE_TYPES = {
// Unstick and try to move the actor; if it's blocked,
// abort the clone
- tile.stuck = false;
+ level.set_actor_stuck(tile, false);
if (level.attempt_step(tile, direction)) {
level.actors.push(tile);
- // FIXME rearrange to make this possible lol
- // FIXME go through level for this, and everything else of course
// FIXME add this underneath, just above the cloner
- cell._add(new tile.constructor(type, me.x, me.y, direction));
+ level.add_tile(new tile.constructor(type, direction), cell);
}
else {
- tile.stuck = true;
+ level.set_actor_stuck(tile, true);
}
}
}
@@ -313,7 +317,7 @@ const TILE_TYPES = {
trap: {
on_arrive(me, level, other) {
if (! me.open) {
- other.stuck = true;
+ level.set_actor_stuck(other, true);
}
},
},
@@ -331,7 +335,7 @@ const TILE_TYPES = {
for (let actor of level.actors) {
// TODO generify somehow??
if (actor.type.name === 'tank_blue') {
- actor.direction = DIRECTIONS[actor.direction].opposite;
+ level.set_actor_direction(actor, DIRECTIONS[actor.direction].opposite);
}
}
},
@@ -339,20 +343,21 @@ const TILE_TYPES = {
button_green: {
on_arrive(me, level, other) {
// Swap green floors and walls
+ // TODO could probably make this more compact for undo purposes
for (let row of level.cells) {
for (let cell of row) {
for (let tile of cell) {
if (tile.type.name === 'green_floor') {
- tile.become('green_wall');
+ level.transmute_tile(tile, 'green_wall');
}
else if (tile.type.name === 'green_wall') {
- tile.become('green_floor');
+ level.transmute_tile(tile, 'green_floor');
}
else if (tile.type.name === 'green_chip') {
- tile.become('green_bomb');
+ level.transmute_tile(tile, 'green_bomb');
}
else if (tile.type.name === 'green_bomb') {
- tile.become('green_chip');
+ level.transmute_tile(tile, 'green_chip');
}
}
}
@@ -366,9 +371,9 @@ const TILE_TYPES = {
if (me.connection && ! me.connection.doomed) {
let trap = me.connection;
trap.open = true;
- for (let tile of level.cells[trap.y][trap.x]) {
+ for (let tile of trap.cell) {
if (tile.stuck) {
- tile.stuck = false;
+ level.set_actor_stuck(tile, false);
}
}
}
@@ -377,9 +382,9 @@ const TILE_TYPES = {
if (me.connection && ! me.connection.doomed) {
let trap = me.connection;
trap.open = false;
- for (let tile of level.cells[trap.y][trap.x]) {
+ for (let tile of trap.cell) {
if (tile.is_actor) {
- tile.stuck = false;
+ level.set_actor_stuck(tile, true);
}
}
}
@@ -553,7 +558,7 @@ const TILE_TYPES = {
on_arrive(me, level, other) {
if (other.type.is_player) {
level.collect_chip();
- me.destroy();
+ level.remove_tile(me);
}
},
},