Rebind Space to jump, gate editor behind localStorage flag

Space is now the alternate jump key (was shoot). Web shell shows
game controls by default and hides editor UI unless
localStorage.show_editor is set to 'true'. The E key and ?edit URL
are blocked when the editor is not enabled.

Also fix Makefile to track web/shell.html as a WASM link dependency
so shell changes trigger a rebuild.
This commit is contained in:
Thomas
2026-03-01 18:24:21 +00:00
parent 3584aace75
commit 302bcc592d
4 changed files with 120 additions and 72 deletions

View File

@@ -203,8 +203,8 @@ Current directives: `TILESET`, `SIZE`, `SPAWN`, `GRAVITY`, `WIND`, `BG_COLOR`, `
| Action | Key | Status |
|-----------|--------------|---------------|
| Move | Arrow keys | Implemented |
| Jump | Z | Implemented |
| Shoot | X / Space | Implemented |
| Jump | Z / Space | Implemented |
| Shoot | X | Implemented |
| Aim up | UP (+ shoot) | Implemented |
| Aim diag | UP+LEFT/RIGHT (+ shoot) | Implemented |
| Dash | C | Implemented |

View File

@@ -80,7 +80,13 @@ OBJ_ALL := $(SRC_ALL:src/%.c=$(OBJ_DIR)/%.o)
all: $(BIN)
$(BIN): $(OBJ_ALL) | outdirs
# On WASM builds the shell template is baked into the output HTML,
# so re-link whenever it changes.
ifdef WASM
EXTRA_LINK_DEPS := web/shell.html
endif
$(BIN): $(OBJ_ALL) $(EXTRA_LINK_DEPS) | outdirs
$(CC) $(OBJ_ALL) -o $@ $(LDFLAGS)
outdirs:

View File

@@ -40,7 +40,7 @@ static SDL_Scancode s_bindings[ACTION_COUNT] = {
/* Alternate bindings (0 = no alternate) */
static SDL_Scancode s_alt_bindings[ACTION_COUNT] = {
[ACTION_SHOOT] = SDL_SCANCODE_SPACE,
[ACTION_JUMP] = SDL_SCANCODE_SPACE,
};
void input_init(void) {

View File

@@ -60,6 +60,17 @@
max-width: 1300px;
}
#hint { color: #666; }
#game-controls {
display: flex;
align-items: center;
gap: 8px;
margin-top: 8px;
font-size: 12px;
color: #666;
flex-wrap: wrap;
justify-content: center;
max-width: 1300px;
}
.ctrl-btn {
color: #4ecdc4;
background: transparent;
@@ -100,7 +111,7 @@
<canvas class="emscripten" id="canvas" tabindex="1"
width="640" height="360"></canvas>
</div>
<div id="controls">
<div id="controls" class="hidden">
<a class="ctrl-btn" id="editor-link" href="?edit">Editor</a>
<span class="ctrl-sep">|</span>
<button class="ctrl-btn" id="btn-save" title="Save level (download .lvl)">Save</button>
@@ -112,10 +123,33 @@
<span class="ctrl-sep">|</span>
<span id="hint">E=editor P=test play 1-6=tools</span>
</div>
<div id="game-controls">
<span>Arrows=move</span>
<span class="ctrl-sep">|</span>
<span>Z/Space=jump</span>
<span class="ctrl-sep">|</span>
<span>X=shoot</span>
<span class="ctrl-sep">|</span>
<span>C=dash</span>
<span class="ctrl-sep">|</span>
<span>Up=aim up</span>
</div>
<div id="status">Loading...</div>
<div id="progress-bar"><div id="progress-bar-inner"></div></div>
<script>
/* ── Editor gate: only enable when localStorage show_editor=true ── */
var editorEnabled = false;
try { editorEnabled = (localStorage.getItem('show_editor') === 'true'); } catch(e) {}
if (editorEnabled) {
document.getElementById('controls').classList.remove('hidden');
document.getElementById('game-controls').classList.add('hidden');
}
/* Strip ?edit from URL when editor is not enabled */
if (!editorEnabled && window.location.search.indexOf('edit') !== -1) {
window.history.replaceState({}, '', window.location.pathname);
}
var statusEl = document.getElementById('status');
var progressEl = document.getElementById('progress-bar');
var progressIn = document.getElementById('progress-bar-inner');
@@ -155,16 +189,22 @@
Module.setStatus('Error - check the browser console');
};
/* ── Keyboard shortcuts: Ctrl+S / Ctrl+O ────────── */
/* ── Keyboard shortcuts: Ctrl+S / Ctrl+O / E-key ── */
document.addEventListener('keydown', function(e) {
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
/* Block E-key editor entry when editor is not enabled */
if (!editorEnabled && e.key === 'e' && !e.ctrlKey && !e.metaKey) {
e.preventDefault();
e.stopPropagation();
return;
}
if (editorEnabled && (e.ctrlKey || e.metaKey) && e.key === 's') {
e.preventDefault();
e.stopPropagation();
if (typeof _editor_save_flag_ptr === 'function') {
HEAP32[_editor_save_flag_ptr() >> 2] = 1;
}
}
if ((e.ctrlKey || e.metaKey) && e.key === 'o') {
if (editorEnabled && (e.ctrlKey || e.metaKey) && e.key === 'o') {
e.preventDefault();
e.stopPropagation();
if (typeof _editor_load_flag_ptr === 'function') {
@@ -173,72 +213,74 @@
}
}, true); /* useCapture=true to intercept before SDL */
/* ── Button handlers ────────────────────────────── */
document.getElementById('btn-save').addEventListener('click', function() {
if (typeof _editor_save_flag_ptr === 'function') {
HEAP32[_editor_save_flag_ptr() >> 2] = 1;
}
/* Return focus to canvas so keys keep working */
document.getElementById('canvas').focus();
});
document.getElementById('btn-load').addEventListener('click', function() {
if (typeof _editor_load_flag_ptr === 'function') {
HEAP32[_editor_load_flag_ptr() >> 2] = 1;
}
document.getElementById('canvas').focus();
});
/* ── Level picker dropdown ──────────────────────── */
var levelSelect = document.getElementById('level-select');
/* Populate the dropdown after the module is ready (FS available) */
Module.postRun = Module.postRun || [];
Module.postRun.push(function() {
/* Scan for .lvl files in the virtual FS */
var levels = [];
try {
var entries = FS.readdir('assets/levels');
for (var i = 0; i < entries.length; i++) {
if (entries[i].endsWith('.lvl') && entries[i][0] !== '_') {
levels.push('assets/levels/' + entries[i]);
}
/* ── Editor-only features (button handlers, level picker) ── */
if (editorEnabled) {
document.getElementById('btn-save').addEventListener('click', function() {
if (typeof _editor_save_flag_ptr === 'function') {
HEAP32[_editor_save_flag_ptr() >> 2] = 1;
}
levels.sort();
} catch(e) {
console.error('Could not read levels dir:', e);
/* Return focus to canvas so keys keep working */
document.getElementById('canvas').focus();
});
document.getElementById('btn-load').addEventListener('click', function() {
if (typeof _editor_load_flag_ptr === 'function') {
HEAP32[_editor_load_flag_ptr() >> 2] = 1;
}
document.getElementById('canvas').focus();
});
/* ── Level picker dropdown ──────────────────────── */
var levelSelect = document.getElementById('level-select');
/* Populate the dropdown after the module is ready (FS available) */
Module.postRun = Module.postRun || [];
Module.postRun.push(function() {
/* Scan for .lvl files in the virtual FS */
var levels = [];
try {
var entries = FS.readdir('assets/levels');
for (var i = 0; i < entries.length; i++) {
if (entries[i].endsWith('.lvl') && entries[i][0] !== '_') {
levels.push('assets/levels/' + entries[i]);
}
}
levels.sort();
} catch(e) {
console.error('Could not read levels dir:', e);
}
for (var j = 0; j < levels.length; j++) {
var opt = document.createElement('option');
opt.value = levels[j];
/* Show just the filename without path */
opt.textContent = levels[j].replace('assets/levels/', '');
levelSelect.appendChild(opt);
}
});
levelSelect.addEventListener('change', function() {
var path = this.value;
if (!path) return;
if (typeof _editor_load_vfs_file === 'function') {
/* Pass the path string to C */
var len = lengthBytesUTF8(path) + 1;
var buf = _malloc(len);
stringToUTF8(path, buf, len);
_editor_load_vfs_file(buf);
_free(buf);
}
/* Reset dropdown to placeholder */
this.selectedIndex = 0;
document.getElementById('canvas').focus();
});
/* Update title if in editor mode */
if (window.location.search.indexOf('edit') !== -1) {
document.title = 'Jump \'n Run - Level Editor';
}
for (var j = 0; j < levels.length; j++) {
var opt = document.createElement('option');
opt.value = levels[j];
/* Show just the filename without path */
opt.textContent = levels[j].replace('assets/levels/', '');
levelSelect.appendChild(opt);
}
});
levelSelect.addEventListener('change', function() {
var path = this.value;
if (!path) return;
if (typeof _editor_load_vfs_file === 'function') {
/* Pass the path string to C */
var len = lengthBytesUTF8(path) + 1;
var buf = _malloc(len);
stringToUTF8(path, buf, len);
_editor_load_vfs_file(buf);
_free(buf);
}
/* Reset dropdown to placeholder */
this.selectedIndex = 0;
document.getElementById('canvas').focus();
});
/* Update title if in editor mode */
if (window.location.search.indexOf('edit') !== -1) {
document.title = 'Jump \'n Run - Level Editor';
}
</script>
{{{ SCRIPT }}}