From c17169f49da738b342020297ab27b76856a93223 Mon Sep 17 00:00:00 2001 From: "Eevee (Evelyn Woods)" Date: Fri, 11 Dec 2020 22:15:39 -0700 Subject: [PATCH] Rearranged debug panel a bit; added progress bar for replay playback --- index.html | 23 +++--- js/format-c2g.js | 1 + js/main-editor.js | 3 +- js/main.js | 204 +++++++++++++++++++++++++++------------------- js/util.js | 6 ++ style.css | 27 ++++-- 6 files changed, 161 insertions(+), 103 deletions(-) diff --git a/index.html b/index.html index 3c15e6e..fbcde40 100644 --- a/index.html +++ b/index.html @@ -218,6 +218,11 @@

+

Inventory

+
+ +
+

Replay

-
-

+

+
+
+
+

100% of 0 tics (0:00s)

-

Options

+

Misc

Viewport size: Player levitates --> - -

Inventory

-
- -
- -

Misc

diff --git a/js/format-c2g.js b/js/format-c2g.js index 6d5ef4d..472bddb 100644 --- a/js/format-c2g.js +++ b/js/format-c2g.js @@ -34,6 +34,7 @@ class CC2Demo { for (let p = 3; p < l; p += 2) { duration += this.bytes[p]; } + duration = Math.floor(duration / 3); let inputs = new Uint8Array(duration); let i = 0; diff --git a/js/main-editor.js b/js/main-editor.js index e86a56a..4518c02 100644 --- a/js/main-editor.js +++ b/js/main-editor.js @@ -6,6 +6,7 @@ import { PrimaryView, TransientOverlay, DialogOverlay, load_json_from_storage, s import CanvasRenderer from './renderer-canvas.js'; import TILE_TYPES from './tiletypes.js'; import { SVG_NS, mk, mk_svg, string_from_buffer_ascii, bytestring_to_buffer, walk_grid } from './util.js'; +import * as util from './util.js'; class EditorPackMetaOverlay extends DialogOverlay { constructor(conductor, stored_pack) { @@ -53,7 +54,7 @@ class EditorLevelMetaOverlay extends DialogOverlay { text = "No time limit"; } else { - text = `${time_limit} (${Math.floor(time_limit / 60)}:${('0' + String(time_limit % 60)).slice(-2)})`; + text = `${time_limit} (${util.format_duration(time_limit)})`; } time_limit_output.textContent = text; }; diff --git a/js/main.js b/js/main.js index 5224f66..e3503a5 100644 --- a/js/main.js +++ b/js/main.js @@ -635,44 +635,14 @@ class Player extends PrimaryView { time_secs_el: this.root.querySelector('#player-debug-time-secs'), }; - let input_el = debug_el.querySelector('#player-debug-input'); - this.debug.input_els = {}; - for (let [action, label] of Object.entries({up: 'W', left: 'A', down: 'S', right: 'D', drop: 'Q', cycle: 'E', swap: 'C'})) { - let el = mk_svg('svg.svg-icon', {viewBox: '0 0 16 16'}, - mk_svg('use', {href: `#svg-icon-${action}`})); - el.style.gridArea = action; - this.debug.input_els[action] = el; - input_el.append(el); - } - - // Add a button for every kind of inventory item - let inventory_el = debug_el.querySelector('.-inventory'); let make_button = (label, onclick) => { let button = mk('button', {type: 'button'}, label); button.addEventListener('click', onclick); return button; }; - for (let name of [ - 'key_blue', 'key_red', 'key_yellow', 'key_green', - 'flippers', 'fire_boots', 'cleats', 'suction_boots', - 'bribe', 'railroad_sign', 'hiking_boots', 'speed_boots', - 'xray_eye', 'helmet', 'foil', 'lightning_bolt', - ]) { - inventory_el.append(make_button( - mk('img', {src: this.render_inventory_tile(name)}), - () => { - this.level.give_actor(this.level.player, name); - this.update_ui(); - })); - } - let clear_button = mk('button.-wide', {type: 'button'}, "clear inventory"); - clear_button.addEventListener('click', ev => { - this.level.take_all_keys_from_actor(this.level.player); - this.level.take_all_tools_from_actor(this.level.player); - this.update_ui(); - }); - inventory_el.append(clear_button); + // -- Time -- + // Hook up back/forward buttons debug_el.querySelector('.-time-controls').addEventListener('click', ev => { let button = ev.target.closest('button.-time-button'); if (! button) @@ -692,43 +662,7 @@ class Player extends PrimaryView { this._redraw(); this.update_ui(); }); - - let speed_el = debug_el.elements.speed; - speed_el.value = "1"; - speed_el.addEventListener('change', ev => { - let speed = ev.target.value; - let [numer, denom] = speed.split('/'); - this.play_speed = parseInt(numer, 10) / parseInt(denom ?? '1', 10); - }); - - let viewport_el = this.root.querySelector('#player-debug-viewport'); - viewport_el.value = "default"; - viewport_el.addEventListener('change', ev => { - let viewport = ev.target.value; - if (viewport === 'default') { - this.debug.viewport_size_override = null; - } - else if (viewport === 'max') { - this.debug.viewport_size_override = 'max'; - } - else { - this.debug.viewport_size_override = parseInt(viewport, 10); - } - this.update_viewport_size(); - this._redraw(); - }); - - this.debug.replay_button = make_button("view replay", () => { - if (this.state === 'playing' || this.state === 'paused' || this.state === 'rewinding') { - new ConfirmOverlay(this.conductor, "Restart this level and watch the replay?", () => { - this.play_demo(); - }).open(); - } - else { - this.play_demo(); - } - }); - this._update_replay_button_enabled(); + // Add buttons for affecting the clock debug_el.querySelector('#player-debug-time-buttons').append( make_button("toggle clock", () => { this.level.pause_timer(); @@ -747,19 +681,99 @@ class Player extends PrimaryView { this.update_ui(); }), ); - debug_el.querySelector('#player-debug-misc-buttons').append( - this.debug.replay_button, - make_button("green button", () => { - TILE_TYPES['button_green'].do_button(this.level); - this._redraw(); - }), - make_button("blue button", () => { - TILE_TYPES['button_blue'].do_button(this.level); - this._redraw(); - }), - ); + // Hook up play speed + let speed_el = debug_el.elements.speed; + speed_el.value = "1"; + speed_el.addEventListener('change', ev => { + let speed = ev.target.value; + let [numer, denom] = speed.split('/'); + this.play_speed = parseInt(numer, 10) / parseInt(denom ?? '1', 10); + }); - // Link up some options checkboxes + // -- Inventory -- + // Add a button for every kind of inventory item + let inventory_el = debug_el.querySelector('.-inventory'); + for (let name of [ + 'key_blue', 'key_red', 'key_yellow', 'key_green', + 'flippers', 'fire_boots', 'cleats', 'suction_boots', + 'bribe', 'railroad_sign', 'hiking_boots', 'speed_boots', + 'xray_eye', 'helmet', 'foil', 'lightning_bolt', + ]) { + inventory_el.append(make_button( + mk('img', {src: this.render_inventory_tile(name)}), + () => { + this.level.give_actor(this.level.player, name); + this.update_ui(); + })); + } + // Add a button to clear your inventory + let clear_button = mk('button.-wide', {type: 'button'}, "clear inventory"); + clear_button.addEventListener('click', ev => { + this.level.take_all_keys_from_actor(this.level.player); + this.level.take_all_tools_from_actor(this.level.player); + this.update_ui(); + }); + inventory_el.append(clear_button); + + // -- Replay -- + // Create the input grid + let input_el = debug_el.querySelector('#player-debug-input'); + this.debug.input_els = {}; + for (let [action, label] of Object.entries({up: 'W', left: 'A', down: 'S', right: 'D', drop: 'Q', cycle: 'E', swap: 'C'})) { + let el = mk_svg('svg.svg-icon', {viewBox: '0 0 16 16'}, + mk_svg('use', {href: `#svg-icon-${action}`})); + el.style.gridArea = action; + this.debug.input_els[action] = el; + input_el.append(el); + } + // Add a button to view a replay + this.debug.replay_button = make_button("run level's replay", () => { + if (this.state === 'playing' || this.state === 'paused' || this.state === 'rewinding') { + new ConfirmOverlay(this.conductor, "Restart this level and watch the replay?", () => { + this.play_demo(); + }).open(); + } + else { + this.play_demo(); + } + }); + this._update_replay_button_enabled(); + debug_el.querySelector('.-replay-columns .-buttons').append( + this.debug.replay_button, + /* + mk('button', {disabled: 'disabled'}, "load external replay"), + mk('button', {disabled: 'disabled'}, "regain control"), + mk('button', {disabled: 'disabled'}, "record new replay"), + mk('button', {disabled: 'disabled'}, "record from here"), + mk('button', {disabled: 'disabled'}, "browse/edit manually"), + */ + ); + // Progress bar and whatnot + let replay_playback_el = debug_el.querySelector('#player-debug-replay-playback'); + this.debug.replay_playback_el = replay_playback_el; + this.debug.replay_progress_el = replay_playback_el.querySelector('progress'); + this.debug.replay_percent_el = replay_playback_el.querySelector('output'); + this.debug.replay_duration_el = replay_playback_el.querySelector('span'); + + // -- Misc -- + // Viewport size + let viewport_el = this.root.querySelector('#player-debug-viewport'); + viewport_el.value = "default"; + viewport_el.addEventListener('change', ev => { + let viewport = ev.target.value; + if (viewport === 'default') { + this.debug.viewport_size_override = null; + } + else if (viewport === 'max') { + this.debug.viewport_size_override = 'max'; + } + else { + this.debug.viewport_size_override = parseInt(viewport, 10); + } + this.update_viewport_size(); + this._redraw(); + }); + // Various checkboxes let wire_checkbox = (name, onclick) => { let checkbox = debug_el.elements[name]; checkbox.checked = false; // override browser memory @@ -773,6 +787,16 @@ class Player extends PrimaryView { this.use_interpolation = ! ev.target.checked; this._redraw(); }); + debug_el.querySelector('#player-debug-misc-buttons').append( + make_button("green button", () => { + TILE_TYPES['button_green'].do_button(this.level); + this._redraw(); + }), + make_button("blue button", () => { + TILE_TYPES['button_blue'].do_button(this.level); + this._redraw(); + }), + ); this.adjust_scale(); if (this.level) { @@ -875,6 +899,7 @@ class Player extends PrimaryView { this.time_el.classList.remove('--frozen'); this.time_el.classList.remove('--danger'); this.time_el.classList.remove('--warning'); + this.root.classList.remove('--replay'); this.root.classList.remove('--bonus-visible'); this.update_ui(); @@ -894,6 +919,14 @@ class Player extends PrimaryView { this.level._blob_modifier = demo.blob_seed; // FIXME should probably start playback on first real input this.set_state('playing'); + this.root.classList.add('--replay'); + + if (this.debug.enabled) { + this.debug.replay_playback_el.style.display = ''; + let t = this.demo_faucet.length; + this.debug.replay_progress_el.setAttribute('max', t); + this.debug.replay_duration_el.textContent = `${t} tics (${util.format_duration(t / TICS_PER_SECOND)})`; + } } get_input() { @@ -1196,6 +1229,11 @@ class Player extends PrimaryView { this.debug.time_tics_el.textContent = `${t}`; this.debug.time_moves_el.textContent = `${Math.floor(t/4)}`; this.debug.time_secs_el.textContent = (t / 20).toFixed(2); + + if (this.demo_faucet) { + this.debug.replay_progress_el.setAttribute('value', t); + this.debug.replay_percent_el.textContent = `${Math.floor((t + 1) / this.demo_faucet.length * 100)}%`; + } } } @@ -1954,9 +1992,7 @@ class LevelBrowserOverlay extends DialogOverlay { // Express absolute time as mm:ss, with two decimals on the seconds (which should be // able to exactly count a number of tics) - let absmin = Math.floor(scorecard.abstime / TICS_PER_SECOND / 60); - let abssec = scorecard.abstime / TICS_PER_SECOND % 60; - abstime = `${absmin}:${abssec < 10 ? '0' : ''}${abssec.toFixed(2)}`; + abstime = util.format_duration(scorecard.abstime / TICS_PER_SECOND, 2); } let title = meta.title; diff --git a/js/util.js b/js/util.js index 15dc749..9c345f2 100644 --- a/js/util.js +++ b/js/util.js @@ -162,6 +162,12 @@ export function bytestring_to_buffer(bytestring) { return Uint8Array.from(bytestring, c => c.charCodeAt(0)).buffer; } +export function format_duration(seconds, places = 0) { + let mins = Math.floor(seconds / 60); + let secs = seconds % 60; + return `${mins}:${secs < 10 ? '0' : ''}${secs.toFixed(places)}`; +} + export class DelayTimer { constructor() { this.active = false; diff --git a/style.css b/style.css index e48b07b..8ed1d7c 100644 --- a/style.css +++ b/style.css @@ -937,7 +937,16 @@ dl.score-chart .-sum { flex: 1 0 max-content; } +#player-debug .-replay-columns { + display: flex; + align-items: flex-start; + gap: 0.5em; +} +#player-debug .-replay-columns .-buttons { + flex-direction: column; +} #player-debug-input { + flex: none; display: grid; grid: "drop up cycle" 1em @@ -953,18 +962,22 @@ dl.score-chart .-sum { #player-debug-input > svg.--held { fill: white; } -#player-debug-demo-playback { - display: flex; - /* TODO finish this later */ +#player-debug-replay-playback { display: none; + gap: 0.25em; } -#player-debug-demo-playback progress { +#player.--replay #player-debug-replay-playback { + display: flex; +} +#player-debug-replay-playback progress { flex: 1; } -#player-debug-demo-playback output { - width: 4em; +#player-debug-replay-playback output { + width: 3em; + width: 5ch; + text-align: right; } -#player-debug-demo-playback span { +#player-debug-replay-playback span { flex: none; }