Allow holding R (for one second) to restart the level
This commit is contained in:
parent
1df89884ed
commit
a3b283b51e
@ -227,8 +227,7 @@
|
||||
<span class="-optional-label">pause</span> <span class="keyhint"><kbd>p</kbd></span></button>
|
||||
<button class="control-restart" type="button" title="restart">
|
||||
<svg class="svg-icon" viewBox="0 0 16 16"><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"></path></svg>
|
||||
<span class="-optional-label">retry</span>
|
||||
</button>
|
||||
<span class="-optional-label">retry</span> <span class="keyhint"><kbd>r</kbd></span></button>
|
||||
<button class="control-undo" type="button" title="undo">
|
||||
<svg class="svg-icon" viewBox="0 0 16 16"><path d="M6,5 6,2 1,7 6,12 6,9 A 10,10 60 0,1 15,12 A 10,10 90 0,0 6,5"></path></svg>
|
||||
<span class="-optional-label">undo</span> <span class="keyhint"><kbd>u</kbd></span></button>
|
||||
|
||||
79
js/main.js
79
js/main.js
@ -21,6 +21,7 @@ const PAGE_TITLE = "Lexy's Labyrinth";
|
||||
// This prefix is LLDEMO in base64, used to be somewhat confident that a string is a valid demo
|
||||
// (it's 6 characters so it becomes exactly 8 base64 chars with no leftovers to entangle)
|
||||
const REPLAY_PREFIX = "TExERU1P";
|
||||
const RESTART_KEY_DELAY = 1.0;
|
||||
|
||||
function format_replay_duration(t) {
|
||||
return `${t} tics (${util.format_duration(t / TICS_PER_SECOND)})`;
|
||||
@ -715,7 +716,6 @@ class Player extends PrimaryView {
|
||||
this.inventory_el.append(img);
|
||||
}
|
||||
|
||||
let last_key;
|
||||
this.pending_player_move = null;
|
||||
this.next_player_move = null;
|
||||
this.player_used_move = false;
|
||||
@ -750,6 +750,11 @@ class Player extends PrimaryView {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ev.key === 'r') {
|
||||
this.start_restarting();
|
||||
return;
|
||||
}
|
||||
|
||||
// Per-tic navigation; only useful if the game isn't running
|
||||
if (ev.key === ',') {
|
||||
if (this.state === 'stopped' || this.state === 'paused' || this.turn_based_mode) {
|
||||
@ -849,10 +854,16 @@ class Player extends PrimaryView {
|
||||
if (! this.active)
|
||||
return;
|
||||
|
||||
if (ev.key === 'r') {
|
||||
this.stop_restarting();
|
||||
return;
|
||||
}
|
||||
|
||||
if (ev.key === 'z') {
|
||||
if (this.state === 'rewinding') {
|
||||
this.set_state('playing');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.key_mapping[ev.key]) {
|
||||
@ -939,23 +950,13 @@ class Player extends PrimaryView {
|
||||
|
||||
// When we lose focus, act as though every key was released, and pause the game
|
||||
window.addEventListener('blur', () => {
|
||||
this.current_keys.clear();
|
||||
this.current_touches = {};
|
||||
|
||||
if (this.state === 'playing' || this.state === 'rewinding') {
|
||||
this.autopause();
|
||||
}
|
||||
this.enter_background();
|
||||
});
|
||||
// Same when the window becomes hidden (especially important on phones, where this covers
|
||||
// turning the screen off!)
|
||||
document.addEventListener('visibilitychange', ev => {
|
||||
if (document.visibilityState === 'hidden') {
|
||||
this.current_keys.clear();
|
||||
this.current_touches = {};
|
||||
|
||||
if (this.state === 'playing' || this.state === 'rewinding') {
|
||||
this.autopause();
|
||||
}
|
||||
this.enter_background();
|
||||
}
|
||||
});
|
||||
|
||||
@ -1402,6 +1403,17 @@ class Player extends PrimaryView {
|
||||
this.captions_el.textContent = '';
|
||||
}
|
||||
|
||||
// Called when we lose focus; assume all keys are released, since we can't be sure any more
|
||||
enter_background() {
|
||||
this.stop_restarting();
|
||||
this.current_keys.clear();
|
||||
this.current_touches = {};
|
||||
|
||||
if (this.state === 'playing' || this.state === 'rewinding') {
|
||||
this.autopause();
|
||||
}
|
||||
}
|
||||
|
||||
reload_options(options) {
|
||||
this.music_audio_el.volume = options.music_volume ?? 1.0;
|
||||
// TODO hide music info when disabled?
|
||||
@ -1946,6 +1958,38 @@ class Player extends PrimaryView {
|
||||
this.set_state('paused');
|
||||
}
|
||||
|
||||
start_restarting() {
|
||||
this.stop_restarting();
|
||||
|
||||
if (! (this.state === 'playing' || this.state === 'paused' || this.state === 'rewinding'))
|
||||
return;
|
||||
|
||||
let t0 = performance.now();
|
||||
let update = () => {
|
||||
let t = performance.now();
|
||||
let p = (t - t0) / 1000 / RESTART_KEY_DELAY;
|
||||
this.restart_button.style.setProperty('--restart-progress', p);
|
||||
|
||||
if (p < 1) {
|
||||
this._restart_handle = requestAnimationFrame(update);
|
||||
}
|
||||
else {
|
||||
this.restart_level();
|
||||
this.stop_restarting();
|
||||
this._restart_handle = null;
|
||||
}
|
||||
};
|
||||
update();
|
||||
}
|
||||
|
||||
stop_restarting() {
|
||||
if (this._restart_handle) {
|
||||
cancelAnimationFrame(this._restart_handle);
|
||||
this._restart_handle = null;
|
||||
}
|
||||
this.restart_button.style.setProperty('--restart-progress', 0);
|
||||
}
|
||||
|
||||
// waiting: haven't yet pressed a key so the timer isn't going
|
||||
// playing: playing normally
|
||||
// paused: um, paused
|
||||
@ -2217,8 +2261,13 @@ class Player extends PrimaryView {
|
||||
|
||||
this.update_music_playback_state();
|
||||
|
||||
// The advance and redraw methods run in a loop, but they cancel
|
||||
// themselves if the game isn't running, so restart them here
|
||||
// Restarting makes no sense if we're not playing
|
||||
if (this.state === 'waiting' || this.state === 'stopped' || this.state === 'ended') {
|
||||
this.stop_restarting();
|
||||
}
|
||||
|
||||
// The advance and redraw methods run in a loop, but they cancel themselves if the game
|
||||
// isn't running, so restart them here
|
||||
if (this.state === 'playing' || this.state === 'rewinding') {
|
||||
if (! this._advance_handle) {
|
||||
this.advance();
|
||||
|
||||
10
style.css
10
style.css
@ -19,6 +19,7 @@ body {
|
||||
|
||||
--panel-bg-color: hsl(220, 10%, 15%);
|
||||
--button-bg-color: hsl(220, 20%, 25%);
|
||||
--button-bg-gradient: linear-gradient(to bottom, var(--button-bg-shadow-color), transparent 75%);
|
||||
--button-bg-shadow-color: #fff1;
|
||||
--button-bg-hover-color: hsl(220, 30%, 30%);
|
||||
--generic-bg-hover-on-white: hsl(220, 60%, 90%);
|
||||
@ -50,7 +51,7 @@ button,
|
||||
font-family: inherit;
|
||||
color: white;
|
||||
background-color: var(--button-bg-color);
|
||||
background-image: linear-gradient(to bottom, var(--button-bg-shadow-color), transparent 75%);
|
||||
background-image: var(--button-bg-gradient);
|
||||
border: 1px solid hsl(220, 10%, 7.5%);
|
||||
box-shadow:
|
||||
inset 0 0 1px 1px #fff2,
|
||||
@ -1238,6 +1239,13 @@ ol.packtest-summary > li {
|
||||
#player button:disabled .keyhint {
|
||||
display: none;
|
||||
}
|
||||
#player-controls button:enabled.control-restart {
|
||||
/* Special shenanigans for holding R to restart */
|
||||
--restart-progress: 0;
|
||||
background-image: var(--button-bg-gradient), conic-gradient(
|
||||
hsl(345, 60%, 40%) 0deg calc(var(--restart-progress) * 360deg),
|
||||
transparent calc(var(--restart-progress) * 360deg) 360deg)
|
||||
}
|
||||
@media (orientation: portrait) {
|
||||
/* On a portrait screen, put the controls on top */
|
||||
#player-main {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user