Add basic support for drop/cycle/swap
This commit is contained in:
parent
8d197ce479
commit
e51665b612
28
index.html
28
index.html
@ -123,7 +123,7 @@
|
|||||||
<div class="play-controls">
|
<div class="play-controls">
|
||||||
<button class="control-pause" type="button">
|
<button class="control-pause" type="button">
|
||||||
<svg class="svg-icon" viewBox="0 0 16 16" title="pause"><path d="M2,1 h4 v14 h-4 z M10,1 h4 v14 h-4 z"></svg>
|
<svg class="svg-icon" viewBox="0 0 16 16" title="pause"><path d="M2,1 h4 v14 h-4 z M10,1 h4 v14 h-4 z"></svg>
|
||||||
<span class="keyhint">(p)</span></button>
|
<span class="keyhint">p</span></button>
|
||||||
<button class="control-restart" type="button">
|
<button class="control-restart" type="button">
|
||||||
<svg class="svg-icon" viewBox="0 0 16 16" title="restart"><path d="M13,13 A 7,7 270 1,1 13,3 L15,1 15,7 9,7 11,5 A 4,4 270 1,0 11,11 z"></svg>
|
<svg class="svg-icon" viewBox="0 0 16 16" title="restart"><path d="M13,13 A 7,7 270 1,1 13,3 L15,1 15,7 9,7 11,5 A 4,4 270 1,0 11,11 z"></svg>
|
||||||
</button>
|
</button>
|
||||||
@ -132,19 +132,36 @@
|
|||||||
</button>
|
</button>
|
||||||
<button class="control-rewind" type="button">
|
<button class="control-rewind" type="button">
|
||||||
<svg class="svg-icon" viewBox="0 0 16 16" title="rewind"><path d="M1,8 7,2 7,14 z M9,8 15,2 15,14 z"></svg>
|
<svg class="svg-icon" viewBox="0 0 16 16" title="rewind"><path d="M1,8 7,2 7,14 z M9,8 15,2 15,14 z"></svg>
|
||||||
<span class="keyhint">(z)</span></button>
|
<span class="keyhint">z</span></button>
|
||||||
<label><input class="control-turn-based" type="checkbox"> Turn-based mode</label>
|
<label><input class="control-turn-based" type="checkbox"> Turn-based mode</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="actions">
|
||||||
|
<button class="action-drop" type="button">
|
||||||
|
<svg class="svg-icon" viewBox="0 0 16 16" title="pause"><path d="M6,0 h4 v9 h3 l-5,5 h7 v2 h-14 v-2 h7 l-5,-5 h3"></svg>
|
||||||
|
drop <span class="keyhint">q</span></button>
|
||||||
|
<button class="action-cycle" type="button">
|
||||||
|
<svg class="svg-icon" viewBox="0 0 16 16" title="pause">
|
||||||
|
<path d="M2,3 H11 V1 l4,4 -4,4 V7 H2 Z"></path>
|
||||||
|
<path d="M14,9 H5 V7 l-4,4 4,4 v-2 h9 z"></path>
|
||||||
|
</svg>
|
||||||
|
cycle <span class="keyhint">e</span></button>
|
||||||
|
<button class="action-swap" type="button">
|
||||||
|
<svg class="svg-icon" viewBox="0 0 16 16" title="pause">
|
||||||
|
<path d="m 7,1 h 2 l 1,1 V 6 L 9,7 v 4 L 8,11.5 7,11 V 7 L 6,6 V 2 Z"></path>
|
||||||
|
<path d="M 8,13 13,11 8,9 3,11 Z m 0,2 7,-3 V 11 L 8,8 1,11 v 1 z"></path>
|
||||||
|
<ellipse cx="5.5" cy="11" rx="0.75" ry="0.5"></ellipse>
|
||||||
|
</svg>
|
||||||
|
switch <span class="keyhint">c</span></button>
|
||||||
|
</div>
|
||||||
<div class="demo-controls">
|
<div class="demo-controls">
|
||||||
<button class="demo-play" type="button">View replay</button>
|
<button class="demo-play" type="button">View replay</button>
|
||||||
<button class="demo-step-1" type="button">Step 1 tic</button>
|
|
||||||
<button class="demo-step-4" type="button">Step 1 move</button>
|
|
||||||
<div class="input"></div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="player-debug">
|
<div id="player-debug">
|
||||||
<p>Play time: A tics, B moves, C seconds</p>
|
<p>Play time: A tics, B moves, C seconds</p>
|
||||||
<p>
|
<p>
|
||||||
|
<button class="demo-step-1" type="button">Step 1 tic</button>
|
||||||
|
<button class="demo-step-4" type="button">Step 1 move</button>
|
||||||
<button>« 4 tics</button>
|
<button>« 4 tics</button>
|
||||||
<button>« 1 tic</button>
|
<button>« 1 tic</button>
|
||||||
<button>1 tic »</button>
|
<button>1 tic »</button>
|
||||||
@ -152,6 +169,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<p>Speed: 6× (0.5 frame), 3× (1 frame), 2× (???), 1.5× (2 frames), 1× (3 frames), ½× (6 frames), ⅓× (9 frames), ¼× (12 frames)</p>
|
<p>Speed: 6× (0.5 frame), 3× (1 frame), 2× (???), 1.5× (2 frames), 1× (3 frames), ½× (6 frames), ⅓× (9 frames), ¼× (12 frames)</p>
|
||||||
<p>Viewport: 9/10, 12, 16, 24, 32, map size</p>
|
<p>Viewport: 9/10, 12, 16, 24, 32, map size</p>
|
||||||
|
<div class="input"></div>
|
||||||
|
|
||||||
<p><input type="checkbox"> Show actor info</p>
|
<p><input type="checkbox"> Show actor info</p>
|
||||||
<p><input type="checkbox"> Show actor bounding boxes</p>
|
<p><input type="checkbox"> Show actor bounding boxes</p>
|
||||||
|
|||||||
135
js/game.js
135
js/game.js
@ -451,7 +451,7 @@ export class Level {
|
|||||||
// For turn-based mode, this is split into two parts: advance_tic_finish_movement completes any
|
// 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
|
// 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.
|
// 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') {
|
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;
|
||||||
@ -460,10 +460,10 @@ export class Level {
|
|||||||
// TODO rip out this try/catch, it's not how the game actually works
|
// TODO rip out this try/catch, it's not how the game actually works
|
||||||
try {
|
try {
|
||||||
if (pass == 1) {
|
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) {
|
else if (pass == 2) {
|
||||||
this.advance_tic_act(p1_primary_direction, p1_secondary_direction);
|
this.advance_tic_act(p1_actions);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
console.warn(`What pass is this?`);
|
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
|
// 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.)
|
// they only take a few bytes each so that's fine.)
|
||||||
for (let key of [
|
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
|
// 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_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
|
// Used to check for a monster chomping the player's tail
|
||||||
this.player_leaving_cell = this.player.cell;
|
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
|
// Second pass: actors decide their upcoming movement simultaneously
|
||||||
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];
|
||||||
@ -607,43 +612,36 @@ export class Level {
|
|||||||
actor.decision = actor.direction;
|
actor.decision = actor.direction;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
else if (actor.slide_mode === 'force') {
|
else if (actor === this.player) {
|
||||||
// Only the player can make voluntary moves on a force floor,
|
// Only the player can make voluntary moves on a force floor, and only if their
|
||||||
// and only if their previous move was an /involuntary/ move on
|
// previous move was an /involuntary/ move on a force floor. If they do, it
|
||||||
// a force floor. If they do, it overrides the forced move
|
// overrides the forced move
|
||||||
// XXX this in particular has some subtleties in lynx (e.g. you
|
// XXX this in particular has some subtleties in lynx (e.g. you can override
|
||||||
// can override forwards??) and DEFINITELY all kinds of stuff
|
// forwards??) and DEFINITELY all kinds of stuff in ms
|
||||||
// in ms
|
|
||||||
// XXX unclear what impact this has on doppelgangers
|
// XXX unclear what impact this has on doppelgangers
|
||||||
if (actor === this.player &&
|
if (actor.slide_mode === 'force' && ! (
|
||||||
p1_primary_direction &&
|
p1_actions.primary && actor.last_move_was_force))
|
||||||
actor.last_move_was_force)
|
|
||||||
{
|
{
|
||||||
actor.decision = p1_primary_direction;
|
// We're forced!
|
||||||
this._set_tile_prop(actor, 'last_move_was_force', false);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
actor.decision = actor.direction;
|
actor.decision = actor.direction;
|
||||||
if (actor === this.player) {
|
|
||||||
this._set_tile_prop(actor, 'last_move_was_force', true);
|
this._set_tile_prop(actor, 'last_move_was_force', true);
|
||||||
}
|
|
||||||
}
|
|
||||||
continue;
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (p1_primary_direction) {
|
if (p1_actions.primary) {
|
||||||
actor.decision = p1_primary_direction;
|
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);
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
else if (actor.cell.some(tile => tile.type.traps && tile.type.traps(tile, actor))) {
|
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
|
// TODO i think player on force floor will still have some issues here
|
||||||
if (direction_preference) {
|
if (direction_preference) {
|
||||||
let fallback_direction;
|
let fallback_direction;
|
||||||
for (let direction of direction_preference) {
|
for (let [i, direction] of direction_preference.entries()) {
|
||||||
if (direction === 'WALKER') {
|
if (direction === 'WALKER') {
|
||||||
// Walkers roll a random direction ONLY if their first attempt was blocked
|
// Walkers roll a random direction ONLY if their first attempt was blocked
|
||||||
direction = actor.direction;
|
direction = actor.direction;
|
||||||
@ -766,6 +764,13 @@ export class Level {
|
|||||||
|
|
||||||
direction = actor.cell.redirect_exit(actor, direction);
|
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);
|
let dest_cell = this.get_neighboring_cell(actor.cell, direction);
|
||||||
if (! dest_cell)
|
if (! dest_cell)
|
||||||
continue;
|
continue;
|
||||||
@ -778,16 +783,23 @@ export class Level {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If all the decisions are blocked, actors still try the last one (and might even
|
// Do some cleanup for the player
|
||||||
// be able to move that way by the time their turn comes around!)
|
if (actor === this.player) {
|
||||||
if (actor.decision === null) {
|
// Sorry for the confusion; "p1" and "p2" in the direction args refer to physical
|
||||||
actor.decision = fallback_direction;
|
// 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
|
// Third pass: everyone actually moves
|
||||||
|
let swap_player1 = false;
|
||||||
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];
|
||||||
if (! actor.cell)
|
if (! actor.cell)
|
||||||
@ -798,6 +810,19 @@ export class Level {
|
|||||||
if (actor.movement_cooldown > 0)
|
if (actor.movement_cooldown > 0)
|
||||||
continue;
|
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)
|
if (! actor.decision)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@ -805,7 +830,7 @@ export class Level {
|
|||||||
let success = this.attempt_step(actor, actor.decision);
|
let success = this.attempt_step(actor, actor.decision);
|
||||||
|
|
||||||
// Track whether the player is blocked, for visual effect
|
// 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');
|
this.sfx.play_once('blocked');
|
||||||
actor.is_blocked = true;
|
actor.is_blocked = true;
|
||||||
}
|
}
|
||||||
@ -862,6 +887,13 @@ export class Level {
|
|||||||
}
|
}
|
||||||
this.actors.length = p;
|
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
|
// Advance the clock
|
||||||
let tic_counter = this.tic_counter;
|
let tic_counter = this.tic_counter;
|
||||||
this.tic_counter += 1;
|
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.
|
// 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.
|
// XXX need to be clear on the order of events here. say everything starts out unpowered.
|
||||||
// then:
|
// then:
|
||||||
|
|||||||
45
js/main.js
45
js/main.js
@ -375,6 +375,20 @@ class Player extends PrimaryView {
|
|||||||
this.set_state('rewinding');
|
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
|
// Demo playback
|
||||||
this.root.querySelector('.demo-controls .demo-play').addEventListener('click', ev => {
|
this.root.querySelector('.demo-controls .demo-play').addEventListener('click', ev => {
|
||||||
if (this.state === 'playing' || this.state === 'paused' || this.state === 'rewinding') {
|
if (this.state === 'playing' || this.state === 'paused' || this.state === 'rewinding') {
|
||||||
@ -386,6 +400,8 @@ class Player extends PrimaryView {
|
|||||||
this.play_demo();
|
this.play_demo();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// FIXME consolidate these into debug controls
|
||||||
|
/*
|
||||||
this.root.querySelector('.demo-controls .demo-step-1').addEventListener('click', ev => {
|
this.root.querySelector('.demo-controls .demo-step-1').addEventListener('click', ev => {
|
||||||
this.advance_by(1);
|
this.advance_by(1);
|
||||||
this._redraw();
|
this._redraw();
|
||||||
@ -394,6 +410,7 @@ class Player extends PrimaryView {
|
|||||||
this.advance_by(4);
|
this.advance_by(4);
|
||||||
this._redraw();
|
this._redraw();
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
this.renderer = new CanvasRenderer(this.conductor.tileset);
|
this.renderer = new CanvasRenderer(this.conductor.tileset);
|
||||||
this.level_el.append(this.renderer.canvas);
|
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,
|
// 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
|
// because we already checked for that above, so this is only necessary if there's
|
||||||
// not already a secondary action
|
// not already a secondary action
|
||||||
|
if (this.secondary_action && ! input.has(this.secondary_action)) {
|
||||||
|
this.secondary_action = null;
|
||||||
|
}
|
||||||
if (! this.secondary_action) {
|
if (! this.secondary_action) {
|
||||||
for (let action of ['down', 'left', 'right', 'up']) {
|
for (let action of ['down', 'left', 'right', 'up']) {
|
||||||
if (action !== this.primary_action &&
|
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.previous_input = input;
|
||||||
|
|
||||||
this.sfx_player.advance_tic();
|
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
|
// Turn-based mode is considered assistance, but only if the game actually attempts to
|
||||||
// progress while it's enabled
|
// progress while it's enabled
|
||||||
if (this.turn_mode > 0) {
|
if (this.turn_mode > 0) {
|
||||||
this.level.aid = Math.max(1, this.level.aid);
|
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
|
// Turn-based mode complicates this slightly; it aligns us to the middle of a tic
|
||||||
if (this.turn_mode === 2) {
|
if (this.turn_mode === 2) {
|
||||||
if (has_input) {
|
if (has_input) {
|
||||||
// Finish the current tic, then continue as usual. This means the end of the
|
// 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
|
// tic doesn't count against the number of tics to advance -- because it already
|
||||||
// did, the first time we tried it
|
// 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;
|
this.turn_mode = 1;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -824,14 +849,14 @@ class Player extends PrimaryView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// We should now be at the start of a tic
|
// 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 (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,
|
// If we're in turn-based mode and could provide input here, but don't have any,
|
||||||
// then wait until we do
|
// then wait until we do
|
||||||
this.turn_mode = 2;
|
this.turn_mode = 2;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.level.advance_tic(primary_dir, secondary_dir, 2);
|
this.level.advance_tic(player_actions, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.level.state !== 'playing') {
|
if (this.level.state !== 'playing') {
|
||||||
@ -931,11 +956,15 @@ class Player extends PrimaryView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
update_ui() {
|
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.restart_button.disabled = (this.state === 'waiting');
|
||||||
this.undo_button.disabled = ! this.level.has_undo();
|
this.undo_button.disabled = ! this.level.has_undo();
|
||||||
this.rewind_button.disabled = ! (this.level.has_undo() || this.state === 'rewinding');
|
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?
|
// TODO can we do this only if they actually changed?
|
||||||
this.chips_el.textContent = this.level.chips_remaining;
|
this.chips_el.textContent = this.level.chips_remaining;
|
||||||
if (this.level.chips_remaining === 0) {
|
if (this.level.chips_remaining === 0) {
|
||||||
|
|||||||
@ -1718,6 +1718,8 @@ const TILE_TYPES = {
|
|||||||
is_player: true,
|
is_player: true,
|
||||||
is_real_player: true,
|
is_real_player: true,
|
||||||
collision_mask: COLLISION.player1,
|
collision_mask: COLLISION.player1,
|
||||||
|
// FIXME does this make us block the doppelgangers??
|
||||||
|
blocks_collision: COLLISION.player,
|
||||||
has_inventory: true,
|
has_inventory: true,
|
||||||
can_reveal_walls: true,
|
can_reveal_walls: true,
|
||||||
movement_speed: 4,
|
movement_speed: 4,
|
||||||
@ -1737,6 +1739,7 @@ const TILE_TYPES = {
|
|||||||
is_player: true,
|
is_player: true,
|
||||||
is_real_player: true,
|
is_real_player: true,
|
||||||
collision_mask: COLLISION.player2,
|
collision_mask: COLLISION.player2,
|
||||||
|
blocks_collision: COLLISION.players,
|
||||||
has_inventory: true,
|
has_inventory: true,
|
||||||
can_reveal_walls: true,
|
can_reveal_walls: true,
|
||||||
movement_speed: 4,
|
movement_speed: 4,
|
||||||
|
|||||||
46
style.css
46
style.css
@ -128,6 +128,7 @@ svg.svg-icon {
|
|||||||
|
|
||||||
stroke: none;
|
stroke: none;
|
||||||
fill: currentColor;
|
fill: currentColor;
|
||||||
|
fill-rule: evenodd;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Overlay styling */
|
/* Overlay styling */
|
||||||
@ -536,6 +537,7 @@ button.level-pack-button p {
|
|||||||
"level bonus" min-content
|
"level bonus" min-content
|
||||||
"level message" 1fr
|
"level message" 1fr
|
||||||
"level inventory" min-content
|
"level inventory" min-content
|
||||||
|
"level actions" min-content
|
||||||
/* Need explicit min-content to force the hint to wrap */
|
/* Need explicit min-content to force the hint to wrap */
|
||||||
/ min-content min-content
|
/ min-content min-content
|
||||||
;
|
;
|
||||||
@ -743,6 +745,15 @@ dl.score-chart .-sum {
|
|||||||
background: #0009;
|
background: #0009;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
#player .actions {
|
||||||
|
grid-area: actions;
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5em;
|
||||||
|
}
|
||||||
|
#player .actions button {
|
||||||
|
flex: 1;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
#player-music {
|
#player-music {
|
||||||
grid-area: music;
|
grid-area: music;
|
||||||
@ -766,7 +777,30 @@ dl.score-chart .-sum {
|
|||||||
#player .controls {
|
#player .controls {
|
||||||
grid-area: controls;
|
grid-area: controls;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
gap: 0.25em;
|
||||||
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
#player button {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
#player button .keyhint {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: -2em;
|
||||||
|
width: 1em;
|
||||||
|
margin: auto;
|
||||||
|
color: #404040;
|
||||||
|
border: 1px solid #202020;
|
||||||
|
border-radius: 0.25em;
|
||||||
|
box-shadow: 0 2px 0 #202020;
|
||||||
|
text-align: center;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
#player button:disabled .keyhint {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.play-controls,
|
.play-controls,
|
||||||
.demo-controls {
|
.demo-controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -778,8 +812,6 @@ dl.score-chart .-sum {
|
|||||||
}
|
}
|
||||||
.demo-controls {
|
.demo-controls {
|
||||||
display: none;
|
display: none;
|
||||||
flex: 1;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
}
|
||||||
main.--has-demo .demo-controls {
|
main.--has-demo .demo-controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -831,6 +863,13 @@ main.--has-demo .demo-controls {
|
|||||||
/* The play area isn't necessarily the biggest thing any more, and it's ugly when stretched */
|
/* The play area isn't necessarily the biggest thing any more, and it's ugly when stretched */
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
#player .controls {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
main.--has-demo .demo-controls {
|
||||||
|
/* TODO need a better place for this! */
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
#player > .-main-area {
|
#player > .-main-area {
|
||||||
/* Rearrange the grid to be vertical */
|
/* Rearrange the grid to be vertical */
|
||||||
grid:
|
grid:
|
||||||
@ -838,6 +877,7 @@ main.--has-demo .demo-controls {
|
|||||||
"chips inventory" calc((var(--tile-height) * var(--scale) * 2 - 1em) / 3)
|
"chips inventory" calc((var(--tile-height) * var(--scale) * 2 - 1em) / 3)
|
||||||
"time inventory" calc((var(--tile-height) * var(--scale) * 2 - 1em) / 3)
|
"time inventory" calc((var(--tile-height) * var(--scale) * 2 - 1em) / 3)
|
||||||
"bonus inventory" calc((var(--tile-height) * var(--scale) * 2 - 1em) / 3)
|
"bonus inventory" calc((var(--tile-height) * var(--scale) * 2 - 1em) / 3)
|
||||||
|
"actions actions" min-content
|
||||||
/* FIXME ideally the first column would be 1fr so the hearts/time have space, but that
|
/* FIXME ideally the first column would be 1fr so the hearts/time have space, but that
|
||||||
* allows hints to grow to the entire width of the window, which incredibly sucks. i
|
* allows hints to grow to the entire width of the window, which incredibly sucks. i
|
||||||
* don't know how to get around this except by giving the grid a fixed width, which i
|
* don't know how to get around this except by giving the grid a fixed width, which i
|
||||||
@ -860,7 +900,7 @@ main.--has-demo .demo-controls {
|
|||||||
z-index: 1;
|
z-index: 1;
|
||||||
font-size: calc(var(--tile-height) * var(--scale) / 2.5);
|
font-size: calc(var(--tile-height) * var(--scale) / 2.5);
|
||||||
}
|
}
|
||||||
#player .controls .keyhint {
|
#player .keyhint {
|
||||||
/* Hide key hints, they take up surprisingly a lot of space */
|
/* Hide key hints, they take up surprisingly a lot of space */
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user