code refactor part 1: advance_tic is now two parts

seems to work so far
This commit is contained in:
Timothy Stiles 2020-10-14 21:24:46 +11:00
parent c8d80dfc63
commit 2e1a87199a
2 changed files with 112 additions and 86 deletions

View File

@ -187,7 +187,6 @@ export class Level {
this.time_remaining = this.stored_level.time_limit * 20; this.time_remaining = this.stored_level.time_limit * 20;
} }
this.timer_paused = false this.timer_paused = false
this.waiting_for_input = false
// Note that this clock counts *up*, even on untimed levels, and is unaffected by CC2's // Note that this clock counts *up*, even on untimed levels, and is unaffected by CC2's
// clock alteration shenanigans // clock alteration shenanigans
this.tic_counter = 0; this.tic_counter = 0;
@ -331,15 +330,32 @@ export class Level {
} }
} }
player_awaiting_input() {
//todo: the tic_counter part feels kludgey. maybe there's some other way a wait/wall nudge can wait a certain amount of time per tap?
return this.player.movement_cooldown === 0 && this.player.slide_mode === null && this.tic_counter % 2 == 0
}
// Move the game state forwards by one tic // Move the game state forwards by one tic
advance_tic(p1_primary_direction, p1_secondary_direction, force_wait) { // split into two parts for turn-based mode: first part is the consequences of the previous tick, second part depends on the player's input
advance_tic(p1_primary_direction, p1_secondary_direction, pass) {
if (this.state !== 'playing') { if (this.state !== 'playing') {
console.warn(`Level.advance_tic() called when state is ${this.state}`); console.warn(`Level.advance_tic() called when state is ${this.state}`);
return; return;
} }
try { try {
this._advance_tic(p1_primary_direction, p1_secondary_direction, force_wait); if (pass == 1)
{
this._advance_tic_part1(p1_primary_direction, p1_secondary_direction);
}
else if (pass == 2)
{
this._advance_tic_part2(p1_primary_direction, p1_secondary_direction);
}
else
{
console.warn(`What pass is this?`);
}
} }
catch (e) { catch (e) {
if (e instanceof GameEnded) { if (e instanceof GameEnded) {
@ -350,26 +366,13 @@ export class Level {
} }
} }
// Commit the undo state at the end of each tic // Commit the undo state at the end of each tic (pass 2)
if (!this.waiting_for_input) { if (pass == 2) {
this.commit(); this.commit();
} }
} }
_advance_tic(p1_primary_direction, p1_secondary_direction, force_wait) { _advance_tic_part1(p1_primary_direction, p1_secondary_direction) {
var skip_to_third_pass = false;
//if we're waiting for input, then we want to skip straight to phase 3 with a player decision filled out when they have one ready
if (this.waiting_for_input) {
this.actor_decision(this.player, p1_primary_direction);
if (this.player.decision != null || force_wait) {
skip_to_third_pass = true;
}
else {
return;
}
}
// 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_prop(this.player, 'secondary_direction', p1_secondary_direction);
@ -387,71 +390,64 @@ export class Level {
// arrival as its own mini pass, for one reason: if the player dies (which will end the game // arrival as its own mini pass, for one reason: if the player dies (which will end the game
// immediately), we still want every time's animation to finish, or it'll look like some // immediately), we still want every time's animation to finish, or it'll look like some
// objects move backwards when the death screen appears! // objects move backwards when the death screen appears!
if (!skip_to_third_pass) { let cell_steppers = [];
let cell_steppers = []; 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) continue;
continue;
// Clear any old decisions ASAP. Note that this prop is only used internally within a // Clear any old decisions ASAP. Note that this prop is only used internally within a
// single tic, so it doesn't need to be undoable // single tic, so it doesn't need to be undoable
actor.decision = null; actor.decision = null;
// 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_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_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_prop(actor, 'previous_cell', null);
this._set_prop(actor, 'animation_progress', null); this._set_prop(actor, 'animation_progress', null);
this._set_prop(actor, 'animation_speed', null); this._set_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
// then they'd end up stepping on the new cell they're moving to instead of // then they'd end up stepping on the new cell they're moving to instead of
// the one they just landed on! // the one they just landed on!
cell_steppers.push([actor, actor.cell]); cell_steppers.push([actor, actor.cell]);
} }
} }
} }
} }
for (let [actor, cell] of cell_steppers) { for (let [actor, cell] of cell_steppers) {
this.step_on_cell(actor, cell); this.step_on_cell(actor, cell);
} }
// Only reset the player's is_pushing between movement, so it lasts for the whole push // Only reset the player's is_pushing between movement, so it lasts for the whole push
if (this.player.movement_cooldown <= 0) { if (this.player.movement_cooldown <= 0) {
this.player.is_pushing = false; this.player.is_pushing = false;
} }
// Second pass: actors decide their upcoming movement simultaneously
for (let actor of this.actors) {
this.actor_decision(actor, p1_primary_direction);
}
}
//in Turn-Based mode, wait for input if the player can voluntarily move on tic_counter % 4 == 0 and isn't
if (this.turn_based && this.player.movement_cooldown == 0 && this.player.decision == null && (this.tic_counter % 4 == 0) && !force_wait)
{
this.waiting_for_input = true;
return;
}
else
{
this.waiting_for_input = false;
}
// Second pass: actors decide their upcoming movement simultaneously
for (let actor of this.actors) {
this.actor_decision(actor, p1_primary_direction);
}
}
_advance_tic_part2(p1_primary_direction, p1_secondary_direction) {
//player now makes a decision based on input
this.actor_decision(this.player, p1_primary_direction);
// Third pass: everyone actually moves // Third pass: everyone actually moves
for (let actor of this.actors) { for (let actor of this.actors) {
if (! actor.cell) if (! actor.cell)
@ -981,8 +977,6 @@ export class Level {
} }
undo() { undo() {
//ok, actually we're not doing an in-progress move after all.
this.waiting_for_input = false;
this.pending_undo = []; this.pending_undo = [];
this.aid = Math.max(1, this.aid); this.aid = Math.max(1, this.aid);

View File

@ -702,6 +702,8 @@ class Player extends PrimaryView {
return input; return input;
} }
} }
waiting_for_input = false;
advance_by(tics) { advance_by(tics) {
for (let i = 0; i < tics; i++) { for (let i = 0; i < tics; i++) {
@ -765,11 +767,41 @@ class Player extends PrimaryView {
this.previous_input = input; this.previous_input = input;
this.sfx_player.advance_tic(); this.sfx_player.advance_tic();
this.level.advance_tic(
this.primary_action ? ACTION_DIRECTIONS[this.primary_action] : null, var primary_dir = this.primary_action ? ACTION_DIRECTIONS[this.primary_action] : null;
this.secondary_action ? ACTION_DIRECTIONS[this.secondary_action] : null, var secondary_dir = this.secondary_action ? ACTION_DIRECTIONS[this.secondary_action] : null;
input.has('wait')
); //turn based logic
//first, handle a part 2 we just got input for
if (this.waiting_for_input)
{
if (!this.turn_based || primary_dir != null || input.has('wait'))
{
this.level.advance_tic(
primary_dir,
secondary_dir,
2);
}
}
else //TODO: or `if (!this.waiting_for_input)` to be snappier
{
this.level.advance_tic(
primary_dir,
secondary_dir,
1);
//then if we should wait for input, the player needs input and we don't have input, we set waiting_for_input, else we run part 2
if (this.turn_based && this.level.player_awaiting_input() && !(primary_dir != null || input.has('wait')))
{
this.waiting_for_input = true;
}
else
{
this.level.advance_tic(
primary_dir,
secondary_dir,
2);
}
}
if (this.level.state !== 'playing') { if (this.level.state !== 'playing') {
// We either won or lost! // We either won or lost!