Remove the "stuck" flag and fix all the repercussions of that

This commit is contained in:
Eevee (Evelyn Woods) 2020-11-23 21:35:28 -07:00
parent e803af2fd2
commit 39d463932b
2 changed files with 60 additions and 102 deletions

View File

@ -82,7 +82,9 @@ export class Tile {
return (
this.type.pushes && this.type.pushes[tile.type.name] &&
(! tile.type.allows_push || tile.type.allows_push(tile, direction)) &&
! tile.stuck);
// 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
! tile.cell.blocks_leaving(tile, direction));
}
// Inventory stuff
@ -155,12 +157,14 @@ export class Cell extends Array {
blocks_leaving(actor, direction) {
for (let tile of this) {
if (tile !== actor &&
! tile.type.is_swivel && tile.type.thin_walls &&
tile.type.thin_walls.has(direction))
{
if (tile === actor)
continue;
if (tile.type.traps && tile.type.traps(tile, actor))
return true;
if (tile.type.blocks_leaving && tile.type.blocks_leaving(tile, actor, direction))
return true;
}
}
return false;
}
@ -266,7 +270,6 @@ export class Level {
let stored_cell = this.stored_level.linear_cells[n];
n++;
let has_cloner, has_trap, has_forbidden;
for (let template_tile of stored_cell) {
let tile = Tile.from_template(template_tile);
@ -279,31 +282,14 @@ export class Level {
this.power_sources.push(tile);
}
// TODO well this is pretty special-casey. maybe come up
// with a specific pass at the beginning of the level?
// TODO also assumes a specific order...
if (tile.type.name === 'cloner') {
has_cloner = true;
}
if (tile.type.name === 'trap') {
has_trap = true;
}
if (tile.type.is_player) {
// TODO handle multiple players, also chip and melinda both
// TODO complain if no player
this.player = tile;
}
if (tile.type.is_actor) {
if (has_cloner) {
// TODO is there any reason not to add clone templates to the actor
// list?
tile.stuck = true;
}
if (! tile.stuck) {
this.actors.push(tile);
}
}
cell._add(tile);
if (tile.type.connects_to) {
@ -581,9 +567,9 @@ export class Level {
{
this._set_tile_prop(actor, 'pending_reverse', false);
}
// Blocks that were pushed while sliding will move in the push direction as soon as they
// stop sliding, regardless of what they landed on
if (actor.pending_push) {
// Blocks that were pushed while sliding will move in the push direction as soon as
// they stop sliding, regardless of what they landed on
actor.decision = actor.pending_push;
this._set_tile_prop(actor, 'pending_push', null);
continue;
@ -622,6 +608,13 @@ export class Level {
}
continue;
}
else if (actor.cell.some(tile => tile.type.traps && tile.type.traps(tile, 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
// go back into the trap. this is consistent with CC2 but not ms/lynx
continue;
}
else if (actor.type.movement_mode === 'forward') {
// blue tank behavior: keep moving forward, reverse if the flag is set
let direction = actor.direction;
@ -715,9 +708,7 @@ export class Level {
// Check which of those directions we *can*, probably, move in
// TODO i think player on force floor will still have some issues here
// FIXME probably bail earlier for stuck actors so the prng isn't advanced? what is the
// lynx behavior? also i hear something about blobs on cloners??
if (direction_preference && ! actor.stuck) {
if (direction_preference) {
let fallback_direction;
for (let direction of direction_preference) {
if (direction === 'WALKER') {
@ -837,9 +828,6 @@ export class Level {
this.set_actor_direction(actor, direction);
if (actor.stuck)
return false;
// Record our speed, and halve it below if we're stepping onto a sliding tile
let speed = actor.type.movement_speed;
@ -1628,8 +1616,4 @@ export class Level {
set_actor_direction(actor, direction) {
this._set_tile_prop(actor, 'direction', direction);
}
set_actor_stuck(actor, is_stuck) {
this._set_tile_prop(actor, 'stuck', is_stuck);
}
}

View File

@ -43,6 +43,10 @@ function on_ready_force_floor(me, level) {
}
}
function blocks_leaving_thin_walls(me, actor, direction) {
return me.type.thin_walls.has(direction);
}
function player_visual_state(me) {
if (! me) {
return 'normal';
@ -169,22 +173,27 @@ const TILE_TYPES = {
thinwall_n: {
draw_layer: LAYER_OVERLAY,
thin_walls: new Set(['north']),
blocks_leaving: blocks_leaving_thin_walls,
},
thinwall_s: {
draw_layer: LAYER_OVERLAY,
thin_walls: new Set(['south']),
blocks_leaving: blocks_leaving_thin_walls,
},
thinwall_e: {
draw_layer: LAYER_OVERLAY,
thin_walls: new Set(['east']),
blocks_leaving: blocks_leaving_thin_walls,
},
thinwall_w: {
draw_layer: LAYER_OVERLAY,
thin_walls: new Set(['west']),
blocks_leaving: blocks_leaving_thin_walls,
},
thinwall_se: {
draw_layer: LAYER_OVERLAY,
thin_walls: new Set(['south', 'east']),
blocks_leaving: blocks_leaving_thin_walls,
},
fake_wall: {
draw_layer: LAYER_TERRAIN,
@ -263,7 +272,6 @@ const TILE_TYPES = {
swivel_ne: {
draw_layer: LAYER_OVERLAY,
thin_walls: new Set(['north', 'east']),
is_swivel: true,
on_depart(me, level, other) {
if (other.direction === 'north') {
level.transmute_tile(me, 'swivel_se');
@ -276,7 +284,6 @@ const TILE_TYPES = {
swivel_se: {
draw_layer: LAYER_OVERLAY,
thin_walls: new Set(['south', 'east']),
is_swivel: true,
on_depart(me, level, other) {
if (other.direction === 'south') {
level.transmute_tile(me, 'swivel_ne');
@ -289,7 +296,6 @@ const TILE_TYPES = {
swivel_sw: {
draw_layer: LAYER_OVERLAY,
thin_walls: new Set(['south', 'west']),
is_swivel: true,
on_depart(me, level, other) {
if (other.direction === 'south') {
level.transmute_tile(me, 'swivel_nw');
@ -302,7 +308,6 @@ const TILE_TYPES = {
swivel_nw: {
draw_layer: LAYER_OVERLAY,
thin_walls: new Set(['north', 'west']),
is_swivel: true,
on_depart(me, level, other) {
if (other.direction === 'north') {
level.transmute_tile(me, 'swivel_sw');
@ -450,6 +455,7 @@ const TILE_TYPES = {
draw_layer: LAYER_TERRAIN,
thin_walls: new Set(['south', 'west']),
slide_mode: 'ice',
blocks_leaving: blocks_leaving_thin_walls,
on_arrive(me, level, other) {
if (other.direction === 'south') {
level.set_actor_direction(other, 'east');
@ -463,6 +469,7 @@ const TILE_TYPES = {
draw_layer: LAYER_TERRAIN,
thin_walls: new Set(['north', 'west']),
slide_mode: 'ice',
blocks_leaving: blocks_leaving_thin_walls,
on_arrive(me, level, other) {
if (other.direction === 'north') {
level.set_actor_direction(other, 'east');
@ -476,6 +483,7 @@ const TILE_TYPES = {
draw_layer: LAYER_TERRAIN,
thin_walls: new Set(['north', 'east']),
slide_mode: 'ice',
blocks_leaving: blocks_leaving_thin_walls,
on_arrive(me, level, other) {
if (other.direction === 'north') {
level.set_actor_direction(other, 'west');
@ -489,6 +497,7 @@ const TILE_TYPES = {
draw_layer: LAYER_TERRAIN,
thin_walls: new Set(['south', 'east']),
slide_mode: 'ice',
blocks_leaving: blocks_leaving_thin_walls,
on_arrive(me, level, other) {
if (other.direction === 'south') {
level.set_actor_direction(other, 'west');
@ -718,30 +727,30 @@ const TILE_TYPES = {
draw_layer: LAYER_TERRAIN,
// TODO not the case for an empty one in cc2, apparently
blocks_all: true,
traps(me, actor) {
return ! actor._clone_release;
},
activate(me, level) {
let cell = me.cell;
// Copy, so we don't end up repeatedly cloning the same object
for (let tile of Array.from(cell)) {
if (tile !== me && tile.type.is_actor) {
// Copy this stuff in case the movement changes it
let type = tile.type;
let direction = tile.direction;
let actor = me.cell.get_actor();
if (! actor)
return;
// Unstick and try to move the actor; if it's blocked,
// abort the clone
level.set_actor_stuck(tile, false);
if (level.attempt_step(tile, direction)) {
level.add_actor(tile);
// Copy this stuff in case the movement changes it
// TODO should anything else be preserved?
let type = actor.type;
let direction = actor.direction;
// Unstick and try to move the actor; if it's blocked, abort the clone.
// This temporary flag tells us to let it leave; it doesn't need to be undoable, since
// it doesn't persist for more than a tic
actor._clone_release = true;
if (level.attempt_step(actor, direction)) {
// FIXME add this underneath, just above the cloner
let new_tile = new tile.constructor(type, direction);
level.add_tile(new_tile, cell);
level.set_actor_stuck(new_tile, true);
}
else {
level.set_actor_stuck(tile, true);
}
}
let new_template = new actor.constructor(type, direction);
level.add_tile(new_template, me.cell);
level.add_actor(new_template);
}
delete actor._clone_release;
},
// Also clones on rising pulse or gray button
on_power(me, level) {
@ -753,45 +762,17 @@ const TILE_TYPES = {
},
trap: {
draw_layer: LAYER_TERRAIN,
on_ready(me, level) {
// Trap any actor standing on us, unless we already know we're pressed
if (me.presses)
return;
for (let tile of me.cell) {
if (tile.type.is_actor) {
tile.stuck = true;
}
}
},
add_press_ready(me, level, other) {
// Same as below, but without using the undo stack or ejection
// Same as below, but without ejection
me.presses = (me.presses ?? 0) + 1;
if (me.presses === 1) {
for (let tile of me.cell) {
if (tile.type.is_actor) {
tile.stuck = false;
}
}
}
},
on_arrive(me, level, other) {
if (me.presses) {
// Lynx: Traps immediately eject their contents, if possible
// TODO compat this, cc2 doens't do it!
//level.attempt_step(other, other.direction);
}
else {
level.set_actor_stuck(other, true);
}
},
// Lynx (not cc2): open traps immediately eject their contents on arrival, if possible
add_press(me, level) {
level._set_tile_prop(me, 'presses', (me.presses ?? 0) + 1);
if (me.presses === 1) {
// 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) {
level.set_actor_stuck(tile, false);
// Forcibly move anything released from a trap, to keep it in sync with
// whatever pushed the button
level.attempt_step(tile, tile.direction);
@ -801,23 +782,16 @@ const TILE_TYPES = {
},
remove_press(me, level) {
level._set_tile_prop(me, 'presses', me.presses - 1);
if (me.presses === 0) {
// Trap everything on us, if we went from 1 to 0 presses (i.e. open to closed)
for (let tile of me.cell) {
if (tile.type.is_actor) {
level.set_actor_stuck(tile, true);
}
}
}
},
traps(me, actor) {
return ! me.presses;
},
on_power(me, level) {
// Treat being powered or not as an extra kind of brown button press
me.type.add_press(me, level);
console.log("powering UP", me.presses);
},
on_depower(me, level) {
me.type.remove_press(me, level);
console.log("powering down...", me.presses);
},
visual_state(me) {
if (me && me.presses) {