Improve splash page slightly; add pack saving in editor
This commit is contained in:
parent
70df85187f
commit
076aa9133a
13
index.html
13
index.html
@ -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>
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
11
js/main.js
11
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) {
|
||||
|
||||
17
style.css
17
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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user