From 076aa9133a7330f4a2d77557da3dafe30ecf2c4b Mon Sep 17 00:00:00 2001 From: "Eevee (Evelyn Woods)" Date: Sun, 6 Dec 2020 14:03:36 -0700 Subject: [PATCH] Improve splash page slightly; add pack saving in editor --- index.html | 13 ++++----- js/main-base.js | 4 +++ js/main-editor.js | 72 +++++++++++++++++++++++++++++++++++++++++------ js/main.js | 11 ++++++-- style.css | 17 +++++++++-- 5 files changed, 95 insertions(+), 22 deletions(-) diff --git a/index.html b/index.html index 1dc2db8..6029ebe 100644 --- a/index.html +++ b/index.html @@ -47,23 +47,22 @@

Lexy's Labyrinth

+

an unapproved Chip's Challenge emulator

Welcome to Lexy's Labyrinth, an open source puzzle game that is curiously similar to — but legally distinct from — the Atari classic Chip's Challenge!

-

(This is a Chip's Challenge emulator, designed to be an accessible way to play community-made levels with free assets. It's 99% compatible with Chip's Challenge 1, and support for Chip's Challenge 2 is underway. But you can safely ignore all that and treat this as its own game.)

-

Please note that levels themselves may contain hints or lore referring to a guy named Chip collecting computer chips, even though you are clearly a fox named Lexy collecting hearts. Weird, right? Sorry for any confusion!

-

Pick a level pack to get started! You can also get more technical details or report bugs on GitHub, find out more about Chip's Challenge via the Bit Busters Club fansite, or support this endeavor (and other things I do) via Patreon!

- +

Pick a level pack to get started! You can also load and play any levels you've got lying around, or brave the level editor and make one of your own.

+

If you're not familiar with the game, read up on how to play. You can also get more technical details or report bugs on GitHub, find out more about Chip's Challenge via the Bit Busters Club fansite, or support this endeavor (and other things I do) on Patreon.

-

Just play something

+

Play community levels

-

Other levels

-

