Abort the tic and draw a final frame after the game ends; fix dead player appearance

This commit is contained in:
Eevee (Evelyn Woods) 2020-09-16 18:14:06 -06:00
parent 1d809601ae
commit fe12c599bc
4 changed files with 41 additions and 31 deletions

View File

@ -163,6 +163,8 @@ export class Cell extends Array {
}
}
class GameEnded extends Error {}
export class Level {
constructor(stored_level, compat = {}) {
this.stored_level = stored_level;
@ -345,6 +347,23 @@ export class Level {
return;
}
try {
this._advance_tic(p1_primary_direction, p1_secondary_direction);
}
catch (e) {
if (e instanceof GameEnded) {
// Do nothing, the game ended and we just wanted to skip the rest
}
else {
throw e;
}
}
// Commit the undo state at the end of each tic
this.commit();
}
_advance_tic(p1_primary_direction, p1_secondary_direction) {
// 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
this._set_prop(this.player, 'secondary_direction', p1_secondary_direction);
@ -352,8 +371,6 @@ export class Level {
// Used to check for a monster chomping the player's tail
this.player_leaving_cell = this.player.cell;
// 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
// First pass: tick cooldowns and animations; have actors arrive in their cells
for (let actor of this.actors) {
// Actors with no cell were destroyed
@ -554,10 +571,6 @@ export class Level {
let old_cell = actor.cell;
this.attempt_step(actor, actor.decision);
// TODO do i need to do this more aggressively?
if (this.state === 'success' || this.state === 'failure')
break;
// Players can also bump the tiles in the cell next to the one they're leaving
let dir2 = actor.secondary_direction;
if (actor.type.is_player && dir2 &&
@ -617,9 +630,6 @@ export class Level {
this.tic_counter = tic_counter;
});
}
// 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
@ -933,11 +943,13 @@ export class Level {
});
this.state = 'failure';
this.fail_message = message;
throw new GameEnded;
}
win() {
this.pending_undo.push(() => this.state = 'playing');
this.state = 'success';
throw new GameEnded;
}
// Get the next direction a random force floor will use. They share global

View File

@ -501,13 +501,6 @@ class Player extends PrimaryView {
// Redraws every frame, unless the game isn't running
redraw() {
// FIXME draw one more frame after losing, so we can see the player explode or whatever
// TODO for bonus points, also finish the player animation (but don't advance the game any further)
if (this.state !== 'playing' && this.state !== 'rewinding') {
this._redraw_handle = null;
return;
}
// Calculate this here, not in _redraw, because that's called at weird
// times when the game might not have actually advanced at all yet
// TODO this is not gonna be right while pausing lol
@ -516,7 +509,16 @@ class Player extends PrimaryView {
this.tic_offset = Math.min(0.9999, (performance.now() - this.last_advance) / 1000 / (1 / TICS_PER_SECOND));
this._redraw();
this._redraw_handle = requestAnimationFrame(this._redraw_bound);
// Check for a stopped game *after* drawing, so that if the game ends, we still draw its
// final result before stopping the draw loop
// TODO for bonus points, also finish the player animation (but don't advance the game any further)
if (this.state === 'playing' || this.state === 'rewinding') {
this._redraw_handle = requestAnimationFrame(this._redraw_bound);
}
else {
this._redraw_handle = null;
}
}
// Actually redraw. Used to force drawing outside of normal play

View File

@ -100,11 +100,10 @@ export class CanvasRenderer {
let y1 = Math.min(this.level.size_y - 1, Math.ceil(y0 + this.viewport_size_y));
// Draw one layer at a time, so animated objects aren't overdrawn by
// neighboring terrain
let x, y;
// XXX layer count hardcoded here
for (let layer = 0; layer < 4; layer++) {
for (x = xf0; x <= x1; x++) {
for (y = yf0; y <= y1; y++) {
for (let x = xf0; x <= x1; x++) {
for (let y = yf0; y <= y1; y++) {
for (let tile of this.level.cells[y][x]) {
if (tile.type.draw_layer !== layer)
continue;

View File

@ -290,8 +290,8 @@ const TILE_TYPES = {
level.transmute_tile(me, 'water');
}
else if (other.type.is_player) {
level.fail("Oops! You can't walk on fire without fire boots!");
level.transmute_tile(other, 'player_burned');
level.fail("Oops! You can't walk on fire without fire boots!");
}
else {
level.remove_tile(other);
@ -311,8 +311,8 @@ const TILE_TYPES = {
level.transmute_tile(me, 'ice');
}
else if (other.type.is_player) {
level.transmute_tile(other, 'splash');
level.fail("swimming with the fishes");
level.transmute_tile(other, 'player_drowned');
}
else {
level.transmute_tile(other, 'splash');
@ -431,15 +431,13 @@ const TILE_TYPES = {
},
bomb: {
draw_layer: LAYER_ITEM,
// TODO explode
on_arrive(me, level, other) {
let cell = me.cell;
level.remove_tile(me);
if (other.type.is_player) {
// Check this /before/ we change it...
let was_player = other.type.is_player;
level.transmute_tile(other, 'explosion');
if (was_player) {
level.fail("watch where you step");
}
level.transmute_tile(other, 'explosion');
},
},
thief_tools: {
@ -553,13 +551,12 @@ const TILE_TYPES = {
draw_layer: LAYER_ITEM,
is_required_chip: true,
on_arrive(me, level, other) {
// TODO explode
level.remove_tile(me);
if (other.type.is_player) {
// Check this /before/ we change it...
let was_player = other.type.is_player;
level.transmute_tile(other, 'explosion');
if (was_player) {
level.fail("watch where you step");
}
level.transmute_tile(other, 'explosion');
},
},
purple_floor: {