Add basic support for drop/cycle/swap

This commit is contained in:
Eevee (Evelyn Woods) committed 2020-12-02 17:18:54 -07:00
1 parent 8d197ce479
commit e51665b612
5 files changed
+209 -64

No files matched your search

+99 -44
View File
@@ -451,7 +451,7 @@ export class Level {
// For turn-based mode, this is split into two parts: advance_tic_finish_movement completes any
// ongoing movement started in the previous tic, and advance_tic_act allows actors to make new
// decisions. The player makes decisions between these two parts.
advance_tic(p1_primary_direction, p1_secondary_direction, pass) {
advance_tic(p1_actions, pass) {
if (this.state !== 'playing') {
console.warn(`Level.advance_tic() called when state is ${this.state}`);
return;
@@ -460,10 +460,10 @@ export class Level {
// TODO rip out this try/catch, it's not how the game actually works
try {
if (pass == 1) {
this.advance_tic_finish_movement(p1_primary_direction, p1_secondary_direction);
this.advance_tic_finish_movement(p1_actions);
}
else if (pass == 2) {
this.advance_tic_act(p1_primary_direction, p1_secondary_direction);
this.advance_tic_act(p1_actions);
}
else {
console.warn(`What pass is this?`);
@@ -484,7 +484,7 @@ export class Level {
}
}
advance_tic_finish_movement(p1_primary_direction, p1_secondary_direction) {
advance_tic_finish_movement(p1_actions) {
// Store some current level state in the undo entry. (These will often not be modified, but
// they only take a few bytes each so that's fine.)
for (let key of [
@@ -498,7 +498,12 @@ export class Level {
// 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_tile_prop(this.player, 'secondary_direction', p1_secondary_direction);
if (p1_actions.secondary === this.player.direction) {
this._set_tile_prop(this.player, 'secondary_direction', p1_actions.primary);
}
else {
this._set_tile_prop(this.player, 'secondary_direction', p1_actions.secondary);
}
// Used to check for a monster chomping the player's tail
this.player_leaving_cell = this.player.cell;
@@ -570,7 +575,7 @@ export class Level {
}
}
advance_tic_act(p1_primary_direction, p1_secondary_direction) {
advance_tic_act(p1_actions) {
// Second pass: actors decide their upcoming movement simultaneously
for (let i = this.actors.length - 1; i >= 0; i--) {
let actor = this.actors[i];
@@ -607,43 +612,36 @@ export class Level {
actor.decision = actor.direction;
continue;
}
else if (actor.slide_mode === 'force') {
// Only the player can make voluntary moves on a force floor,
// and only if their previous move was an /involuntary/ move on
// a force floor. If they do, it overrides the forced move
// XXX this in particular has some subtleties in lynx (e.g. you
// can override forwards??) and DEFINITELY all kinds of stuff
// in ms
// XXX unclear what impact this has on doppelgangers
if (actor === this.player &&
p1_primary_direction &&
actor.last_move_was_force)
{
actor.decision = p1_primary_direction;
this._set_tile_prop(actor, 'last_move_was_force', false);
}
else {
actor.decision = actor.direction;
if (actor === this.player) {
this._set_tile_prop(actor, 'last_move_was_force', true);
}
}
continue;
}
else if (actor === this.player) {
// Sorry for the confusion; "p1" and "p2" in the direction args refer to physical
// human players, NOT to the two types of player tiles!
if (this.player.type.name === 'player') {
this.player1_move = p1_primary_direction;
}
else {
this.player2_move = p1_primary_direction;
// Only the player can make voluntary moves on a force floor, and only if their
// previous move was an /involuntary/ move on a force floor. If they do, it
// overrides the forced move
// XXX this in particular has some subtleties in lynx (e.g. you can override
// forwards??) and DEFINITELY all kinds of stuff in ms
// XXX unclear what impact this has on doppelgangers
if (actor.slide_mode === 'force' && ! (
p1_actions.primary && actor.last_move_was_force))
{
// We're forced!
actor.decision = actor.direction;
this._set_tile_prop(actor, 'last_move_was_force', true);
continue;
}
if (p1_primary_direction) {
actor.decision = p1_primary_direction;
if (p1_actions.primary) {
direction_preference = [p1_actions.primary];
if (p1_actions.secondary) {
direction_preference.push(p1_actions.secondary);
}
this._set_tile_prop(actor, 'last_move_was_force', false);
}
else {
continue;
}
}
else if (actor.slide_mode === 'force') {
// Anything not an active player can't override force floors
actor.decision = actor.direction;
continue;
}
else if (actor.cell.some(tile => tile.type.traps && tile.type.traps(tile, actor))) {
@@ -753,7 +751,7 @@ export class Level {
// TODO i think player on force floor will still have some issues here
if (direction_preference) {
let fallback_direction;
for (let direction of direction_preference) {
for (let [i, direction] of direction_preference.entries()) {
if (direction === 'WALKER') {
// Walkers roll a random direction ONLY if their first attempt was blocked
direction = actor.direction;
@@ -766,6 +764,13 @@ export class Level {
direction = actor.cell.redirect_exit(actor, direction);
// If every other preference be blocked, actors unconditionally try the last one
// (and might even be able to move that way by the time their turn comes!)
if (i === direction_preference.length - 1) {
actor.decision = direction;
break;
}
let dest_cell = this.get_neighboring_cell(actor.cell, direction);
if (! dest_cell)
continue;
@@ -778,16 +783,23 @@ export class Level {
break;
}
}
}
// If all the decisions are blocked, actors still try the last one (and might even
// be able to move that way by the time their turn comes around!)
if (actor.decision === null) {
actor.decision = fallback_direction;
// Do some cleanup for the player
if (actor === this.player) {
// Sorry for the confusion; "p1" and "p2" in the direction args refer to physical
// human players, NOT to the two types of player tiles!
if (this.player.type.name === 'player') {
this.player1_move = actor.decision;
}
else {
this.player2_move = actor.decision;
}
}
}
// Third pass: everyone actually moves
let swap_player1 = false;
for (let i = this.actors.length - 1; i >= 0; i--) {
let actor = this.actors[i];
if (! actor.cell)
@@ -798,6 +810,19 @@ export class Level {
if (actor.movement_cooldown > 0)
continue;
// Check for special player actions
if (actor === this.player) {
if (p1_actions.cycle) {
this.cycle_inventory(this.player);
}
if (p1_actions.drop) {
this.drop_item(this.player);
}
if (p1_actions.swap) {
swap_player1 = true;
}
}
if (! actor.decision)
continue;
@@ -805,7 +830,7 @@ export class Level {
let success = this.attempt_step(actor, actor.decision);
// Track whether the player is blocked, for visual effect
if (actor === this.player && p1_primary_direction && ! success) {
if (actor === this.player && actor.decision && ! success) {
this.sfx.play_once('blocked');
actor.is_blocked = true;
}
@@ -862,6 +887,13 @@ export class Level {
}
this.actors.length = p;
// Possibly switch players
if (swap_player1) {
this.player_index += 1;
this.player_index %= this.players.length;
this.player = this.players[this.player_index];
}
// Advance the clock
let tic_counter = this.tic_counter;
this.tic_counter += 1;
@@ -1154,6 +1186,29 @@ export class Level {
}
}
cycle_inventory(actor) {
if (actor.movement_cooldown > 0)
return;
// Cycle leftwards, i.e., the oldest item moves to the end of the list
if (actor.toolbelt) {
actor.toolbelt.push(actor.toolbelt.shift());
this.pending_undo.push(() => actor.toolbelt.unshift(actor.toolbelt.pop()));
}
}
drop_item(actor) {
if (actor.movement_cooldown > 0)
return;
// Drop the oldest item, i.e. the first one
if (actor.toolbelt && ! actor.cell.get_item()) {
let name = actor.toolbelt.shift();
this.pending_undo.push(() => actor.toolbelt.unshift(name));
this.add_tile(new Tile(TILE_TYPES[name]), actor.cell);
}
}
// Update the state of all wired tiles in the game.
// XXX need to be clear on the order of events here. say everything starts out unpowered.
// then:
+37 -8
View File
@@ -375,6 +375,20 @@ class Player extends PrimaryView {
this.set_state('rewinding');
}
});
// Game actions
// TODO do these need buttons?? feel like they're not discoverable otherwise
this.drop_button = this.root.querySelector('.actions .action-drop');
this.drop_button.addEventListener('click', ev => {
this.current_keys.add('q');
});
this.cycle_button = this.root.querySelector('.actions .action-cycle');
this.cycle_button.addEventListener('click', ev => {
this.current_keys.add('e');
});
this.swap_button = this.root.querySelector('.actions .action-swap');
this.swap_button.addEventListener('click', ev => {
this.current_keys.add('c');
});
// Demo playback
this.root.querySelector('.demo-controls .demo-play').addEventListener('click', ev => {
if (this.state === 'playing' || this.state === 'paused' || this.state === 'rewinding') {
@@ -386,6 +400,8 @@ class Player extends PrimaryView {
this.play_demo();
}
});
// FIXME consolidate these into debug controls
/*
this.root.querySelector('.demo-controls .demo-step-1').addEventListener('click', ev => {
this.advance_by(1);
this._redraw();
@@ -394,6 +410,7 @@ class Player extends PrimaryView {
this.advance_by(4);
this._redraw();
});
*/
this.renderer = new CanvasRenderer(this.conductor.tileset);
this.level_el.append(this.renderer.canvas);
@@ -753,6 +770,9 @@ class Player extends PrimaryView {
// newly pressed secondary action; remember, there can't be two opposing keys held,
// because we already checked for that above, so this is only necessary if there's
// not already a secondary action
if (this.secondary_action && ! input.has(this.secondary_action)) {
this.secondary_action = null;
}
if (! this.secondary_action) {
for (let action of ['down', 'left', 'right', 'up']) {
if (action !== this.primary_action &&
@@ -795,27 +815,32 @@ class Player extends PrimaryView {
}
}
let player_actions = {
primary: this.primary_action ? ACTION_DIRECTIONS[this.primary_action] : null,
secondary: this.secondary_action ? ACTION_DIRECTIONS[this.secondary_action] : null,
cycle: input.has('cycle') && ! this.previous_input.has('cycle'),
drop: input.has('drop') && ! this.previous_input.has('drop'),
swap: input.has('swap') && ! this.previous_input.has('swap'),
}
this.previous_input = input;
this.sfx_player.advance_tic();
let primary_dir = this.primary_action ? ACTION_DIRECTIONS[this.primary_action] : null;
let secondary_dir = this.secondary_action ? ACTION_DIRECTIONS[this.secondary_action] : null;
// Turn-based mode is considered assistance, but only if the game actually attempts to
// progress while it's enabled
if (this.turn_mode > 0) {
this.level.aid = Math.max(1, this.level.aid);
}
let has_input = primary_dir !== null || input.has('wait');
let has_input = Object.values(player_actions).some(x => x);
// Turn-based mode complicates this slightly; it aligns us to the middle of a tic
if (this.turn_mode === 2) {
if (has_input) {
// Finish the current tic, then continue as usual. This means the end of the
// tic doesn't count against the number of tics to advance -- because it already
// did, the first time we tried it
this.level.advance_tic(primary_dir, secondary_dir, 2);
this.level.advance_tic(player_actions, 2);
this.turn_mode = 1;
}
else {
@@ -824,14 +849,14 @@ class Player extends PrimaryView {
}
// We should now be at the start of a tic
this.level.advance_tic(primary_dir, secondary_dir, 1);
this.level.advance_tic(player_actions, 1);
if (this.turn_mode > 0 && this.level.can_accept_input() && ! has_input) {
// If we're in turn-based mode and could provide input here, but don't have any,
// then wait until we do
this.turn_mode = 2;
}
else {
this.level.advance_tic(primary_dir, secondary_dir, 2);
this.level.advance_tic(player_actions, 2);
}
if (this.level.state !== 'playing') {
@@ -931,11 +956,15 @@ class Player extends PrimaryView {
}
update_ui() {
this.pause_button.disabled = !(this.state === 'playing' || this.state === 'paused' || this.state === 'rewinding');
this.pause_button.disabled = ! (this.state === 'playing' || this.state === 'paused' || this.state === 'rewinding');
this.restart_button.disabled = (this.state === 'waiting');
this.undo_button.disabled = ! this.level.has_undo();
this.rewind_button.disabled = ! (this.level.has_undo() || this.state === 'rewinding');
this.drop_button.disabled = ! (this.state === 'playing' && this.level.player.toolbelt && this.level.player.toolbelt.length > 0);
this.cycle_button.disabled = ! (this.state === 'playing' && this.level.player.toolbelt && this.level.player.toolbelt.length > 1);
this.swap_button.disabled = ! (this.state === 'playing' && this.level.players.length > 1);
// TODO can we do this only if they actually changed?
this.chips_el.textContent = this.level.chips_remaining;
if (this.level.chips_remaining === 0) {
+3
View File
@@ -1718,6 +1718,8 @@ const TILE_TYPES = {
is_player: true,
is_real_player: true,
collision_mask: COLLISION.player1,
// FIXME does this make us block the doppelgangers??
blocks_collision: COLLISION.player,
has_inventory: true,
can_reveal_walls: true,
movement_speed: 4,
@@ -1737,6 +1739,7 @@ const TILE_TYPES = {
is_player: true,
is_real_player: true,
collision_mask: COLLISION.player2,
blocks_collision: COLLISION.players,
has_inventory: true,
can_reveal_walls: true,
movement_speed: 4,