Split up the actor loop, so actors make decisions in a separate pass
This fixes a lot of subtle issues: creatures hitting you when you push a block past them, blocks moving jerkily while you push them (not even sure why on that one), probably implementation of "the stupid glitch"...
This commit is contained in:
parent
549b34ad30
commit
1453f68de5
154
js/game.js
154
js/game.js
@ -56,6 +56,9 @@ export class Tile {
|
|||||||
if (other.type.is_block && this.type.blocks_blocks)
|
if (other.type.is_block && this.type.blocks_blocks)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
if (this.type.blocks)
|
||||||
|
return this.type.blocks(this, other);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,10 +81,11 @@ export class Tile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Inventory stuff
|
// Inventory stuff
|
||||||
give_item(name) {
|
has_item(name) {
|
||||||
this.inventory[name] = (this.inventory[name] ?? 0) + 1;
|
return this.inventory[name] ?? 0 > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO remove, not undoable
|
||||||
take_item(name, amount = null) {
|
take_item(name, amount = null) {
|
||||||
if (this.inventory[name] && this.inventory[name] >= 1) {
|
if (this.inventory[name] && this.inventory[name] >= 1) {
|
||||||
if (amount == null && this.type.infinite_items && this.type.infinite_items[name]) {
|
if (amount == null && this.type.infinite_items && this.type.infinite_items[name]) {
|
||||||
@ -128,6 +132,26 @@ export class Cell extends Array {
|
|||||||
tile.cell = null;
|
tile.cell = null;
|
||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
blocks_entering(actor, direction) {
|
||||||
|
for (let tile of this) {
|
||||||
|
if (tile.blocks(actor, direction) && ! actor.ignores(tile.type.name))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Level {
|
export class Level {
|
||||||
@ -311,6 +335,7 @@ export class Level {
|
|||||||
|
|
||||||
// XXX this entire turn order is rather different in ms rules
|
// XXX this entire turn order is rather different in ms rules
|
||||||
// FIXME OK, do a pass to make everyone decide their movement, and then actually do it. the question iiis, where does that fit in with animation
|
// FIXME OK, do a pass to make everyone decide their movement, and then actually do it. the question iiis, where does that fit in with animation
|
||||||
|
// First pass: tick cooldowns and animations; have actors arrive in their cells
|
||||||
for (let actor of this.actors) {
|
for (let actor of this.actors) {
|
||||||
// Actors with no cell were destroyed
|
// Actors with no cell were destroyed
|
||||||
if (! actor.cell)
|
if (! actor.cell)
|
||||||
@ -336,12 +361,19 @@ export class Level {
|
|||||||
actor.animation_speed = null;
|
actor.animation_speed = null;
|
||||||
if (! this.compat.tiles_react_instantly) {
|
if (! this.compat.tiles_react_instantly) {
|
||||||
this.step_on_cell(actor);
|
this.step_on_cell(actor);
|
||||||
// May have been destroyed here, too!
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second pass: actors decide their upcoming movement simultaneously
|
||||||
|
for (let actor of this.actors) {
|
||||||
|
// Note that this prop is only used internally within a single iteration of this loop,
|
||||||
|
// so it doesn't need to be undoable
|
||||||
|
actor.decision = null;
|
||||||
|
|
||||||
if (! actor.cell)
|
if (! actor.cell)
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (actor.movement_cooldown > 0)
|
if (actor.movement_cooldown > 0)
|
||||||
continue;
|
continue;
|
||||||
@ -373,7 +405,7 @@ export class Level {
|
|||||||
player_direction &&
|
player_direction &&
|
||||||
actor.last_move_was_force)
|
actor.last_move_was_force)
|
||||||
{
|
{
|
||||||
direction_preference = [player_direction, actor.direction];
|
direction_preference = [player_direction];
|
||||||
this._set_prop(actor, 'last_move_was_force', false);
|
this._set_prop(actor, 'last_move_was_force', false);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -463,24 +495,61 @@ export class Level {
|
|||||||
direction_preference = [['north', 'south', 'east', 'west'][Math.floor(Math.random() * 4)]];
|
direction_preference = [['north', 'south', 'east', 'west'][Math.floor(Math.random() * 4)]];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! direction_preference)
|
// Check which of those directions we *can*, probably, move in
|
||||||
|
// TODO i think player on force floor will still have some issues here
|
||||||
|
if (direction_preference) {
|
||||||
|
// Players always move the way they want, even if blocked
|
||||||
|
if (actor.type.is_player) {
|
||||||
|
actor.decision = direction_preference[0];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let direction of direction_preference) {
|
||||||
|
let dest_cell = this.cell_with_offset(actor.cell, direction);
|
||||||
|
if (! dest_cell)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
let moved = false;
|
if (! actor.cell.blocks_leaving(actor, direction) &&
|
||||||
for (let direction of direction_preference) {
|
! dest_cell.blocks_entering(actor, direction))
|
||||||
this.set_actor_direction(actor, direction);
|
{
|
||||||
if (this.attempt_step(actor, direction)) {
|
// We found a good direction! Stop here
|
||||||
moved = true;
|
actor.decision = direction;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Third pass: everyone actually moves
|
||||||
|
for (let actor of this.actors) {
|
||||||
|
if (! actor.cell)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (! actor.decision)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
this.set_actor_direction(actor, actor.decision);
|
||||||
|
this.attempt_step(actor, actor.decision);
|
||||||
|
|
||||||
// TODO do i need to do this more aggressively?
|
// TODO do i need to do this more aggressively?
|
||||||
if (this.state === 'success' || this.state === 'failure')
|
if (this.state === 'success' || this.state === 'failure')
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass time
|
// Strip out any destroyed actors from the acting order
|
||||||
|
let p = 0;
|
||||||
|
for (let i = 0, l = this.actors.length; i < l; i++) {
|
||||||
|
let actor = this.actors[i];
|
||||||
|
if (actor.cell) {
|
||||||
|
if (p !== i) {
|
||||||
|
this.actors[p] = actor;
|
||||||
|
}
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.actors.length = p;
|
||||||
|
|
||||||
|
// Advance the clock
|
||||||
let tic_counter = this.tic_counter;
|
let tic_counter = this.tic_counter;
|
||||||
let time_remaining = this.time_remaining;
|
let time_remaining = this.time_remaining;
|
||||||
this.tic_counter++;
|
this.tic_counter++;
|
||||||
@ -502,19 +571,6 @@ export class Level {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strip out any destroyed actors from the acting order
|
|
||||||
let p = 0;
|
|
||||||
for (let i = 0, l = this.actors.length; i < l; i++) {
|
|
||||||
let actor = this.actors[i];
|
|
||||||
if (actor.cell) {
|
|
||||||
if (p !== i) {
|
|
||||||
this.actors[p] = actor;
|
|
||||||
}
|
|
||||||
p++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.actors.length = p;
|
|
||||||
|
|
||||||
// Commit the undo state at the end of each tic
|
// Commit the undo state at the end of each tic
|
||||||
this.commit();
|
this.commit();
|
||||||
}
|
}
|
||||||
@ -535,22 +591,15 @@ export class Level {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let move = DIRECTIONS[direction].movement;
|
let move = DIRECTIONS[direction].movement;
|
||||||
let original_cell = actor.cell;
|
if (!actor.cell) console.error(actor);
|
||||||
if (!original_cell) console.error(actor);
|
let goal_cell = this.cell_with_offset(actor.cell, direction);
|
||||||
let goal_x = original_cell.x + move[0];
|
|
||||||
let goal_y = original_cell.y + move[1];
|
|
||||||
|
|
||||||
|
// TODO this could be a lot simpler if i could early-return! should ice bumping be
|
||||||
|
// somewhere else?
|
||||||
let blocked;
|
let blocked;
|
||||||
if (goal_x >= 0 && goal_x < this.width && goal_y >= 0 && goal_y < this.height) {
|
if (goal_cell) {
|
||||||
// Check for a thin wall in our current cell first
|
if (actor.cell.blocks_leaving(actor, direction)) {
|
||||||
for (let tile of original_cell) {
|
|
||||||
if (tile !== actor &&
|
|
||||||
! tile.type.is_swivel && tile.type.thin_walls &&
|
|
||||||
tile.type.thin_walls.has(direction))
|
|
||||||
{
|
|
||||||
blocked = true;
|
blocked = true;
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only bother touching the goal cell if we're not already trapped
|
// Only bother touching the goal cell if we're not already trapped
|
||||||
@ -561,7 +610,7 @@ export class Level {
|
|||||||
// mid-iteration.)
|
// mid-iteration.)
|
||||||
// FIXME actually, this prevents flicking!
|
// FIXME actually, this prevents flicking!
|
||||||
if (! blocked) {
|
if (! blocked) {
|
||||||
let goal_cell = this.cells[goal_y][goal_x];
|
// This is similar to Cell.blocks_entering, but we have to do a little more work
|
||||||
// FIXME splashes should block you (e.g. pushing a block off a
|
// FIXME splashes should block you (e.g. pushing a block off a
|
||||||
// turtle) but currently do not because of this copy; we don't
|
// turtle) but currently do not because of this copy; we don't
|
||||||
// notice a new thing was added to the tile :(
|
// notice a new thing was added to the tile :(
|
||||||
@ -604,7 +653,7 @@ export class Level {
|
|||||||
this.set_actor_direction(actor, DIRECTIONS[direction].opposite);
|
this.set_actor_direction(actor, DIRECTIONS[direction].opposite);
|
||||||
// Somewhat clumsy hack: step on the ice tile again, so if it's
|
// Somewhat clumsy hack: step on the ice tile again, so if it's
|
||||||
// a corner, it'll turn us in the correct direction
|
// a corner, it'll turn us in the correct direction
|
||||||
for (let tile of original_cell) {
|
for (let tile of actor.cell) {
|
||||||
if (tile.type.slide_mode === 'ice' && tile.type.on_arrive) {
|
if (tile.type.slide_mode === 'ice' && tile.type.on_arrive) {
|
||||||
tile.type.on_arrive(tile, this, actor);
|
tile.type.on_arrive(tile, this, actor);
|
||||||
}
|
}
|
||||||
@ -614,7 +663,7 @@ export class Level {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// We're clear!
|
// We're clear!
|
||||||
this.move_to(actor, goal_x, goal_y, 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_prop(actor, 'movement_cooldown', speed);
|
||||||
@ -624,16 +673,15 @@ export class Level {
|
|||||||
// Move the given actor to the given position and perform any appropriate
|
// Move the given actor to the given position and perform any appropriate
|
||||||
// tile interactions. Does NOT check for whether the move is actually
|
// tile interactions. Does NOT check for whether the move is actually
|
||||||
// legal; use attempt_step for that!
|
// legal; use attempt_step for that!
|
||||||
move_to(actor, x, y, speed) {
|
move_to(actor, goal_cell, speed) {
|
||||||
let original_cell = actor.cell;
|
if (actor.cell === goal_cell)
|
||||||
if (x === original_cell.x && y === original_cell.y)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
actor.previous_cell = actor.cell;
|
actor.previous_cell = actor.cell;
|
||||||
actor.animation_speed = speed;
|
actor.animation_speed = speed;
|
||||||
actor.animation_progress = 0;
|
actor.animation_progress = 0;
|
||||||
|
|
||||||
let goal_cell = this.cells[y][x];
|
let original_cell = actor.cell;
|
||||||
this.remove_tile(actor);
|
this.remove_tile(actor);
|
||||||
this.make_slide(actor, null);
|
this.make_slide(actor, null);
|
||||||
this.add_tile(actor, goal_cell);
|
this.add_tile(actor, goal_cell);
|
||||||
@ -731,6 +779,18 @@ export class Level {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cell_with_offset(cell, direction) {
|
||||||
|
let move = DIRECTIONS[direction].movement;
|
||||||
|
let goal_x = cell.x + move[0];
|
||||||
|
let goal_y = cell.y + move[1];
|
||||||
|
if (goal_x >= 0 && goal_x < this.width && goal_y >= 0 && goal_y < this.height) {
|
||||||
|
return this.cells[goal_y][goal_x];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Undo handling
|
// Undo handling
|
||||||
|
|
||||||
|
|||||||
@ -241,7 +241,7 @@ class Player extends PrimaryView {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
let [x, y] = this.renderer.cell_coords_from_event(ev);
|
let [x, y] = this.renderer.cell_coords_from_event(ev);
|
||||||
this.level.move_to(this.level.player, x, y);
|
this.level.move_to(this.level.player, this.level.cells[y][x], 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
let last_key;
|
let last_key;
|
||||||
|
|||||||
@ -169,8 +169,11 @@ const TILE_TYPES = {
|
|||||||
// Locked doors
|
// Locked doors
|
||||||
door_red: {
|
door_red: {
|
||||||
draw_layer: LAYER_TERRAIN,
|
draw_layer: LAYER_TERRAIN,
|
||||||
blocks_all: true,
|
blocks(me, other) {
|
||||||
on_bump(me, level, other) {
|
// TODO not quite sure if this one is right; there are complex interactions with monsters, e.g. most monsters can eat blue keys but can't actually use them
|
||||||
|
return ! (other.type.has_inventory && other.has_item('key_red'));
|
||||||
|
},
|
||||||
|
on_arrive(me, level, other) {
|
||||||
if (other.type.has_inventory && other.take_item('key_red')) {
|
if (other.type.has_inventory && other.take_item('key_red')) {
|
||||||
level.transmute_tile(me, 'floor');
|
level.transmute_tile(me, 'floor');
|
||||||
}
|
}
|
||||||
@ -178,8 +181,10 @@ const TILE_TYPES = {
|
|||||||
},
|
},
|
||||||
door_blue: {
|
door_blue: {
|
||||||
draw_layer: LAYER_TERRAIN,
|
draw_layer: LAYER_TERRAIN,
|
||||||
blocks_all: true,
|
blocks(me, other) {
|
||||||
on_bump(me, level, other) {
|
return ! (other.type.has_inventory && other.has_item('key_blue'));
|
||||||
|
},
|
||||||
|
on_arrive(me, level, other) {
|
||||||
if (other.type.has_inventory && other.take_item('key_blue')) {
|
if (other.type.has_inventory && other.take_item('key_blue')) {
|
||||||
level.transmute_tile(me, 'floor');
|
level.transmute_tile(me, 'floor');
|
||||||
}
|
}
|
||||||
@ -187,8 +192,10 @@ const TILE_TYPES = {
|
|||||||
},
|
},
|
||||||
door_yellow: {
|
door_yellow: {
|
||||||
draw_layer: LAYER_TERRAIN,
|
draw_layer: LAYER_TERRAIN,
|
||||||
blocks_all: true,
|
blocks(me, other) {
|
||||||
on_bump(me, level, other) {
|
return ! (other.type.has_inventory && other.has_item('key_yellow'));
|
||||||
|
},
|
||||||
|
on_arrive(me, level, other) {
|
||||||
if (other.type.has_inventory && other.take_item('key_yellow')) {
|
if (other.type.has_inventory && other.take_item('key_yellow')) {
|
||||||
level.transmute_tile(me, 'floor');
|
level.transmute_tile(me, 'floor');
|
||||||
}
|
}
|
||||||
@ -196,8 +203,10 @@ const TILE_TYPES = {
|
|||||||
},
|
},
|
||||||
door_green: {
|
door_green: {
|
||||||
draw_layer: LAYER_TERRAIN,
|
draw_layer: LAYER_TERRAIN,
|
||||||
blocks_all: true,
|
blocks(me, other) {
|
||||||
on_bump(me, level, other) {
|
return ! (other.type.has_inventory && other.has_item('key_green'));
|
||||||
|
},
|
||||||
|
on_arrive(me, level, other) {
|
||||||
if (other.type.has_inventory && other.take_item('key_green')) {
|
if (other.type.has_inventory && other.take_item('key_green')) {
|
||||||
level.transmute_tile(me, 'floor');
|
level.transmute_tile(me, 'floor');
|
||||||
}
|
}
|
||||||
@ -795,8 +804,12 @@ const TILE_TYPES = {
|
|||||||
},
|
},
|
||||||
socket: {
|
socket: {
|
||||||
draw_layer: LAYER_TERRAIN,
|
draw_layer: LAYER_TERRAIN,
|
||||||
blocks_all: true,
|
blocks_monsters: true,
|
||||||
on_bump(me, level, other) {
|
blocks_blocks: true,
|
||||||
|
blocks(me, other) {
|
||||||
|
return (level.chips_remaining > 0);
|
||||||
|
},
|
||||||
|
on_arrive(me, level, other) {
|
||||||
if (other.type.is_player && level.chips_remaining === 0) {
|
if (other.type.is_player && level.chips_remaining === 0) {
|
||||||
level.transmute_tile(me, 'floor');
|
level.transmute_tile(me, 'floor');
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user