Load and play any levels you have on hand — both the original levels and any custom ones, perhaps ones you found on the Bit Busters Club set list. Supports CCL/DAT, C2G, and individual C2Ms (though scores aren't saved for those).

+

Load other levels

+

Load and play any levels you have on hand, including the original levels. Supports CCL/DAT, C2G, and individual C2Ms (though scores aren't saved for those). Find more on the Bit Busters Club set list.

You can also drag and drop files or directories into this window.

diff --git a/js/main-base.js b/js/main-base.js index b27e528..4b8319f 100644 --- a/js/main-base.js +++ b/js/main-base.js @@ -37,6 +37,10 @@ export class Overlay { this.root.addEventListener('click', ev => { ev.stopPropagation(); }); + // Block any window-level key handlers from firing when we type + this.root.addEventListener('keydown', ev => { + ev.stopPropagation(); + }); } open() { diff --git a/js/main-editor.js b/js/main-editor.js index 6eb6526..348cf6e 100644 --- a/js/main-editor.js +++ b/js/main-editor.js @@ -7,10 +7,40 @@ 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'; +class EditorPackMetaOverlay extends DialogOverlay { + constructor(conductor, stored_pack) { + super(conductor); + this.set_title("pack properties"); + let dl = mk('dl.formgrid'); + this.main.append(dl); + + dl.append( + mk('dt', "Title"), + mk('dd', mk('input', {name: 'title', type: 'text', value: stored_pack.title})), + ); + // TODO...? what else is a property of the pack itself + + this.add_button("save", () => { + let els = this.root.elements; + + let title = els.title.value; + if (title !== stored_pack.title) { + stored_pack.title = title; + this.conductor.update_level_title(); + } + + this.close(); + }); + this.add_button("nevermind", () => { + this.close(); + }); + } +} + class EditorLevelMetaOverlay extends DialogOverlay { constructor(conductor, stored_level) { super(conductor); - this.set_title("edit level metadata"); + this.set_title("level properties"); let dl = mk('dl.formgrid'); this.main.append(dl); @@ -84,18 +114,14 @@ class EditorLevelMetaOverlay extends DialogOverlay { this.root.elements['viewport'].value = stored_level.viewport_size; this.root.elements['blob_behavior'].value = stored_level.blob_behavior; // TODO: - // - author // - chips? // - password??? // - comment - // - viewport size mode - // - rng/blob behavior // - use CC1 tools // - hide logic // - "unviewable", "read only" - let ok = mk('button', {type: 'button'}, "make it so"); - ok.addEventListener('click', ev => { + this.add_button("save", () => { let els = this.root.elements; let title = els.title.value; @@ -122,7 +148,9 @@ class EditorLevelMetaOverlay extends DialogOverlay { this.close(); }); - this.footer.append(ok); + this.add_button("nevermind", () => { + this.close(); + }); } } @@ -1025,18 +1053,41 @@ export class Editor extends PrimaryView { button_container.append(button); return button; }; - _make_button("Properties...", ev => { + _make_button("Pack properties...", ev => { + new EditorPackMetaOverlay(this.conductor, this.conductor.stored_game).open(); + }); + _make_button("Level properties...", ev => { new EditorLevelMetaOverlay(this.conductor, this.stored_level).open(); }); this.save_button = _make_button("Save", ev => { // TODO need feedback. or maybe not bc this should be replaced with autosave later // TODO also need to update the pack data's last modified time - if (! this.conductor.stored_game.editor_metadata) + let stored_game = this.conductor.stored_game; + if (! stored_game.editor_metadata) return; + // Update the pack index; we need to do this to update the last modified time anyway, so + // there's no point in checking whether anything actually changed + let pack_key = stored_game.editor_metadata.key; + this.stash.packs[pack_key].title = stored_game.title; + this.stash.packs[pack_key].last_modified = Date.now(); + + // Update the pack itself + // TODO maybe should keep this around, but there's a tricky order of operations thing + // with it + let pack_stash = load_json_from_storage(pack_key); + pack_stash.title = stored_game.title; + pack_stash.last_modified = Date.now(); + + // Serialize the level itself let buf = c2g.synthesize_level(this.stored_level); let stringy_buf = string_from_buffer_ascii(buf); + + // Save everything at once, level first, to minimize chances of an error getting things + // out of sync window.localStorage.setItem(this.stored_level.editor_metadata.key, stringy_buf); + save_json_to_storage(pack_key, pack_stash); + save_json_to_storage("Lexy's Labyrinth editor", this.stash); }); if (this.stored_level) { this.save_button.disabled = ! this.conductor.stored_game.editor_metadata; @@ -1163,6 +1214,7 @@ export class Editor extends PrimaryView { load_editor_pack(pack_key) { let pack_stash = load_json_from_storage(pack_key); + let stored_pack = new format_base.StoredPack(pack_key, meta => { let buf = bytestring_to_buffer(localStorage.getItem(meta.key)); let stored_level = c2g.parse_level(buf, meta.number); @@ -1171,6 +1223,8 @@ export class Editor extends PrimaryView { }; return stored_level; }); + // TODO should this also be in the pack's stash...? + stored_pack.title = this.stash.packs[pack_key].title; stored_pack.editor_metadata = { key: pack_key, }; diff --git a/js/main.js b/js/main.js index 6f95ea7..b0ca8da 100644 --- a/js/main.js +++ b/js/main.js @@ -1486,10 +1486,15 @@ class Splash extends PrimaryView { let packs = this.conductor.editor.stash.packs; let pack_keys = Object.keys(packs); pack_keys.sort((a, b) => packs[a].last_modified - packs[b].last_modified); - let editor_list = this.root.querySelector('#splash-your-levels'); + let editor_section = this.root.querySelector('#splash-your-levels'); + let editor_list = editor_section; for (let key of pack_keys) { let pack = packs[key]; - let button = mk('button', {type: 'button'}, pack.title); + let button = mk('button.button-big.level-pack-button', {type: 'button'}, + mk('h3', pack.title), + // TODO whether it's yours or not? + // TODO number of levels? + ); // TODO make a container so this can be 1 event button.addEventListener('click', ev => { this.conductor.editor.load_editor_pack(key); @@ -2052,7 +2057,7 @@ class Conductor { // TODO handle errors // TODO cancel a download if we start another one? let buf = await util.fetch(path); - await this.parse_and_load_game(buf, new util.HTTPFileSource(new URL(location)), path, title); + await this.parse_and_load_game(buf, new util.HTTPFileSource(new URL(location)), path, undefined, title); } async parse_and_load_game(buf, source, path, identifier, title) { diff --git a/style.css b/style.css index 887d44b..2c34189 100644 --- a/style.css +++ b/style.css @@ -110,10 +110,10 @@ a:visited { text-decoration: underline dotted; } a:link { - color: hsl(225, 50%, 60%); + color: hsl(225, 50%, 75%); } a:visited { - color: hsl(300, 50%, 60%); + color: hsl(255, 50%, 75%); } a:link:hover, a:visited:hover { @@ -193,6 +193,12 @@ svg.svg-icon { background: #f0d0d0; padding: 0.5em 1em; } +.dialog a:link { + color: hsl(225, 50%, 50%); +} +.dialog a:visited { + color: hsl(255, 50%, 50%); +} dl.formgrid { display: grid; grid: auto-flow min-content / 1fr 4fr; @@ -447,6 +453,11 @@ body[data-mode=player] #editor-play { #splash > header h1 { font-size: 3em; } +#splash > header p { + margin: 0; + font-style: italic; + color: #909090; +} #splash h2 { border-bottom: 1px solid #404040; color: #909090; @@ -459,7 +470,7 @@ body[data-mode=player] #editor-play { } #splash > #splash-intro { grid-area: intro; - font-size: 18px; + font-size: 20px; } #splash > #splash-stock-levels { grid-area: stock;