Spaces:
Running
Running
File size: 7,362 Bytes
020d2dc 71eae78 0b13ced 71eae78 ec98146 020d2dc 71eae78 0b13ced 020d2dc ec98146 020d2dc 71eae78 2428d78 0b13ced 2428d78 71eae78 020d2dc 71eae78 0b13ced 71eae78 020d2dc ec98146 020d2dc 71eae78 ec98146 71eae78 ec98146 0b13ced 71eae78 020d2dc ec98146 0b13ced ec98146 7d93bf0 6f7f7b5 7d93bf0 6f7f7b5 45cc183 7d93bf0 1c99eda ec98146 6f7f7b5 ec98146 020d2dc ec98146 020d2dc | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 | // Torch is a HOLD, never a toggle. The cost has to be continuous.
//
// A / Left arrow walk left
// D / Right arrow walk right
// Space (hold) torch up, sword away, and he cannot move
// F swing
// E take the object lying at his feet
// Q (hold) his loose pages. Raises the torch with it — one button, one cost.
// It was called the map until somebody played it and said
// "I thought the first page was an inventory index" — which
// it should have been, and now is.
// R reset
// ` toggle the debug readout (handled in the renderer)
//
// On a touch screen there is no keyboard, so there is an on-screen pad in
// index.html: walk on the left, act on the right. Screen ZONES were tried first
// and could not carry it — the game needs two holds at once (walking and the
// torch) plus two taps, and a zone cannot tell you which one a thumb meant.
//
// Every button is its own element with its own captured pointer, so holding
// "walk right" and stabbing SWING with the other thumb both land.
export function createInput(target, { onReset } = {}) {
// `attack` and `pickup` are one-shots: the scene consumes them and clears them.
const state = { torch: false, attack: false, pickup: false, sheaf: false, left: false, right: false };
// Track the two hold sources separately so releasing the mouse does not cancel
// a key that is still held, and vice versa.
let keyHeld = false;
let pointerHeld = false;
let padHeld = false;
const sync = () => { state.torch = keyHeld || pointerHeld || padHeld; };
const onKeyDown = (e) => {
switch (e.code) {
case 'Space':
if (e.repeat) return;
e.preventDefault();
keyHeld = true;
sync();
break;
case 'KeyA': case 'ArrowLeft':
e.preventDefault();
state.left = true;
break;
case 'KeyD': case 'ArrowRight':
e.preventDefault();
state.right = true;
break;
case 'KeyF':
if (!e.repeat) state.attack = true;
break;
case 'KeyE':
if (!e.repeat) state.pickup = true;
break;
case 'KeyQ':
e.preventDefault();
state.sheaf = true;
break;
case 'KeyR':
onReset?.();
break;
default:
}
};
const onKeyUp = (e) => {
switch (e.code) {
case 'Space':
e.preventDefault();
keyHeld = false;
sync();
break;
case 'KeyA': case 'ArrowLeft': state.left = false; break;
case 'KeyD': case 'ArrowRight': state.right = false; break;
case 'KeyQ': state.sheaf = false; break;
default:
}
};
const onPointerDown = (e) => {
if (e.button !== undefined && e.button !== 0) return;
// A finger on the scene does nothing; the pad owns touch. Otherwise a thumb
// resting anywhere would silently hold the torch up.
if (e.pointerType === 'touch') return;
pointerHeld = true;
sync();
// Capture keeps a drag outside the canvas counted as still held. It throws
// for a pointer id the browser does not recognise, which never matters to
// the demo, so it is not worth failing the handler over.
try { target.setPointerCapture?.(e.pointerId); } catch { /* not capturable */ }
};
const onPointerUp = () => { pointerHeld = false; sync(); };
// Losing focus mid-hold would otherwise strand the torch in the up position,
// or leave him walking forever, because the keyup never arrives.
const releaseAll = () => {
keyHeld = false;
pointerHeld = false;
padHeld = false;
for (const b of document.querySelectorAll('#pad button.on')) b.classList.remove('on');
state.attack = false;
state.pickup = false;
state.sheaf = false;
state.left = false;
state.right = false;
sync();
};
window.addEventListener('keydown', onKeyDown);
window.addEventListener('keyup', onKeyUp);
window.addEventListener('blur', releaseAll);
document.addEventListener('visibilitychange', () => { if (document.hidden) releaseAll(); });
// --- the touch pad ----------------------------------------------------
const pad = document.getElementById('pad');
if (pad) {
const press = (act, down) => {
switch (act) {
case 'left': state.left = down; break;
case 'right': state.right = down; break;
case 'torch': padHeld = down; sync(); break;
case 'sheaf': state.sheaf = down; break;
// One-shots. Fire on the way down so the swing is not waiting for a
// thumb to lift, which reads as lag on a control you are mashing.
case 'attack': if (down) state.attack = true; break;
case 'pickup': if (down) state.pickup = true; break;
default:
}
};
for (const btn of pad.querySelectorAll('button')) {
const act = btn.dataset.act;
btn.addEventListener('pointerdown', (e) => {
e.preventDefault();
btn.classList.add('on');
// Capture, so sliding a thumb off the button still counts as held and
// still delivers the pointerup. Without it a walk can strand itself on.
try { btn.setPointerCapture?.(e.pointerId); } catch { /* not capturable */ }
press(act, true);
});
const release = (e) => {
if (e) e.preventDefault();
btn.classList.remove('on');
press(act, false);
};
btn.addEventListener('pointerup', release);
btn.addEventListener('pointercancel', release);
// Belt and braces for engines that drop a capture on scroll or a call.
btn.addEventListener('lostpointercapture', release);
btn.addEventListener('contextmenu', (e) => e.preventDefault());
}
const show = () => { pad.hidden = false; };
if (window.matchMedia?.('(hover: none) and (pointer: coarse)')?.matches) show();
// Some devices report a fine pointer and still only have a finger.
window.addEventListener('touchstart', show, { once: true, passive: true });
}
/**
* Lights TAKE only when there is something to do with it.
*
* ONE BUTTON, ONE SLOT, NO LABEL CHANGE. It used to be two: TAKE on the left
* wheel and a second MARK button over by the walk arrows, because pinning a
* page needed PAGES held and TAKE pressed and one thumb cannot do both.
*
* The top page of the pile is now what he is holding, so TAKE acts on it
* wherever he stands and the second button is deleted. Whether this press
* pins a page, takes one back down, takes a key or picks something off the
* ground is decided by where he is standing — which is what TAKE has meant
* everywhere else in this game since the first object. Letters are not on
* that list: they stay on the door.
*/
const takeBtn = pad?.querySelector('.wheel [data-act="pickup"]');
let takeLive = false;
const setTakeAvailable = (yes) => {
if (!takeBtn || yes === takeLive) return;
takeLive = yes;
takeBtn.classList.toggle('live', yes);
};
target.addEventListener('pointerdown', onPointerDown);
window.addEventListener('pointerup', onPointerUp);
window.addEventListener('pointercancel', onPointerUp);
target.addEventListener('contextmenu', (e) => e.preventDefault());
return { state, setTakeAvailable };
}
|