Shrink size of undo buffer by 40%
Using simple maps of changed properties, rather than a big pile of closures, takes up significantly less space.
This commit is contained in:
parent
84840d2b02
commit
350ac08d4d
172
js/game.js
172
js/game.js
@ -1,4 +1,4 @@
|
|||||||
import { DIRECTIONS } from './defs.js';
|
import { DIRECTIONS, TICS_PER_SECOND } from './defs.js';
|
||||||
import TILE_TYPES from './tiletypes.js';
|
import TILE_TYPES from './tiletypes.js';
|
||||||
|
|
||||||
export class Tile {
|
export class Tile {
|
||||||
@ -165,6 +165,11 @@ Cell.prototype.powered_edges = 0;
|
|||||||
|
|
||||||
class GameEnded extends Error {}
|
class GameEnded extends Error {}
|
||||||
|
|
||||||
|
// The undo stack is implemented with a ring buffer, and this is its size. One entry per tic.
|
||||||
|
// Based on Chrome measurements made against the pathological level CCLP4 #40 (Periodic Lasers) and
|
||||||
|
// sitting completely idle, undo consumes about 2 MB every five seconds.
|
||||||
|
// TODO actually make it a ring buffer
|
||||||
|
const UNDO_STACK_SIZE = TICS_PER_SECOND * 10;
|
||||||
export class Level {
|
export class Level {
|
||||||
constructor(stored_level, compat = {}) {
|
constructor(stored_level, compat = {}) {
|
||||||
this.stored_level = stored_level;
|
this.stored_level = stored_level;
|
||||||
@ -225,7 +230,7 @@ export class Level {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.undo_stack = [];
|
this.undo_stack = [];
|
||||||
this.pending_undo = [];
|
this.pending_undo = this.create_undo_entry();
|
||||||
|
|
||||||
let n = 0;
|
let n = 0;
|
||||||
let connectables = [];
|
let connectables = [];
|
||||||
@ -376,15 +381,6 @@ export class Level {
|
|||||||
|
|
||||||
// Lynx PRNG, used unchanged in CC2
|
// Lynx PRNG, used unchanged in CC2
|
||||||
prng() {
|
prng() {
|
||||||
// TODO what if we just saved this stuff, as well as the RFF direction, at the beginning of
|
|
||||||
// each tic?
|
|
||||||
let rng1 = this._rng1;
|
|
||||||
let rng2 = this._rng2;
|
|
||||||
this.pending_undo.push(() => {
|
|
||||||
this._rng1 = rng1;
|
|
||||||
this._rng2 = rng2;
|
|
||||||
});
|
|
||||||
|
|
||||||
let n = (this._rng1 >> 2) - this._rng1;
|
let n = (this._rng1 >> 2) - this._rng1;
|
||||||
if (!(this._rng1 & 0x02)) --n;
|
if (!(this._rng1 & 0x02)) --n;
|
||||||
this._rng1 = (this._rng1 >> 1) | (this._rng2 & 0x80);
|
this._rng1 = (this._rng1 >> 1) | (this._rng2 & 0x80);
|
||||||
@ -396,7 +392,6 @@ export class Level {
|
|||||||
// Weird thing done by CC2 to make blobs... more... random
|
// Weird thing done by CC2 to make blobs... more... random
|
||||||
get_blob_modifier() {
|
get_blob_modifier() {
|
||||||
let mod = this._blob_modifier;
|
let mod = this._blob_modifier;
|
||||||
this.pending_undo.push(() => this._blob_modifier = mod);
|
|
||||||
|
|
||||||
if (this.stored_level.blob_behavior === 1) {
|
if (this.stored_level.blob_behavior === 1) {
|
||||||
// "4 patterns" just increments by 1 every time (but /after/ returning)
|
// "4 patterns" just increments by 1 every time (but /after/ returning)
|
||||||
@ -455,9 +450,19 @@ export class Level {
|
|||||||
}
|
}
|
||||||
|
|
||||||
advance_tic_finish_movement(p1_primary_direction, p1_secondary_direction) {
|
advance_tic_finish_movement(p1_primary_direction, p1_secondary_direction) {
|
||||||
|
// Store some current level state in the undo entry. (These will often not be modified, but
|
||||||
|
// they only take a few bytes each so that's fine.)
|
||||||
|
for (let key of [
|
||||||
|
'_rng1', '_rng2', '_blob_modifier', 'force_floor_direction',
|
||||||
|
'tic_counter', 'time_remaining', 'timer_paused',
|
||||||
|
'chips_remaining', 'bonus_points', 'hint_shown', 'state'
|
||||||
|
]) {
|
||||||
|
this.pending_undo.level_props[key] = this[key];
|
||||||
|
}
|
||||||
|
|
||||||
// Player's secondary direction is set immediately; it applies on arrival to cells even if
|
// Player's secondary direction is set immediately; it applies on arrival to cells even if
|
||||||
// it wasn't held the last time the player started moving
|
// it wasn't held the last time the player started moving
|
||||||
this._set_prop(this.player, 'secondary_direction', p1_secondary_direction);
|
this._set_tile_prop(this.player, 'secondary_direction', p1_secondary_direction);
|
||||||
|
|
||||||
// Used to check for a monster chomping the player's tail
|
// Used to check for a monster chomping the player's tail
|
||||||
this.player_leaving_cell = this.player.cell;
|
this.player_leaving_cell = this.player.cell;
|
||||||
@ -491,21 +496,21 @@ export class Level {
|
|||||||
// Decrement the cooldown here, but don't check it quite yet,
|
// Decrement the cooldown here, but don't check it quite yet,
|
||||||
// because stepping on cells in the next block might reset it
|
// because stepping on cells in the next block might reset it
|
||||||
if (actor.movement_cooldown > 0) {
|
if (actor.movement_cooldown > 0) {
|
||||||
this._set_prop(actor, 'movement_cooldown', actor.movement_cooldown - 1);
|
this._set_tile_prop(actor, 'movement_cooldown', actor.movement_cooldown - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (actor.animation_speed) {
|
if (actor.animation_speed) {
|
||||||
// Deal with movement animation
|
// Deal with movement animation
|
||||||
this._set_prop(actor, 'animation_progress', actor.animation_progress + 1);
|
this._set_tile_prop(actor, 'animation_progress', actor.animation_progress + 1);
|
||||||
if (actor.animation_progress >= actor.animation_speed) {
|
if (actor.animation_progress >= actor.animation_speed) {
|
||||||
if (actor.type.ttl) {
|
if (actor.type.ttl) {
|
||||||
// This is purely an animation so it disappears once it's played
|
// This is purely an animation so it disappears once it's played
|
||||||
this.remove_tile(actor);
|
this.remove_tile(actor);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
this._set_prop(actor, 'previous_cell', null);
|
this._set_tile_prop(actor, 'previous_cell', null);
|
||||||
this._set_prop(actor, 'animation_progress', null);
|
this._set_tile_prop(actor, 'animation_progress', null);
|
||||||
this._set_prop(actor, 'animation_speed', null);
|
this._set_tile_prop(actor, 'animation_speed', null);
|
||||||
if (! this.compat.tiles_react_instantly) {
|
if (! this.compat.tiles_react_instantly) {
|
||||||
// We need to track the actor AND the cell explicitly, because it's possible
|
// We need to track the actor AND the cell explicitly, because it's possible
|
||||||
// that one actor's step will cause another actor to start another move, and
|
// that one actor's step will cause another actor to start another move, and
|
||||||
@ -552,13 +557,13 @@ export class Level {
|
|||||||
if (this.compat.sliding_tanks_ignore_button &&
|
if (this.compat.sliding_tanks_ignore_button &&
|
||||||
actor.slide_mode && actor.pending_reverse)
|
actor.slide_mode && actor.pending_reverse)
|
||||||
{
|
{
|
||||||
this._set_prop(actor, 'pending_reverse', false);
|
this._set_tile_prop(actor, 'pending_reverse', false);
|
||||||
}
|
}
|
||||||
// Blocks that were pushed while sliding will move in the push direction as soon as they
|
// Blocks that were pushed while sliding will move in the push direction as soon as they
|
||||||
// stop sliding, regardless of what they landed on
|
// stop sliding, regardless of what they landed on
|
||||||
if (actor.pending_push) {
|
if (actor.pending_push) {
|
||||||
actor.decision = actor.pending_push;
|
actor.decision = actor.pending_push;
|
||||||
this._set_prop(actor, 'pending_push', null);
|
this._set_tile_prop(actor, 'pending_push', null);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (actor.slide_mode === 'ice') {
|
if (actor.slide_mode === 'ice') {
|
||||||
@ -578,12 +583,12 @@ export class Level {
|
|||||||
actor.last_move_was_force)
|
actor.last_move_was_force)
|
||||||
{
|
{
|
||||||
actor.decision = p1_primary_direction;
|
actor.decision = p1_primary_direction;
|
||||||
this._set_prop(actor, 'last_move_was_force', false);
|
this._set_tile_prop(actor, 'last_move_was_force', false);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
actor.decision = actor.direction;
|
actor.decision = actor.direction;
|
||||||
if (actor === this.player) {
|
if (actor === this.player) {
|
||||||
this._set_prop(actor, 'last_move_was_force', true);
|
this._set_tile_prop(actor, 'last_move_was_force', true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
@ -591,7 +596,7 @@ export class Level {
|
|||||||
else if (actor === this.player) {
|
else if (actor === this.player) {
|
||||||
if (p1_primary_direction) {
|
if (p1_primary_direction) {
|
||||||
actor.decision = p1_primary_direction;
|
actor.decision = p1_primary_direction;
|
||||||
this._set_prop(actor, 'last_move_was_force', false);
|
this._set_tile_prop(actor, 'last_move_was_force', false);
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -600,7 +605,7 @@ export class Level {
|
|||||||
let direction = actor.direction;
|
let direction = actor.direction;
|
||||||
if (actor.pending_reverse) {
|
if (actor.pending_reverse) {
|
||||||
direction = DIRECTIONS[actor.direction].opposite;
|
direction = DIRECTIONS[actor.direction].opposite;
|
||||||
this._set_prop(actor, 'pending_reverse', false);
|
this._set_tile_prop(actor, 'pending_reverse', false);
|
||||||
}
|
}
|
||||||
// Tanks are controlled explicitly so they don't check if they're blocked
|
// Tanks are controlled explicitly so they don't check if they're blocked
|
||||||
// TODO tanks in traps turn around, but tanks on cloners do not, and i use the same
|
// TODO tanks in traps turn around, but tanks on cloners do not, and i use the same
|
||||||
@ -791,12 +796,6 @@ export class Level {
|
|||||||
let tic_counter = this.tic_counter;
|
let tic_counter = this.tic_counter;
|
||||||
this.tic_counter += 1;
|
this.tic_counter += 1;
|
||||||
if (this.time_remaining !== null && ! this.timer_paused) {
|
if (this.time_remaining !== null && ! this.timer_paused) {
|
||||||
let time_remaining = this.time_remaining;
|
|
||||||
this.pending_undo.push(() => {
|
|
||||||
this.tic_counter = tic_counter;
|
|
||||||
this.time_remaining = time_remaining;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.time_remaining -= 1;
|
this.time_remaining -= 1;
|
||||||
if (this.time_remaining <= 0) {
|
if (this.time_remaining <= 0) {
|
||||||
this.fail('time');
|
this.fail('time');
|
||||||
@ -805,11 +804,6 @@ export class Level {
|
|||||||
this.sfx.play_once('tick');
|
this.sfx.play_once('tick');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
this.pending_undo.push(() => {
|
|
||||||
this.tic_counter = tic_counter;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to move the given actor one tile in the given direction and update
|
// Try to move the given actor one tile in the given direction and update
|
||||||
@ -891,7 +885,7 @@ export class Level {
|
|||||||
{
|
{
|
||||||
// If the push failed and the obstacle is in the middle of a slide,
|
// If the push failed and the obstacle is in the middle of a slide,
|
||||||
// remember this as the next move it'll make
|
// remember this as the next move it'll make
|
||||||
this._set_prop(tile, 'pending_push', direction);
|
this._set_tile_prop(tile, 'pending_push', direction);
|
||||||
}
|
}
|
||||||
if (actor === this.player) {
|
if (actor === this.player) {
|
||||||
actor.is_pushing = true;
|
actor.is_pushing = true;
|
||||||
@ -937,7 +931,7 @@ export class Level {
|
|||||||
this.move_to(actor, goal_cell, speed);
|
this.move_to(actor, goal_cell, speed);
|
||||||
|
|
||||||
// Set movement cooldown since we just moved
|
// Set movement cooldown since we just moved
|
||||||
this._set_prop(actor, 'movement_cooldown', speed);
|
this._set_tile_prop(actor, 'movement_cooldown', speed);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -948,9 +942,9 @@ export class Level {
|
|||||||
if (actor.cell === goal_cell)
|
if (actor.cell === goal_cell)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this._set_prop(actor, 'previous_cell', actor.cell);
|
this._set_tile_prop(actor, 'previous_cell', actor.cell);
|
||||||
this._set_prop(actor, 'animation_speed', speed);
|
this._set_tile_prop(actor, 'animation_speed', speed);
|
||||||
this._set_prop(actor, 'animation_progress', 0);
|
this._set_tile_prop(actor, 'animation_progress', 0);
|
||||||
|
|
||||||
let original_cell = actor.cell;
|
let original_cell = actor.cell;
|
||||||
this.remove_tile(actor);
|
this.remove_tile(actor);
|
||||||
@ -972,7 +966,7 @@ export class Level {
|
|||||||
// Check for a couple effects that always apply immediately
|
// Check for a couple effects that always apply immediately
|
||||||
// TODO do blocks smash monsters?
|
// TODO do blocks smash monsters?
|
||||||
if (actor === this.player) {
|
if (actor === this.player) {
|
||||||
this._set_prop(this, 'hint_shown', null);
|
this.hint_shown = null;
|
||||||
}
|
}
|
||||||
for (let tile of goal_cell) {
|
for (let tile of goal_cell) {
|
||||||
if (actor.type.is_player && tile.type.is_monster) {
|
if (actor.type.is_player && tile.type.is_monster) {
|
||||||
@ -990,7 +984,7 @@ export class Level {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (actor === this.player && tile.type.is_hint) {
|
if (actor === this.player && tile.type.is_hint) {
|
||||||
this._set_prop(this, 'hint_shown', tile.specific_hint ?? this.stored_level.hint);
|
this.hint_shown = tile.specific_hint ?? this.stored_level.hint;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1321,13 +1315,19 @@ export class Level {
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Undo handling
|
// Undo handling
|
||||||
|
|
||||||
|
create_undo_entry() {
|
||||||
|
let entry = [];
|
||||||
|
entry.tile_changes = new Map;
|
||||||
|
entry.level_props = {};
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
commit() {
|
commit() {
|
||||||
this.undo_stack.push(this.pending_undo);
|
this.undo_stack.push(this.pending_undo);
|
||||||
this.pending_undo = [];
|
this.pending_undo = this.create_undo_entry();
|
||||||
|
|
||||||
// Limit the stack to, idk, 200 tics (10 seconds)
|
if (this.undo_stack.length > UNDO_STACK_SIZE) {
|
||||||
if (this.undo_stack.length > 200) {
|
this.undo_stack.splice(0, this.undo_stack.length - UNDO_STACK_SIZE);
|
||||||
this.undo_stack.splice(0, this.undo_stack.length - 200);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1335,18 +1335,27 @@ export class Level {
|
|||||||
this.aid = Math.max(1, this.aid);
|
this.aid = Math.max(1, this.aid);
|
||||||
|
|
||||||
// In turn-based mode, we might still be in mid-tic with a partial undo stack; do that first
|
// In turn-based mode, we might still be in mid-tic with a partial undo stack; do that first
|
||||||
this.pending_undo.reverse();
|
this._undo_entry(this.pending_undo);
|
||||||
for (let undo of this.pending_undo) {
|
this.pending_undo = this.create_undo_entry();
|
||||||
undo();
|
|
||||||
}
|
|
||||||
this.pending_undo = [];
|
|
||||||
|
|
||||||
let entry = this.undo_stack.pop();
|
this._undo_entry(this.undo_stack.pop());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reverse a single undo entry
|
||||||
|
_undo_entry(entry) {
|
||||||
// Undo in reverse order! There's no redo, so it's okay to destroy this
|
// Undo in reverse order! There's no redo, so it's okay to destroy this
|
||||||
entry.reverse();
|
entry.reverse();
|
||||||
for (let undo of entry) {
|
for (let undo of entry) {
|
||||||
undo();
|
undo();
|
||||||
}
|
}
|
||||||
|
for (let [tile, changes] of entry.tile_changes) {
|
||||||
|
for (let [key, value] of changes) {
|
||||||
|
tile[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let [key, value] of Object.entries(entry.level_props)) {
|
||||||
|
this[key] = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
@ -1354,26 +1363,37 @@ export class Level {
|
|||||||
// including the state of a single tile, should do it through one of these
|
// including the state of a single tile, should do it through one of these
|
||||||
// for undo/rewind purposes
|
// for undo/rewind purposes
|
||||||
|
|
||||||
_set_prop(obj, key, val) {
|
_set_tile_prop(tile, key, val) {
|
||||||
let old_val = obj[key];
|
if (tile[key] === val)
|
||||||
if (val === old_val)
|
|
||||||
return;
|
return;
|
||||||
this.pending_undo.push(() => obj[key] = old_val);
|
|
||||||
obj[key] = val;
|
let changes = this.pending_undo.tile_changes.get(tile);
|
||||||
|
if (! changes) {
|
||||||
|
changes = new Map;
|
||||||
|
this.pending_undo.tile_changes.set(tile, changes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we haven't yet done so, log the original value
|
||||||
|
if (! changes.has(key)) {
|
||||||
|
changes.set(key, tile[key]);
|
||||||
|
}
|
||||||
|
// If there's an original value already logged, and it's the value we're about to change
|
||||||
|
// back to, then delete the change
|
||||||
|
else if (changes.get(key) === val) {
|
||||||
|
changes.delete(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
tile[key] = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
collect_chip() {
|
collect_chip() {
|
||||||
let current = this.chips_remaining;
|
if (this.chips_remaining > 0) {
|
||||||
if (current > 0) {
|
|
||||||
this.sfx.play_once('get-chip');
|
this.sfx.play_once('get-chip');
|
||||||
this.pending_undo.push(() => this.chips_remaining = current);
|
|
||||||
this.chips_remaining--;
|
this.chips_remaining--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
adjust_bonus(add, mult = 1) {
|
adjust_bonus(add, mult = 1) {
|
||||||
let current = this.bonus_points;
|
|
||||||
this.pending_undo.push(() => this.bonus_points = current);
|
|
||||||
this.bonus_points = Math.ceil(this.bonus_points * mult) + add;
|
this.bonus_points = Math.ceil(this.bonus_points * mult) + add;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1381,14 +1401,10 @@ export class Level {
|
|||||||
if (this.time_remaining === null)
|
if (this.time_remaining === null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this.pending_undo.push(() => this.timer_paused = ! this.timer_paused);
|
|
||||||
this.timer_paused = ! this.timer_paused;
|
this.timer_paused = ! this.timer_paused;
|
||||||
}
|
}
|
||||||
|
|
||||||
adjust_timer(dt) {
|
adjust_timer(dt) {
|
||||||
let current = this.time_remaining;
|
|
||||||
this.pending_undo.push(() => this.time_remaining = current);
|
|
||||||
|
|
||||||
// Untimed levels become timed levels with 0 seconds remaining
|
// Untimed levels become timed levels with 0 seconds remaining
|
||||||
this.time_remaining = Math.max(0, (this.time_remaining ?? 0) + dt * 20);
|
this.time_remaining = Math.max(0, (this.time_remaining ?? 0) + dt * 20);
|
||||||
if (this.time_remaining <= 0) {
|
if (this.time_remaining <= 0) {
|
||||||
@ -1410,7 +1426,6 @@ export class Level {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.pending_undo.push(() => {
|
this.pending_undo.push(() => {
|
||||||
this.state = 'playing';
|
|
||||||
this.fail_reason = null;
|
this.fail_reason = null;
|
||||||
this.player.fail_reason = null;
|
this.player.fail_reason = null;
|
||||||
});
|
});
|
||||||
@ -1422,7 +1437,6 @@ export class Level {
|
|||||||
|
|
||||||
win() {
|
win() {
|
||||||
this.sfx.play_once('win');
|
this.sfx.play_once('win');
|
||||||
this.pending_undo.push(() => this.state = 'playing');
|
|
||||||
this.state = 'success';
|
this.state = 'success';
|
||||||
throw new GameEnded;
|
throw new GameEnded;
|
||||||
}
|
}
|
||||||
@ -1472,8 +1486,8 @@ export class Level {
|
|||||||
spawn_animation(cell, name) {
|
spawn_animation(cell, name) {
|
||||||
let type = TILE_TYPES[name];
|
let type = TILE_TYPES[name];
|
||||||
let tile = new Tile(type);
|
let tile = new Tile(type);
|
||||||
this._set_prop(tile, 'animation_speed', tile.type.ttl);
|
this._set_tile_prop(tile, 'animation_speed', tile.type.ttl);
|
||||||
this._set_prop(tile, 'animation_progress', 0);
|
this._set_tile_prop(tile, 'animation_progress', 0);
|
||||||
cell._add(tile);
|
cell._add(tile);
|
||||||
this.actors.push(tile);
|
this.actors.push(tile);
|
||||||
this.pending_undo.push(() => {
|
this.pending_undo.push(() => {
|
||||||
@ -1492,8 +1506,8 @@ export class Level {
|
|||||||
if (! TILE_TYPES[current].is_actor) {
|
if (! TILE_TYPES[current].is_actor) {
|
||||||
console.warn("Transmuting a non-actor into an animation!");
|
console.warn("Transmuting a non-actor into an animation!");
|
||||||
}
|
}
|
||||||
this._set_prop(tile, 'animation_speed', tile.type.ttl);
|
this._set_tile_prop(tile, 'animation_speed', tile.type.ttl);
|
||||||
this._set_prop(tile, 'animation_progress', 0);
|
this._set_tile_prop(tile, 'animation_progress', 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1566,23 +1580,15 @@ export class Level {
|
|||||||
|
|
||||||
// Mark an actor as sliding
|
// Mark an actor as sliding
|
||||||
make_slide(actor, mode) {
|
make_slide(actor, mode) {
|
||||||
let current = actor.slide_mode;
|
this._set_tile_prop(actor, 'slide_mode', mode);
|
||||||
this.pending_undo.push(() => actor.slide_mode = current);
|
|
||||||
actor.slide_mode = mode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Change an actor's direction
|
// Change an actor's direction
|
||||||
set_actor_direction(actor, direction) {
|
set_actor_direction(actor, direction) {
|
||||||
let current = actor.direction;
|
this._set_tile_prop(actor, 'direction', direction);
|
||||||
this.pending_undo.push(() => actor.direction = current);
|
|
||||||
actor.direction = direction;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set_actor_stuck(actor, is_stuck) {
|
set_actor_stuck(actor, is_stuck) {
|
||||||
let current = actor.stuck;
|
this._set_tile_prop(actor, 'stuck', is_stuck);
|
||||||
if (current === is_stuck)
|
|
||||||
return;
|
|
||||||
this.pending_undo.push(() => actor.stuck = current);
|
|
||||||
actor.stuck = is_stuck;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -747,7 +747,7 @@ const TILE_TYPES = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
add_press(me, level) {
|
add_press(me, level) {
|
||||||
level._set_prop(me, 'presses', (me.presses ?? 0) + 1);
|
level._set_tile_prop(me, 'presses', (me.presses ?? 0) + 1);
|
||||||
if (me.presses === 1) {
|
if (me.presses === 1) {
|
||||||
// Free everything on us, if we went from 0 to 1 presses (i.e. closed to open)
|
// 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)) {
|
for (let tile of Array.from(me.cell)) {
|
||||||
@ -761,7 +761,7 @@ const TILE_TYPES = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
remove_press(me, level) {
|
remove_press(me, level) {
|
||||||
level._set_prop(me, 'presses', me.presses - 1);
|
level._set_tile_prop(me, 'presses', me.presses - 1);
|
||||||
if (me.presses === 0) {
|
if (me.presses === 0) {
|
||||||
// Trap everything on us, if we went from 1 to 0 presses (i.e. open to closed)
|
// Trap everything on us, if we went from 1 to 0 presses (i.e. open to closed)
|
||||||
for (let tile of me.cell) {
|
for (let tile of me.cell) {
|
||||||
@ -920,7 +920,7 @@ const TILE_TYPES = {
|
|||||||
for (let actor of level.actors) {
|
for (let actor of level.actors) {
|
||||||
// TODO generify somehow??
|
// TODO generify somehow??
|
||||||
if (actor.type.name === 'tank_blue') {
|
if (actor.type.name === 'tank_blue') {
|
||||||
level._set_prop(actor, 'pending_reverse', ! actor.pending_reverse);
|
level._set_tile_prop(actor, 'pending_reverse', ! actor.pending_reverse);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user