Improve splash page slightly; add pack saving in editor

This commit is contained in:
Eevee (Evelyn Woods) 2020-12-06 14:03:36 -07:00
parent 70df85187f
commit 076aa9133a
5 changed files with 95 additions and 22 deletions

View File

@ -47,23 +47,22 @@
<div class="drag-overlay"></div>
<header>
<h1><img src="og-preview.png" alt="">Lexy's Labyrinth</h1>
<p>an unapproved Chip's Challenge emulator</p>
</header>
<section id="splash-intro">
<p><strong>Welcome</strong> to Lexy's Labyrinth, an open source puzzle game that is curiously similar to — but legally distinct from — the Atari classic <a href="https://en.wikipedia.org/wiki/Chip%27s_Challenge">Chip's Challenge</a>!</p>
<p>(This is a Chip's Challenge <em>emulator</em>, 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.)</p>
<p>Please note that <em>levels themselves</em> 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!</p>
<p>Pick a level pack to get started! You can also get more technical details or report bugs on <a href="https://github.com/eevee/lexys-labyrinth">GitHub</a>, find out more about Chip's Challenge via the <a href="https://bitbusters.club/">Bit Busters Club</a> fansite, or support this endeavor (and other things I do) via <a href="https://www.patreon.com/eevee">Patreon</a>!</p>
<!-- TODO i want to make clear this is a chip's challenge emulator without bogging people down too much about what that means -->
<p>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.</p>
<p>If you're not familiar with the game, read up on <a href="https://github.com/eevee/lexys-labyrinth/wiki/How-To-Play">how to play</a>. You can also get more technical details or report bugs on <a href="https://github.com/eevee/lexys-labyrinth">GitHub</a>, find out more about Chip's Challenge via the <a href="https://bitbusters.club/">Bit Busters Club</a> fansite, or support this endeavor (and other things I do) on <a href="https://www.patreon.com/eevee">Patreon</a>.</p>
</section>
<section id="splash-stock-levels">
<h2>Just play something</h2>
<h2>Play community levels</h2>
<!-- populated by js -->
</section>
<section id="splash-upload-levels">
<h2>Other levels</h2>
<p>Load and play any levels you have on hand — both the original levels and any custom ones, perhaps ones you found on the <a href="https://sets.bitbusters.club/">Bit Busters Club set list</a>. Supports CCL/DAT, C2G, and individual C2Ms (though scores aren't saved for those).</p>
<h2>Load other levels</h2>
<p>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 <a href="https://sets.bitbusters.club/">Bit Busters Club set list</a>.</p>
<!-- TODO zip files! -->
<p>You can also drag and drop files or directories into this window.</p>
<input id="splash-upload-file" type="file" accept=".dat,.ccl,.c2m,.ccs" multiple>

View File

@ -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() {

View File

@ -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,
};

View File

@ -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) {

View File

@ -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;