Restore manually-saved doppelganger behavior, to make it work with undo

This commit is contained in:
Eevee (Evelyn Woods) 2021-01-25 12:29:18 -07:00
parent 0b6ea68a7b
commit 884d6d9164
2 changed files with 62 additions and 43 deletions

View File

@ -504,6 +504,9 @@ export class Level extends LevelInterface {
} }
} }
// TODO complain if no player // TODO complain if no player
// Used for doppelgängers
this.player1_move = null;
this.player2_move = null;
// Connect buttons and teleporters // Connect buttons and teleporters
let num_cells = this.width * this.height; let num_cells = this.width * this.height;
@ -783,7 +786,7 @@ export class Level extends LevelInterface {
'_rng1', '_rng2', '_blob_modifier', 'force_floor_direction', '_rng1', '_rng2', '_blob_modifier', 'force_floor_direction',
'tic_counter', 'time_remaining', 'timer_paused', 'tic_counter', 'time_remaining', 'timer_paused',
'chips_remaining', 'bonus_points', 'state', 'chips_remaining', 'bonus_points', 'state',
'remaining_players', 'player', 'player1_move', 'player2_move', 'remaining_players', 'player',
]) { ]) {
this.pending_undo.level_props[key] = this[key]; this.pending_undo.level_props[key] = this[key];
} }
@ -865,6 +868,8 @@ export class Level extends LevelInterface {
} }
} }
this._swap_players();
this._do_wire_phase(); this._do_wire_phase();
// TODO should this also happen three times? // TODO should this also happen three times?
this._do_static_phase(); this._do_static_phase();
@ -923,6 +928,18 @@ export class Level extends LevelInterface {
// Decision phase: all actors decide on their movement "simultaneously" // Decision phase: all actors decide on their movement "simultaneously"
_do_decision_phase(forced_only = false) { _do_decision_phase(forced_only = false) {
// Before decisions happen, remember the player's /current/ direction, which may be affected
// by sliding. This will be used by doppelgängers earlier in actor order than the player.
if (! forced_only) {
// Check whether the player is /attempting/ to move: either they did, or they're blocked
if (this.player.movement_cooldown > 0 || this.player.is_blocked) {
this.remember_player_move(this.player.direction);
}
else {
this.remember_player_move(null);
}
}
for (let i = this.actors.length - 1; i >= 0; i--) { for (let i = this.actors.length - 1; i >= 0; i--) {
let actor = this.actors[i]; let actor = this.actors[i];
@ -987,6 +1004,8 @@ export class Level extends LevelInterface {
this.attempt_teleport(actor); this.attempt_teleport(actor);
} }
} }
this._swap_players();
} }
// Have an actor attempt to move // Have an actor attempt to move
@ -1079,34 +1098,23 @@ export class Level extends LevelInterface {
} }
} }
_do_cleanup_phase() { _swap_players() {
// Strip out any destroyed actors from the acting order if (this.remaining_players <= 0) {
// FIXME this is O(n), where n is /usually/ small, but i still don't love it. not strictly this.win();
// necessary, either; maybe only do it every few tics?
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++;
}
else {
let local_p = p;
this._push_pending_undo(() => this.actors.splice(local_p, 0, actor));
}
}
this.actors.length = p;
// Possibly switch players // Possibly switch players
// FIXME cc2 has very poor interactions between this feature and cloners; come up with some // FIXME cc2 has very poor interactions between this feature and cloners; come up with some
// better rules as a default // better rules as a default
if (this.swap_player1) { if (this.swap_player1) {
this.swap_player1 = false;
// Reset the set of keys released since last tic (but not the swap key, or holding it // Reset the set of keys released since last tic (but not the swap key, or holding it
// will swap us endlessly) // will swap us endlessly)
// FIXME this doesn't even quite work, it just swaps less aggressively? wtf // FIXME this doesn't even quite work, it just swaps less aggressively? wtf
this.p1_released = 0xff & ~INPUT_BITS.swap; this.p1_released = 0xff & ~INPUT_BITS.swap;
// Clear remembered moves
this.player1_move = null;
this.player2_move = null;
// Iterate backwards over the actor list looking for a viable next player to control // Iterate backwards over the actor list looking for a viable next player to control
let i0 = this.actors.indexOf(this.player); let i0 = this.actors.indexOf(this.player);
@ -1132,11 +1140,28 @@ export class Level extends LevelInterface {
} }
} }
} }
if (this.remaining_players <= 0) {
this.win();
} }
_do_cleanup_phase() {
// Strip out any destroyed actors from the acting order
// FIXME this is O(n), where n is /usually/ small, but i still don't love it. not strictly
// necessary, either; maybe only do it every few tics?
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++;
}
else {
let local_p = p;
this._push_pending_undo(() => this.actors.splice(local_p, 0, actor));
}
}
this.actors.length = p;
// Advance the clock // Advance the clock
// TODO i suspect cc2 does this at the beginning of the tic, but even if you've won? if you // TODO i suspect cc2 does this at the beginning of the tic, but even if you've won? if you
// step on a penalty + exit you win, but you see the clock flicker 1 for a single frame. // step on a penalty + exit you win, but you see the clock flicker 1 for a single frame.
@ -1331,6 +1356,9 @@ export class Level extends LevelInterface {
this._set_tile_prop(actor, 'last_move_was_force', false); this._set_tile_prop(actor, 'last_move_was_force', false);
} }
} }
// Remember our decision so doppelgängers can copy it
this.remember_player_move(actor.decision);
} }
make_actor_decision(actor, forced_only = false) { make_actor_decision(actor, forced_only = false) {
@ -1767,6 +1795,17 @@ export class Level extends LevelInterface {
this.add_tile(actor, dest.cell); this.add_tile(actor, dest.cell);
} }
remember_player_move(direction) {
if (this.player.type.name === 'player') {
this.player1_move = direction;
this.player2_move = null;
}
else {
this.player1_move = null;
this.player2_move = direction;
}
}
cycle_inventory(actor) { cycle_inventory(actor) {
if (this.stored_level.use_cc1_boots) if (this.stored_level.use_cc1_boots)
return; return;

View File

@ -2569,17 +2569,7 @@ const TILE_TYPES = {
key_green: true, key_green: true,
}, },
decide_movement(me, level) { decide_movement(me, level) {
if (level.player.type.name === 'player') { return level.player1_move ? [level.player1_move] : null;
if (level.player.movement_cooldown || level.player.is_blocked) {
return [level.player.direction];
}
else {
return [level.player.decision];
}
}
else {
return null;
}
}, },
//visual_state: doppelganger_visual_state, //visual_state: doppelganger_visual_state,
}, },
@ -2604,17 +2594,7 @@ const TILE_TYPES = {
key_yellow: true, key_yellow: true,
}, },
decide_movement(me, level) { decide_movement(me, level) {
if (level.player.type.name === 'player2') { return level.player2_move ? [level.player2_move] : null;
if (level.player.movement_cooldown || level.player.is_blocked) {
return [level.player.direction];
}
else {
return [level.player.decision];
}
}
else {
return null;
}
}, },
//visual_state: doppelganger_visual_state, //visual_state: doppelganger_visual_state,
}, },