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 ( return (
this.type.pushes && this.type.pushes[tile.type.name] && this.type.pushes && this.type.pushes[tile.type.name] &&
(! tile.type.allows_push || tile.type.allows_push(tile, direction)) && (! 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 // Inventory stuff
@ -155,12 +157,14 @@ export class Cell extends Array {
blocks_leaving(actor, direction) { blocks_leaving(actor, direction) {
for (let tile of this) { for (let tile of this) {
if (tile !== actor && if (tile === actor)
! tile.type.is_swivel && tile.type.thin_walls && continue;
tile.type.thin_walls.has(direction))
{ 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 true;
}
} }
return false; return false;
} }
@ -266,7 +270,6 @@ export class Level {
let stored_cell = this.stored_level.linear_cells[n]; let stored_cell = this.stored_level.linear_cells[n];
n++; n++;
let has_cloner, has_trap, has_forbidden;
for (let template_tile of stored_cell) { for (let template_tile of stored_cell) {
let tile = Tile.from_template(template_tile); let tile = Tile.from_template(template_tile);
@ -279,31 +282,14 @@ export class Level {
this.power_sources.push(tile); 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) { if (tile.type.is_player) {
// TODO handle multiple players, also chip and melinda both // TODO handle multiple players, also chip and melinda both
// TODO complain if no player // TODO complain if no player
this.player = tile; this.player = tile;
} }
if (tile.type.is_actor) { 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); this.actors.push(tile);
} }
}
cell._add(tile); cell._add(tile);
if (tile.type.connects_to) { if (tile.type.connects_to) {
@ -581,9 +567,9 @@ export class Level {
{ {
this._set_tile_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
// stop sliding, regardless of what they landed on
if (actor.pending_push) { 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; actor.decision = actor.pending_push;
this._set_tile_prop(actor, 'pending_push', null); this._set_tile_prop(actor, 'pending_push', null);
continue; continue;
@ -622,6 +608,13 @@ export class Level {
} }
continue; 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') { else if (actor.type.movement_mode === 'forward') {
// blue tank behavior: keep moving forward, reverse if the flag is set // blue tank behavior: keep moving forward, reverse if the flag is set
let direction = actor.direction; let direction = actor.direction;
@ -715,9 +708,7 @@ export class Level {
// Check which of those directions we *can*, probably, move in // Check which of those directions we *can*, probably, move in
// TODO i think player on force floor will still have some issues here // 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 if (direction_preference) {
// lynx behavior? also i hear something about blobs on cloners??
if (direction_preference && ! actor.stuck) {
let fallback_direction; let fallback_direction;
for (let direction of direction_preference) { for (let direction of direction_preference) {
if (direction === 'WALKER') { if (direction === 'WALKER') {
@ -837,9 +828,6 @@ export class Level {
this.set_actor_direction(actor, direction); 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 // Record our speed, and halve it below if we're stepping onto a sliding tile
let speed = actor.type.movement_speed; let speed = actor.type.movement_speed;
@ -1628,8 +1616,4 @@ export class Level {
set_actor_direction(actor, direction) { set_actor_direction(actor, direction) {
this._set_tile_prop(actor, 'direction', 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) { function player_visual_state(me) {
if (! me) { if (! me) {
return 'normal'; return 'normal';
@ -169,22 +173,27 @@ const TILE_TYPES = {
thinwall_n: { thinwall_n: {
draw_layer: LAYER_OVERLAY, draw_layer: LAYER_OVERLAY,
thin_walls: new Set(['north']), thin_walls: new Set(['north']),
blocks_leaving: blocks_leaving_thin_walls,
}, },
thinwall_s: { thinwall_s: {
draw_layer: LAYER_OVERLAY, draw_layer: LAYER_OVERLAY,
thin_walls: new Set(['south']), thin_walls: new Set(['south']),
blocks_leaving: blocks_leaving_thin_walls,
}, },
thinwall_e: { thinwall_e: {
draw_layer: LAYER_OVERLAY, draw_layer: LAYER_OVERLAY,
thin_walls: new Set(['east']), thin_walls: new Set(['east']),
blocks_leaving: blocks_leaving_thin_walls,
}, },
thinwall_w: { thinwall_w: {
draw_layer: LAYER_OVERLAY, draw_layer: LAYER_OVERLAY,
thin_walls: new Set(['west']), thin_walls: new Set(['west']),
blocks_leaving: blocks_leaving_thin_walls,
}, },
thinwall_se: { thinwall_se: {
draw_layer: LAYER_OVERLAY, draw_layer: LAYER_OVERLAY,
thin_walls: new Set(['south', 'east']), thin_walls: new Set(['south', 'east']),
blocks_leaving: blocks_leaving_thin_walls,
}, },
fake_wall: { fake_wall: {
draw_layer: LAYER_TERRAIN, draw_layer: LAYER_TERRAIN,
@ -263,7 +272,6 @@ const TILE_TYPES = {
swivel_ne: { swivel_ne: {
draw_layer: LAYER_OVERLAY, draw_layer: LAYER_OVERLAY,
thin_walls: new Set(['north', 'east']), thin_walls: new Set(['north', 'east']),
is_swivel: true,
on_depart(me, level, other) { on_depart(me, level, other) {
if (other.direction === 'north') { if (other.direction === 'north') {
level.transmute_tile(me, 'swivel_se'); level.transmute_tile(me, 'swivel_se');
@ -276,7 +284,6 @@ const TILE_TYPES = {
swivel_se: { swivel_se: {
draw_layer: LAYER_OVERLAY, draw_layer: LAYER_OVERLAY,
thin_walls: new Set(['south', 'east']), thin_walls: new Set(['south', 'east']),
is_swivel: true,
on_depart(me, level, other) { on_depart(me, level, other) {
if (other.direction === 'south') { if (other.direction === 'south') {
level.transmute_tile(me, 'swivel_ne'); level.transmute_tile(me, 'swivel_ne');
@ -289,7 +296,6 @@ const TILE_TYPES = {
swivel_sw: { swivel_sw: {
draw_layer: LAYER_OVERLAY, draw_layer: LAYER_OVERLAY,
thin_walls: new Set(['south', 'west']), thin_walls: new Set(['south', 'west']),
is_swivel: true,
on_depart(me, level, other) { on_depart(me, level, other) {
if (other.direction === 'south') { if (other.direction === 'south') {
level.transmute_tile(me, 'swivel_nw'); level.transmute_tile(me, 'swivel_nw');
@ -302,7 +308,6 @@ const TILE_TYPES = {
swivel_nw: { swivel_nw: {
draw_layer: LAYER_OVERLAY, draw_layer: LAYER_OVERLAY,
thin_walls: new Set(['north', 'west']), thin_walls: new Set(['north', 'west']),
is_swivel: true,
on_depart(me, level, other) { on_depart(me, level, other) {
if (other.direction === 'north') { if (other.direction === 'north') {
level.transmute_tile(me, 'swivel_sw'); level.transmute_tile(me, 'swivel_sw');
@ -450,6 +455,7 @@ const TILE_TYPES = {
draw_layer: LAYER_TERRAIN, draw_layer: LAYER_TERRAIN,
thin_walls: new Set(['south', 'west']), thin_walls: new Set(['south', 'west']),
slide_mode: 'ice', slide_mode: 'ice',
blocks_leaving: blocks_leaving_thin_walls,
on_arrive(me, level, other) { on_arrive(me, level, other) {
if (other.direction === 'south') { if (other.direction === 'south') {
level.set_actor_direction(other, 'east'); level.set_actor_direction(other, 'east');
@ -463,6 +469,7 @@ const TILE_TYPES = {
draw_layer: LAYER_TERRAIN, draw_layer: LAYER_TERRAIN,
thin_walls: new Set(['north', 'west']), thin_walls: new Set(['north', 'west']),
slide_mode: 'ice', slide_mode: 'ice',
blocks_leaving: blocks_leaving_thin_walls,
on_arrive(me, level, other) { on_arrive(me, level, other) {
if (other.direction === 'north') { if (other.direction === 'north') {
level.set_actor_direction(other, 'east'); level.set_actor_direction(other, 'east');
@ -476,6 +483,7 @@ const TILE_TYPES = {
draw_layer: LAYER_TERRAIN, draw_layer: LAYER_TERRAIN,
thin_walls: new Set(['north', 'east']), thin_walls: new Set(['north', 'east']),
slide_mode: 'ice', slide_mode: 'ice',
blocks_leaving: blocks_leaving_thin_walls,
on_arrive(me, level, other) { on_arrive(me, level, other) {
if (other.direction === 'north') { if (other.direction === 'north') {
level.set_actor_direction(other, 'west'); level.set_actor_direction(other, 'west');
@ -489,6 +497,7 @@ const TILE_TYPES = {
draw_layer: LAYER_TERRAIN, draw_layer: LAYER_TERRAIN,
thin_walls: new Set(['south', 'east']), thin_walls: new Set(['south', 'east']),
slide_mode: 'ice', slide_mode: 'ice',
blocks_leaving: blocks_leaving_thin_walls,
on_arrive(me, level, other) { on_arrive(me, level, other) {
if (other.direction === 'south') { if (other.direction === 'south') {
level.set_actor_direction(other, 'west'); level.set_actor_direction(other, 'west');
@ -718,30 +727,30 @@ const TILE_TYPES = {
draw_layer: LAYER_TERRAIN, draw_layer: LAYER_TERRAIN,
// TODO not the case for an empty one in cc2, apparently // TODO not the case for an empty one in cc2, apparently
blocks_all: true, blocks_all: true,
traps(me, actor) {
return ! actor._clone_release;
},
activate(me, level) { activate(me, level) {
let cell = me.cell; let actor = me.cell.get_actor();
// Copy, so we don't end up repeatedly cloning the same object if (! actor)
for (let tile of Array.from(cell)) { return;
if (tile !== me && tile.type.is_actor) {
// Copy this stuff in case the movement changes it
let type = tile.type;
let direction = tile.direction;
// Unstick and try to move the actor; if it's blocked, // Copy this stuff in case the movement changes it
// abort the clone // TODO should anything else be preserved?
level.set_actor_stuck(tile, false); let type = actor.type;
if (level.attempt_step(tile, direction)) { let direction = actor.direction;
level.add_actor(tile);
// 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 // FIXME add this underneath, just above the cloner
let new_tile = new tile.constructor(type, direction); let new_template = new actor.constructor(type, direction);
level.add_tile(new_tile, cell); level.add_tile(new_template, me.cell);
level.set_actor_stuck(new_tile, true); level.add_actor(new_template);
}
else {
level.set_actor_stuck(tile, true);
}
}
} }
delete actor._clone_release;
}, },
// Also clones on rising pulse or gray button // Also clones on rising pulse or gray button
on_power(me, level) { on_power(me, level) {
@ -753,45 +762,17 @@ const TILE_TYPES = {
}, },
trap: { trap: {
draw_layer: LAYER_TERRAIN, 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) { 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; 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) { add_press(me, level) {
level._set_tile_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)) {
if (tile.type.is_actor) { if (tile.type.is_actor) {
level.set_actor_stuck(tile, false);
// Forcibly move anything released from a trap, to keep it in sync with // Forcibly move anything released from a trap, to keep it in sync with
// whatever pushed the button // whatever pushed the button
level.attempt_step(tile, tile.direction); level.attempt_step(tile, tile.direction);
@ -801,23 +782,16 @@ const TILE_TYPES = {
}, },
remove_press(me, level) { remove_press(me, level) {
level._set_tile_prop(me, 'presses', me.presses - 1); 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) traps(me, actor) {
for (let tile of me.cell) { return ! me.presses;
if (tile.type.is_actor) {
level.set_actor_stuck(tile, true);
}
}
}
}, },
on_power(me, level) { on_power(me, level) {
// Treat being powered or not as an extra kind of brown button press // Treat being powered or not as an extra kind of brown button press
me.type.add_press(me, level); me.type.add_press(me, level);
console.log("powering UP", me.presses);
}, },
on_depower(me, level) { on_depower(me, level) {
me.type.remove_press(me, level); me.type.remove_press(me, level);
console.log("powering down...", me.presses);
}, },
visual_state(me) { visual_state(me) {
if (me && me.presses) { if (me && me.presses) {