@the-inclusionist/engine 6.36.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.md +107 -0
- package/app/css/style.css +622 -0
- package/app/public/vendor/fonts/andika-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/andika-400.woff2 +0 -0
- package/app/public/vendor/fonts/andika-700-ext.woff2 +0 -0
- package/app/public/vendor/fonts/andika-700.woff2 +0 -0
- package/app/public/vendor/fonts/atkinson-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/atkinson-400.woff2 +0 -0
- package/app/public/vendor/fonts/atkinson-700-ext.woff2 +0 -0
- package/app/public/vendor/fonts/atkinson-700.woff2 +0 -0
- package/app/public/vendor/fonts/atkinson-mono-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/atkinson-mono-400.woff2 +0 -0
- package/app/public/vendor/fonts/comicneue-400.woff2 +0 -0
- package/app/public/vendor/fonts/comicneue-700.woff2 +0 -0
- package/app/public/vendor/fonts/greatvibes-400.woff2 +0 -0
- package/app/public/vendor/fonts/inter-400.woff2 +0 -0
- package/app/public/vendor/fonts/inter-700.woff2 +0 -0
- package/app/public/vendor/fonts/lato-400.woff2 +0 -0
- package/app/public/vendor/fonts/lato-700.woff2 +0 -0
- package/app/public/vendor/fonts/lexend-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/lexend-400.woff2 +0 -0
- package/app/public/vendor/fonts/lexend-700-ext.woff2 +0 -0
- package/app/public/vendor/fonts/lexend-700.woff2 +0 -0
- package/app/public/vendor/fonts/literata-400.woff2 +0 -0
- package/app/public/vendor/fonts/literata-700.woff2 +0 -0
- package/app/public/vendor/fonts/newsreader-400.woff2 +0 -0
- package/app/public/vendor/fonts/newsreader-700.woff2 +0 -0
- package/app/public/vendor/fonts/opensans-400.woff2 +0 -0
- package/app/public/vendor/fonts/opensans-700.woff2 +0 -0
- package/app/public/vendor/fonts/pinyon-400.woff2 +0 -0
- package/app/public/vendor/fonts/quattro-400.woff2 +0 -0
- package/app/public/vendor/fonts/quattro-700.woff2 +0 -0
- package/app/public/vendor/fonts/sourcesans-400.woff2 +0 -0
- package/app/public/vendor/fonts/sourcesans-700.woff2 +0 -0
- package/app/public/vendor/fonts/sourceserif-400.woff2 +0 -0
- package/app/public/vendor/fonts/sourceserif-700.woff2 +0 -0
- package/app/public/vendor/fonts/ufcook-700.woff2 +0 -0
- package/app/public/vendor/fonts/ufmag-400.woff2 +0 -0
- package/app/public/vendor/fonts.css +88 -0
- package/dist-pkg/boot/create-game.d.ts +159 -0
- package/dist-pkg/boot/create-game.js +266 -0
- package/dist-pkg/core/a11y-sr.d.ts +4 -0
- package/dist-pkg/core/a11y-sr.js +20 -0
- package/dist-pkg/core/actions.d.ts +100 -0
- package/dist-pkg/core/actions.js +149 -0
- package/dist-pkg/core/anel.d.ts +17 -0
- package/dist-pkg/core/anel.js +31 -0
- package/dist-pkg/core/collision.d.ts +21 -0
- package/dist-pkg/core/collision.js +62 -0
- package/dist-pkg/core/constants.d.ts +84 -0
- package/dist-pkg/core/constants.js +64 -0
- package/dist-pkg/core/contract.d.ts +227 -0
- package/dist-pkg/core/contract.js +184 -0
- package/dist-pkg/core/dom-query.d.ts +9 -0
- package/dist-pkg/core/dom-query.js +25 -0
- package/dist-pkg/core/entity.d.ts +150 -0
- package/dist-pkg/core/entity.js +38 -0
- package/dist-pkg/core/escape-html.d.ts +16 -0
- package/dist-pkg/core/escape-html.js +32 -0
- package/dist-pkg/core/i18n.d.ts +71 -0
- package/dist-pkg/core/i18n.js +236 -0
- package/dist-pkg/core/layers.d.ts +39 -0
- package/dist-pkg/core/layers.js +71 -0
- package/dist-pkg/core/letter-grid.d.ts +55 -0
- package/dist-pkg/core/letter-grid.js +116 -0
- package/dist-pkg/core/loop.d.ts +17 -0
- package/dist-pkg/core/loop.js +36 -0
- package/dist-pkg/core/password.d.ts +34 -0
- package/dist-pkg/core/password.js +205 -0
- package/dist-pkg/core/rng.d.ts +20 -0
- package/dist-pkg/core/rng.js +61 -0
- package/dist-pkg/core/rotulo-acessivel.d.ts +17 -0
- package/dist-pkg/core/rotulo-acessivel.js +41 -0
- package/dist-pkg/core/run-state.d.ts +115 -0
- package/dist-pkg/core/run-state.js +40 -0
- package/dist-pkg/core/scenes.d.ts +68 -0
- package/dist-pkg/core/scenes.js +64 -0
- package/dist-pkg/core/screens.d.ts +16 -0
- package/dist-pkg/core/screens.js +28 -0
- package/dist-pkg/core/state.d.ts +138 -0
- package/dist-pkg/core/state.js +290 -0
- package/dist-pkg/core/tiles.d.ts +12 -0
- package/dist-pkg/core/tiles.js +61 -0
- package/dist-pkg/core/world.d.ts +4 -0
- package/dist-pkg/core/world.js +58 -0
- package/dist-pkg/educational/activities-registry.d.ts +47 -0
- package/dist-pkg/educational/activities-registry.js +95 -0
- package/dist-pkg/i18n/en.d.ts +3 -0
- package/dist-pkg/i18n/en.js +560 -0
- package/dist-pkg/i18n/es.d.ts +3 -0
- package/dist-pkg/i18n/es.js +558 -0
- package/dist-pkg/i18n/pt.d.ts +3 -0
- package/dist-pkg/i18n/pt.js +618 -0
- package/dist-pkg/input/default-bindings.d.ts +24 -0
- package/dist-pkg/input/default-bindings.js +122 -0
- package/dist-pkg/input/devices.d.ts +15 -0
- package/dist-pkg/input/devices.js +31 -0
- package/dist-pkg/input/edges.d.ts +58 -0
- package/dist-pkg/input/edges.js +49 -0
- package/dist-pkg/input/gamepad.d.ts +203 -0
- package/dist-pkg/input/gamepad.js +531 -0
- package/dist-pkg/input/keyboard-runtime.d.ts +61 -0
- package/dist-pkg/input/keyboard-runtime.js +83 -0
- package/dist-pkg/input/keyboard.d.ts +45 -0
- package/dist-pkg/input/keyboard.js +80 -0
- package/dist-pkg/input/keydown.d.ts +270 -0
- package/dist-pkg/input/keydown.js +381 -0
- package/dist-pkg/input/latch.d.ts +30 -0
- package/dist-pkg/input/latch.js +63 -0
- package/dist-pkg/input/pointer-space.d.ts +40 -0
- package/dist-pkg/input/pointer-space.js +51 -0
- package/dist-pkg/input/state.d.ts +11 -0
- package/dist-pkg/input/state.js +12 -0
- package/dist-pkg/input/touch-bindings.d.ts +210 -0
- package/dist-pkg/input/touch-bindings.js +365 -0
- package/dist-pkg/input/touch.d.ts +173 -0
- package/dist-pkg/input/touch.js +314 -0
- package/dist-pkg/input/transports.d.ts +90 -0
- package/dist-pkg/input/transports.js +96 -0
- package/dist-pkg/input/vocabulary-migration.d.ts +58 -0
- package/dist-pkg/input/vocabulary-migration.js +125 -0
- package/dist-pkg/platform/audio-ambient.d.ts +27 -0
- package/dist-pkg/platform/audio-ambient.js +89 -0
- package/dist-pkg/platform/audio-earcons.d.ts +24 -0
- package/dist-pkg/platform/audio-earcons.js +62 -0
- package/dist-pkg/platform/audio-jingles.d.ts +17 -0
- package/dist-pkg/platform/audio-jingles.js +54 -0
- package/dist-pkg/platform/audio-mixer.d.ts +14 -0
- package/dist-pkg/platform/audio-mixer.js +62 -0
- package/dist-pkg/platform/audio-nav.d.ts +51 -0
- package/dist-pkg/platform/audio-nav.js +99 -0
- package/dist-pkg/platform/audio-sonar.d.ts +71 -0
- package/dist-pkg/platform/audio-sonar.js +173 -0
- package/dist-pkg/platform/audio.d.ts +32 -0
- package/dist-pkg/platform/audio.js +191 -0
- package/dist-pkg/platform/interruptible-speech.d.ts +19 -0
- package/dist-pkg/platform/interruptible-speech.js +79 -0
- package/dist-pkg/platform/speech.d.ts +2 -0
- package/dist-pkg/platform/speech.js +35 -0
- package/dist-pkg/platform/storage.d.ts +87 -0
- package/dist-pkg/platform/storage.js +148 -0
- package/dist-pkg/platform/tts.d.ts +33 -0
- package/dist-pkg/platform/tts.js +142 -0
- package/dist-pkg/render/camera.d.ts +58 -0
- package/dist-pkg/render/camera.js +105 -0
- package/dist-pkg/render/canvas.d.ts +13 -0
- package/dist-pkg/render/canvas.js +26 -0
- package/dist-pkg/render/cenario-data.d.ts +127 -0
- package/dist-pkg/render/cenario-data.js +145 -0
- package/dist-pkg/render/city-tex.d.ts +63 -0
- package/dist-pkg/render/city-tex.js +228 -0
- package/dist-pkg/render/city-tiles.d.ts +11 -0
- package/dist-pkg/render/city-tiles.js +140 -0
- package/dist-pkg/render/crt.d.ts +19 -0
- package/dist-pkg/render/crt.js +92 -0
- package/dist-pkg/render/cvd-matrices.d.ts +35 -0
- package/dist-pkg/render/cvd-matrices.js +91 -0
- package/dist-pkg/render/draw.d.ts +149 -0
- package/dist-pkg/render/draw.js +221 -0
- package/dist-pkg/render/fx.d.ts +63 -0
- package/dist-pkg/render/fx.js +123 -0
- package/dist-pkg/render/hc-role-data.d.ts +14 -0
- package/dist-pkg/render/hc-role-data.js +24 -0
- package/dist-pkg/render/high-contrast.d.ts +109 -0
- package/dist-pkg/render/high-contrast.js +254 -0
- package/dist-pkg/render/lq-filter.d.ts +37 -0
- package/dist-pkg/render/lq-filter.js +94 -0
- package/dist-pkg/render/minimap.d.ts +11 -0
- package/dist-pkg/render/minimap.js +94 -0
- package/dist-pkg/render/parallax.d.ts +85 -0
- package/dist-pkg/render/parallax.js +158 -0
- package/dist-pkg/render/player-anim.d.ts +71 -0
- package/dist-pkg/render/player-anim.js +152 -0
- package/dist-pkg/render/port.d.ts +143 -0
- package/dist-pkg/render/port.js +29 -0
- package/dist-pkg/render/recycling-tex.d.ts +48 -0
- package/dist-pkg/render/recycling-tex.js +164 -0
- package/dist-pkg/render/scene-city.d.ts +77 -0
- package/dist-pkg/render/scene-city.js +181 -0
- package/dist-pkg/render/scene-parallax.d.ts +72 -0
- package/dist-pkg/render/scene-parallax.js +426 -0
- package/dist-pkg/render/scene-sky.d.ts +126 -0
- package/dist-pkg/render/scene-sky.js +295 -0
- package/dist-pkg/render/screen-pipeline.d.ts +134 -0
- package/dist-pkg/render/screen-pipeline.js +177 -0
- package/dist-pkg/render/set-cenario.d.ts +54 -0
- package/dist-pkg/render/set-cenario.js +103 -0
- package/dist-pkg/render/sprite-fx.d.ts +3 -0
- package/dist-pkg/render/sprite-fx.js +40 -0
- package/dist-pkg/render/textures.d.ts +75 -0
- package/dist-pkg/render/textures.js +240 -0
- package/dist-pkg/render/title-scene.d.ts +46 -0
- package/dist-pkg/render/title-scene.js +75 -0
- package/dist-pkg/render/viewports.d.ts +52 -0
- package/dist-pkg/render/viewports.js +185 -0
- package/dist-pkg/render/viz-axes.d.ts +98 -0
- package/dist-pkg/render/viz-axes.js +184 -0
- package/dist-pkg/render/viz-modes.d.ts +61 -0
- package/dist-pkg/render/viz-modes.js +74 -0
- package/dist-pkg/render/viz-setters.d.ts +130 -0
- package/dist-pkg/render/viz-setters.js +244 -0
- package/dist-pkg/render/weather.d.ts +96 -0
- package/dist-pkg/render/weather.js +171 -0
- package/dist-pkg/render/wheelchair-sprites.d.ts +24 -0
- package/dist-pkg/render/wheelchair-sprites.js +63 -0
- package/dist-pkg/render/world-tex.d.ts +18 -0
- package/dist-pkg/render/world-tex.js +87 -0
- package/dist-pkg/ui/activities-menu.d.ts +258 -0
- package/dist-pkg/ui/activities-menu.js +648 -0
- package/dist-pkg/ui/caa-sets.d.ts +39 -0
- package/dist-pkg/ui/caa-sets.js +65 -0
- package/dist-pkg/ui/changed-mark.d.ts +15 -0
- package/dist-pkg/ui/changed-mark.js +96 -0
- package/dist-pkg/ui/debug-panel.d.ts +67 -0
- package/dist-pkg/ui/debug-panel.js +173 -0
- package/dist-pkg/ui/dom.d.ts +24 -0
- package/dist-pkg/ui/dom.js +47 -0
- package/dist-pkg/ui/focus-trap.d.ts +71 -0
- package/dist-pkg/ui/focus-trap.js +99 -0
- package/dist-pkg/ui/fonts.d.ts +41 -0
- package/dist-pkg/ui/fonts.js +59 -0
- package/dist-pkg/ui/hud.d.ts +137 -0
- package/dist-pkg/ui/hud.js +229 -0
- package/dist-pkg/ui/item-announcement.d.ts +19 -0
- package/dist-pkg/ui/item-announcement.js +36 -0
- package/dist-pkg/ui/layout.d.ts +6 -0
- package/dist-pkg/ui/layout.js +54 -0
- package/dist-pkg/ui/loop-crash.d.ts +19 -0
- package/dist-pkg/ui/loop-crash.js +60 -0
- package/dist-pkg/ui/map-hub.d.ts +56 -0
- package/dist-pkg/ui/map-hub.js +138 -0
- package/dist-pkg/ui/menu-nav.d.ts +141 -0
- package/dist-pkg/ui/menu-nav.js +390 -0
- package/dist-pkg/ui/pause-icons.d.ts +327 -0
- package/dist-pkg/ui/pause-icons.js +620 -0
- package/dist-pkg/ui/reach-notice.d.ts +34 -0
- package/dist-pkg/ui/reach-notice.js +71 -0
- package/dist-pkg/ui/settings-audio.d.ts +139 -0
- package/dist-pkg/ui/settings-audio.js +544 -0
- package/dist-pkg/ui/settings-caa.d.ts +45 -0
- package/dist-pkg/ui/settings-caa.js +137 -0
- package/dist-pkg/ui/settings-controls.d.ts +100 -0
- package/dist-pkg/ui/settings-controls.js +152 -0
- package/dist-pkg/ui/settings-empathy.d.ts +62 -0
- package/dist-pkg/ui/settings-empathy.js +137 -0
- package/dist-pkg/ui/settings-motion.d.ts +95 -0
- package/dist-pkg/ui/settings-motion.js +282 -0
- package/dist-pkg/ui/settings-motor.d.ts +77 -0
- package/dist-pkg/ui/settings-motor.js +215 -0
- package/dist-pkg/ui/settings-panel.d.ts +76 -0
- package/dist-pkg/ui/settings-panel.js +187 -0
- package/dist-pkg/ui/settings-typo.d.ts +74 -0
- package/dist-pkg/ui/settings-typo.js +150 -0
- package/dist-pkg/ui/settings-visual.d.ts +111 -0
- package/dist-pkg/ui/settings-visual.js +252 -0
- package/dist-pkg/ui/shell.d.ts +239 -0
- package/dist-pkg/ui/shell.js +430 -0
- package/dist-pkg/ui/title.d.ts +36 -0
- package/dist-pkg/ui/title.js +60 -0
- package/dist-pkg/ui/vlibras.d.ts +11 -0
- package/dist-pkg/ui/vlibras.js +87 -0
- package/dist-pkg/ui/webcam.d.ts +7 -0
- package/dist-pkg/ui/webcam.js +88 -0
- package/docs/CREDITS.md +64 -0
- package/docs/LICENSES.md +132 -0
- package/package.json +115 -0
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// input/gamepad.ts — gamepad READING (stdDirs/padActions/bindActive/padMapFor) + the poll dispatcher
|
|
3
|
+
// (pollPads) + the accessibility MAPPING WIZARD (openPadWiz/openPadWizFor/closePadWiz/padWizTick). Pure
|
|
4
|
+
// button->action logic is separated from the DI runtime (initGamepad(ctx)): the Gamepad API is injected
|
|
5
|
+
// (ctx.getGamepads, so tests feed a fake pad with no browser) and every cross-subsystem call (menu nav, quiz,
|
|
6
|
+
// join/respawn, phase, overlays) is injected too — never reaches `document`/`navigator` or imports game.js.
|
|
7
|
+
// Boundary: this module only READS buttons/axes and maps them to actions; the physical button ARTWORK/labels
|
|
8
|
+
// (PAD_DESIGNS) and the on-screen touch pad are input/devices.ts + input/touch (a parallel extraction) — not here.
|
|
9
|
+
import { t } from '../core/i18n.js';
|
|
10
|
+
import { EDGE_BY_ACTION, edgeAllowed } from './edges.js';
|
|
11
|
+
import { migrarMapaDeControle } from './vocabulary-migration.js';
|
|
12
|
+
import { GAMEPAD_STANDARD } from './default-bindings.js';
|
|
13
|
+
import { padCur, padPrevAct, padPrevStart, PAD_DEAD } from './state.js';
|
|
14
|
+
import * as store from '../platform/storage.js';
|
|
15
|
+
function bindingAt(map, key) {
|
|
16
|
+
const v = map[key];
|
|
17
|
+
return typeof v === 'object' && v !== null ? v : undefined;
|
|
18
|
+
}
|
|
19
|
+
// [axisValue, up, down, left, right] — os 8 passos de um D-pad "POV hat" (eixo alto do DirectInput), repouso ~1.286.
|
|
20
|
+
const HAT_STEPS = [
|
|
21
|
+
[-1, 1, 0, 0, 0], [-0.7143, 1, 0, 0, 1], [-0.4286, 0, 0, 0, 1], [-0.1429, 0, 1, 0, 1],
|
|
22
|
+
[0.1429, 0, 1, 0, 0], [0.4286, 0, 1, 1, 0], [0.7143, 0, 0, 1, 0], [1, 1, 0, 1, 0],
|
|
23
|
+
];
|
|
24
|
+
/** Direções pelas FONTES PADRÃO: stick 0/1 (zona morta PAD_DEAD), D-pad 12-15, e o "hat" nos eixos >=6 (POV do
|
|
25
|
+
* DirectInput). Controles com os dois direcionais mapeados ficam com ambos vivos (dedo no stick não mata o D-pad). */
|
|
26
|
+
export function stdDirs(gp) {
|
|
27
|
+
const b = (i) => !!(gp.buttons[i] && gp.buttons[i].pressed);
|
|
28
|
+
const ax = (i) => gp.axes[i] || 0;
|
|
29
|
+
const d = { left: ax(0) < -PAD_DEAD || b(14), right: ax(0) > PAD_DEAD || b(15), up: ax(1) < -PAD_DEAD || b(12), down: ax(1) > PAD_DEAD || b(13) };
|
|
30
|
+
for (let i = 6; i < gp.axes.length; i++) {
|
|
31
|
+
const v = gp.axes[i];
|
|
32
|
+
if (typeof v !== 'number' || Math.abs(v) > 1.001)
|
|
33
|
+
continue; // fora do repouso do hat
|
|
34
|
+
for (const [hv, u, dn, l, r] of HAT_STEPS) {
|
|
35
|
+
if (Math.abs(v - hv) <= 0.09) {
|
|
36
|
+
if (u)
|
|
37
|
+
d.up = true;
|
|
38
|
+
if (dn)
|
|
39
|
+
d.down = true;
|
|
40
|
+
if (l)
|
|
41
|
+
d.left = true;
|
|
42
|
+
if (r)
|
|
43
|
+
d.right = true;
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return d;
|
|
49
|
+
}
|
|
50
|
+
/** Está o binding `bd` ativo agora neste gamepad? Digital = pressed; analógico ({ax,s}) = limiar por sinal
|
|
51
|
+
* (metade do curso); hat ({av,v}) = valor exato do passo (±0.13 — os 8 passos distam ~0.286). */
|
|
52
|
+
export function bindActive(gp, bd) {
|
|
53
|
+
if (!bd)
|
|
54
|
+
return false;
|
|
55
|
+
if (bd.b != null)
|
|
56
|
+
return !!(gp.buttons[bd.b] && gp.buttons[bd.b].pressed);
|
|
57
|
+
if (bd.ax != null)
|
|
58
|
+
return ((gp.axes[bd.ax] || 0) * (bd.s ?? 0)) > 0.5;
|
|
59
|
+
if (bd.av != null)
|
|
60
|
+
return Math.abs((gp.axes[bd.av] || 0) - (bd.v ?? 0)) <= 0.13;
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
/** Ações do frame para este gamepad. `custom` = mapa salvo pelo wizard para este `gp.id` (null/`_skip` = usa o
|
|
64
|
+
* mapa PADRÃO da Gamepad API "standard": 0=pulo · 1=especial · 2/5/7=correr · 3=troca · 9=START). Direções
|
|
65
|
+
* custom caem de volta em stdDirs quando o binding do usuário não está ativo (D-pad/stick continuam vivos). */
|
|
66
|
+
export function padActions(gp, custom) {
|
|
67
|
+
if (custom && !custom._skip) {
|
|
68
|
+
const A = (k) => bindActive(gp, bindingAt(custom, k));
|
|
69
|
+
const sd = stdDirs(gp);
|
|
70
|
+
return {
|
|
71
|
+
left: A('left') || sd.left, right: A('right') || sd.right, up: A('up') || sd.up, down: A('down') || sd.down,
|
|
72
|
+
action2: A('action2'), action1: A('action1'), action4: A('action4'), action3: A('action3'), _start: A('action2') || A('start'), _pause: A('start'),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
const b = (i) => !!(gp.buttons[i] && gp.buttons[i].pressed);
|
|
76
|
+
const sd = stdDirs(gp);
|
|
77
|
+
// ⚠️ OS ÍNDICES SAEM DA TABELA DECLARADA, e não de literais aqui. Enquanto eram literais, esta linha e
|
|
78
|
+
// `input/default-bindings` DISCORDAVAM e nada notava — a mesma forma de defeito que o gate do toque
|
|
79
|
+
// apanhou: duas tabelas que concordam entre si não provam nada sobre um terceiro que as lê.
|
|
80
|
+
//
|
|
81
|
+
// ⚠️ E A DISCORDÂNCIA ERA REAL: aqui estava `action1: b(2) || b(5) || b(7)`, ou seja X, R1 e R2 todos a
|
|
82
|
+
// correr, enquanto a tabela declara R1 como `rightShoulder` e R2 como `rightTrigger`. O ADR-0086 registrou
|
|
83
|
+
// esta mudança como o asterisco do seu «zero movimento»: nenhum VERBO muda de botão, mas `run` perde dois
|
|
84
|
+
// dos seus três. Quem usava R1 para correr sente — e é o preço de os quatro ombros existirem.
|
|
85
|
+
const B = GAMEPAD_STANDARD;
|
|
86
|
+
const at = (a) => { const i = B[a]; return typeof i === 'number' ? b(i) : false; };
|
|
87
|
+
return {
|
|
88
|
+
left: sd.left, right: sd.right, up: sd.up, down: sd.down,
|
|
89
|
+
action1: at('action1'), action2: at('action2'), action3: at('action3'), action4: at('action4'),
|
|
90
|
+
leftShoulder: at('leftShoulder'), leftTrigger: at('leftTrigger'),
|
|
91
|
+
rightShoulder: at('rightShoulder'), rightTrigger: at('rightTrigger'),
|
|
92
|
+
// ⚠️ `start` COMO POSIÇÃO, e não só como os derivados abaixo. Faltava, e o gate da tabela foi quem
|
|
93
|
+
// o encontrou: quem quisesse saber «o START está apertado?» tinha de ler `_start`, que começa por
|
|
94
|
+
// underscore e quer dizer outra coisa (fecha diálogo, e aceita a ação 2 também).
|
|
95
|
+
start: at('start'), select: at('select'),
|
|
96
|
+
// `_start` e `_pause` são DERIVADOS e não posições: «fecha diálogo» aceita a ação 2 ou o START, «pausa»
|
|
97
|
+
// só o START. Ficam escritos aqui porque descrevem o que a raiz faz com duas posições, não uma terceira.
|
|
98
|
+
_start: at('action2') || at('start'), _pause: at('start'),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
// ---------------------------------------------------------------------------------------------
|
|
102
|
+
// Wizard data (a ORDEM dos passos + a animação de demonstração; as PALAVRAS vêm do jogo)
|
|
103
|
+
// ---------------------------------------------------------------------------------------------
|
|
104
|
+
/**
|
|
105
|
+
* A ORDEM em que o assistente pergunta. SÓ a ordem.
|
|
106
|
+
*
|
|
107
|
+
* ⚠️ ELA CARREGAVA AS PALAVRAS, EM PORTUGUÊS CRU, DENTRO DA ENGINE: `['action2', 'PULAR']`,
|
|
108
|
+
* `['action1', 'CORRER / INTERAGIR']`. Era o defeito do ADR-0074 na sua forma mais visível — não só
|
|
109
|
+
* vocabulário de plataforma dentro do motor, mas vocabulário de plataforma NUM IDIOMA SÓ, à frente de uma
|
|
110
|
+
* criança, num ficheiro que o pilar 3 obriga a ser localizável.
|
|
111
|
+
*
|
|
112
|
+
* A palavra vem agora de `ctx.rotuloDaAcao`, que o jogo fornece pelo seu preset. Aqui fica o que é mesmo da
|
|
113
|
+
* engine: a sequência em que se pergunta — direções primeiro, ação depois, sistema no fim —, que é uma
|
|
114
|
+
* decisão de ergonomia do assistente e não do jogo.
|
|
115
|
+
*
|
|
116
|
+
* ⚠️ E UMA AÇÃO QUE O JOGO NÃO NOMEIA NÃO É PERGUNTADA. `wizPrompt` salta-a, porque um jogo que não usa a
|
|
117
|
+
* posição não tem o que mapear nela — e perguntar produziria um passo mudo, ou pior, um passo a dizer
|
|
118
|
+
* `action7` em voz alta.
|
|
119
|
+
*/
|
|
120
|
+
export const PADWIZ_ORDER = [
|
|
121
|
+
// Direções primeiro: são o que a criança encontra sem pensar, e acertar as quatro dá confiança para as
|
|
122
|
+
// outras dez.
|
|
123
|
+
'up', 'down', 'left', 'right',
|
|
124
|
+
// O losango, na ordem em que o dedo o percorre neste projeto (ADR-0086 §2).
|
|
125
|
+
'action2', 'action1', 'action4', 'action3',
|
|
126
|
+
// ⚠️ OS QUATRO OMBROS FALTAVAM AQUI ATÉ 2026-09-06, e a falta era um buraco de acessibilidade e não uma
|
|
127
|
+
// omissão cosmética: o assistente existe PARA controles que não são «standard» — genéricos, adaptados,
|
|
128
|
+
// de uma mão —, e um jogo que declarasse `leftShoulder` não tinha por onde a criança o mapear. Cinco das
|
|
129
|
+
// quatorze posições eram inalcançáveis exatamente para quem mais precisa do assistente.
|
|
130
|
+
'leftShoulder', 'leftTrigger', 'rightShoulder', 'rightTrigger',
|
|
131
|
+
// Sistema por último: `start` e `select` costumam ser os botões mais pequenos e mais escondidos, e pedi-los
|
|
132
|
+
// no fim deixa a criança já habituada ao ritmo do assistente quando chega neles.
|
|
133
|
+
'start', 'select',
|
|
134
|
+
];
|
|
135
|
+
/** Demonstração animada de cada ação (frames reais do jogo) mostrada durante o passo correspondente do wizard. */
|
|
136
|
+
export const PADWIZ_ANIM = {
|
|
137
|
+
up: { seq: ['escada/0', 'escada/1'], hold: 9, cls: 'pw-up' },
|
|
138
|
+
down: { seq: ['escada/1', 'escada/0'], hold: 9, cls: 'pw-down' },
|
|
139
|
+
left: { seq: ['andar/0', 'andar/1', 'andar/2', 'andar/3', 'andar/4', 'andar/5', 'andar/6', 'andar/7'], hold: 4, cls: 'pw-left', flip: 1 },
|
|
140
|
+
right: { seq: ['andar/0', 'andar/1', 'andar/2', 'andar/3', 'andar/4', 'andar/5', 'andar/6', 'andar/7'], hold: 4, cls: 'pw-right' },
|
|
141
|
+
action2: { seq: ['pulo/0', 'pulo/0', 'pulo/1', 'pulo/1'], hold: 7, cls: 'pw-jump' },
|
|
142
|
+
action1: { seq: ['correr/0', 'correr/1', 'correr/2', 'correr/3'], hold: 3, cls: 'pw-run' },
|
|
143
|
+
action4: { fx: '👟 🕷️ 🎈 🐇 🦘', cls: 'pw-swap', noimg: 1 },
|
|
144
|
+
action3: { seq: ['idle/0', 'idle/1', 'idle/2', 'idle/3'], hold: 8, fx: '✨', cls: 'pw-especial' },
|
|
145
|
+
start: { fx: 'PAUSA', cls: 'pw-start', noimg: 1 },
|
|
146
|
+
};
|
|
147
|
+
export function initGamepad(ctx) {
|
|
148
|
+
const _padMaps = new Map(); // cache id -> mapa custom (evita reparsear JSON a cada frame)
|
|
149
|
+
let padWiz = null;
|
|
150
|
+
let padWizAutoResume = false; // wizard aberto automaticamente no meio do jogo -> retoma a fase ao fechar
|
|
151
|
+
let padWizAnim = null;
|
|
152
|
+
function padMapFor(id) {
|
|
153
|
+
// ⚠️ O MAPA SALVO PASSA PELO TRADUTOR NA LEITURA. Ele é indexado por AÇÃO — `{ jump: { b: 0 } }` — e um
|
|
154
|
+
// mapa gravado antes do ADR-0086 tem as chaves antigas, que `bindingAt` já não procura: o controle
|
|
155
|
+
// custom simplesmente pararia de responder, e o jogo cairia no mapa padrão sem dizer nada.
|
|
156
|
+
// É o mais caro dos três formatos a perder: um mapa destes existe porque alguém passou por um assistente
|
|
157
|
+
// de nove passos, botão a botão, quase sempre porque o controle NÃO é «standard» — e controles genéricos
|
|
158
|
+
// ou adaptados raramente são. Ver `input/vocabulary-migration.ts`.
|
|
159
|
+
if (!_padMaps.has(id)) {
|
|
160
|
+
_padMaps.set(id, migrarMapaDeControle(store.getJSON('incl_padmap_' + id, null)));
|
|
161
|
+
}
|
|
162
|
+
return _padMaps.get(id) ?? null;
|
|
163
|
+
}
|
|
164
|
+
function actionsFor(gp) { return padActions(gp, padMapFor(gp.id)); }
|
|
165
|
+
// ----- wizard: anúncio + demo animada (DOM-facing, thin) -----
|
|
166
|
+
function wizSay(t) {
|
|
167
|
+
const el = ctx.$('#padwiz-prompt');
|
|
168
|
+
if (el)
|
|
169
|
+
el.textContent = t;
|
|
170
|
+
ctx.srSay(t);
|
|
171
|
+
}
|
|
172
|
+
function wizDemo(k) {
|
|
173
|
+
const d = ctx.$('#padwiz-demo');
|
|
174
|
+
const img = ctx.$('#padwiz-demo-img');
|
|
175
|
+
const fx = ctx.$('#padwiz-demo-fx');
|
|
176
|
+
if (!d)
|
|
177
|
+
return;
|
|
178
|
+
const a = k ? PADWIZ_ANIM[k] : null;
|
|
179
|
+
d.className = a ? a.cls : '';
|
|
180
|
+
padWizAnim = null;
|
|
181
|
+
if (fx)
|
|
182
|
+
fx.textContent = (a && a.fx) || '';
|
|
183
|
+
if (img) {
|
|
184
|
+
img.style.display = a && a.noimg ? 'none' : '';
|
|
185
|
+
img.style.transform = a && a.flip ? 'scaleX(-1)' : '';
|
|
186
|
+
if (a && a.seq) {
|
|
187
|
+
img.src = ctx.spriteBase + a.seq[0] + '.png';
|
|
188
|
+
padWizAnim = { seq: a.seq, hold: a.hold || 6, t: 0 };
|
|
189
|
+
}
|
|
190
|
+
else if (!a)
|
|
191
|
+
img.src = ctx.spriteBase + 'idle/0.png';
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function wizDemoTick() {
|
|
195
|
+
if (!padWizAnim)
|
|
196
|
+
return;
|
|
197
|
+
const a = padWizAnim;
|
|
198
|
+
a.t++;
|
|
199
|
+
const img = ctx.$('#padwiz-demo-img');
|
|
200
|
+
if (img)
|
|
201
|
+
img.src = ctx.spriteBase + a.seq[Math.floor(a.t / a.hold) % a.seq.length] + '.png';
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Anda até o próximo passo que ESTE jogo usa, ou fecha se não houver mais.
|
|
205
|
+
*
|
|
206
|
+
* ⚠️ UMA FUNÇÃO SÓ, e antes eram duas com regras diferentes: `wizBind` incrementava e fechava no fim,
|
|
207
|
+
* `wizPrompt` saltava as não nomeadas. Depois do último passo nomeado o assistente ficava aberto a
|
|
208
|
+
* apontar para uma posição que o jogo não usa, e só fechava no tique seguinte — uma criança veria o
|
|
209
|
+
* assistente pendurado, sem pergunta nenhuma na tela.
|
|
210
|
+
*/
|
|
211
|
+
function wizAvancar() {
|
|
212
|
+
if (!padWiz)
|
|
213
|
+
return;
|
|
214
|
+
while (padWiz.step < PADWIZ_ORDER.length && !ctx.rotuloDaAcao(PADWIZ_ORDER[padWiz.step]))
|
|
215
|
+
padWiz.step++;
|
|
216
|
+
if (padWiz.step >= PADWIZ_ORDER.length)
|
|
217
|
+
closePadWiz(true);
|
|
218
|
+
}
|
|
219
|
+
function wizPrompt() {
|
|
220
|
+
if (!padWiz)
|
|
221
|
+
return;
|
|
222
|
+
wizAvancar();
|
|
223
|
+
if (!padWiz)
|
|
224
|
+
return; // fechou ao avançar
|
|
225
|
+
const acao = PADWIZ_ORDER[padWiz.step];
|
|
226
|
+
const rotulo = ctx.rotuloDaAcao(acao);
|
|
227
|
+
wizSay((padWiz.step + 1) + ' de ' + PADWIZ_ORDER.length + ' — aperte: ' + rotulo);
|
|
228
|
+
wizDemo(acao); // demonstração animada do que a ação FAZ
|
|
229
|
+
const pr = ctx.$('#padwiz-progress');
|
|
230
|
+
if (pr)
|
|
231
|
+
pr.textContent = 'Mapeados: ' + (Object.keys(padWiz.map).join(' · ') || '—');
|
|
232
|
+
}
|
|
233
|
+
function wizBind(bd) {
|
|
234
|
+
if (!padWiz)
|
|
235
|
+
return;
|
|
236
|
+
padWiz.map[PADWIZ_ORDER[padWiz.step]] = bd;
|
|
237
|
+
padWiz.step++;
|
|
238
|
+
padWiz.release = true; // exige soltar antes do próximo passo (mesmo botão segurado não dobra pro passo seguinte)
|
|
239
|
+
wizAvancar(); // salta o que este jogo não usa, e fecha se o resto da lista for tudo isso
|
|
240
|
+
}
|
|
241
|
+
// ----- wizard: abrir/fechar -----
|
|
242
|
+
function openPadWiz() {
|
|
243
|
+
const ov = ctx.$('#padwiz');
|
|
244
|
+
if (!ov)
|
|
245
|
+
return;
|
|
246
|
+
ov.hidden = false;
|
|
247
|
+
ctx.frontOverlay(ov);
|
|
248
|
+
padWiz = { gi: -1, id: '', step: -1, base: null, map: {}, release: false, baseWait: false, axTrack: null, timer: null };
|
|
249
|
+
wizSay('Aperte QUALQUER botão no controle que deseja mapear.');
|
|
250
|
+
wizDemo(null);
|
|
251
|
+
const pr = ctx.$('#padwiz-progress');
|
|
252
|
+
if (pr)
|
|
253
|
+
pr.textContent = '';
|
|
254
|
+
padWiz.timer = setInterval(padWizTick, 30);
|
|
255
|
+
}
|
|
256
|
+
// Wizard aberto AUTOMATICAMENTE (controle DirectInput sem mapa apertou algo): já sabemos qual controle é.
|
|
257
|
+
function openPadWizFor(gp) {
|
|
258
|
+
const ov = ctx.$('#padwiz');
|
|
259
|
+
if (!ov)
|
|
260
|
+
return;
|
|
261
|
+
ov.hidden = false;
|
|
262
|
+
ctx.frontOverlay(ov);
|
|
263
|
+
padWiz = { gi: gp.index, id: gp.id, step: -1, base: null, map: {}, release: false, baseWait: true, axTrack: null, timer: null };
|
|
264
|
+
wizSay('Controle novo detectado: ' + gp.id + '. O jogo pausou para você configurá-lo. SOLTE tudo para começar.');
|
|
265
|
+
wizDemo(null);
|
|
266
|
+
const pr = ctx.$('#padwiz-progress');
|
|
267
|
+
if (pr)
|
|
268
|
+
pr.textContent = '';
|
|
269
|
+
padWiz.timer = setInterval(padWizTick, 30);
|
|
270
|
+
}
|
|
271
|
+
function closePadWiz(save) {
|
|
272
|
+
if (!padWiz)
|
|
273
|
+
return;
|
|
274
|
+
if (padWiz.timer != null)
|
|
275
|
+
clearInterval(padWiz.timer);
|
|
276
|
+
if (save && padWiz.id) {
|
|
277
|
+
store.setJSON('incl_padmap_' + padWiz.id, padWiz.map);
|
|
278
|
+
_padMaps.set(padWiz.id, padWiz.map);
|
|
279
|
+
ctx.srAlert(t('sr.pad.mapSaved', { id: padWiz.id }));
|
|
280
|
+
}
|
|
281
|
+
else if (padWiz.id && !_padMaps.get(padWiz.id)) {
|
|
282
|
+
_padMaps.set(padWiz.id, { _skip: true }); // cancelou: usa o mapa PADRÃO nesta sessão (não persiste, evita reabrir em loop)
|
|
283
|
+
}
|
|
284
|
+
const gi = padWiz.gi;
|
|
285
|
+
padWiz = null;
|
|
286
|
+
const ov = ctx.$('#padwiz');
|
|
287
|
+
if (ov)
|
|
288
|
+
ov.hidden = true;
|
|
289
|
+
// sem edges fantasmas: o botão ainda SEGURADO do último passo (START) não pode pausar/agir ao retomar
|
|
290
|
+
try {
|
|
291
|
+
const pads = ctx.getGamepads() ?? [];
|
|
292
|
+
const gp = pads[gi];
|
|
293
|
+
if (gp) {
|
|
294
|
+
const c = actionsFor(gp);
|
|
295
|
+
padCur[gi] = c;
|
|
296
|
+
padPrevAct[gi] = c;
|
|
297
|
+
padPrevStart[gi] = c._start;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
catch { /* espelha o try/catch silencioso do original */ }
|
|
301
|
+
if (padWizAutoResume) {
|
|
302
|
+
padWizAutoResume = false;
|
|
303
|
+
if (ctx.menuDePausa())
|
|
304
|
+
ctx.retomar();
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
// ----- wizard: tick (roda a cada 30ms enquanto aberto) -----
|
|
308
|
+
function padWizTick() {
|
|
309
|
+
if (!padWiz)
|
|
310
|
+
return;
|
|
311
|
+
wizDemoTick();
|
|
312
|
+
const pads = ctx.getGamepads() ?? [];
|
|
313
|
+
if (padWiz.gi < 0) {
|
|
314
|
+
for (const gp of pads) {
|
|
315
|
+
if (gp && gp.buttons.some((b) => b && b.pressed)) {
|
|
316
|
+
padWiz.gi = gp.index;
|
|
317
|
+
padWiz.id = gp.id;
|
|
318
|
+
padWiz.baseWait = true;
|
|
319
|
+
wizSay('Controle: ' + gp.id + '. Agora SOLTE tudo.');
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
const gp = pads[padWiz.gi];
|
|
326
|
+
if (!gp)
|
|
327
|
+
return; // controle desconectado (ou índice ainda não populado): congela até voltar
|
|
328
|
+
if (padWiz.baseWait) {
|
|
329
|
+
if (!gp.buttons.some((b) => b && b.pressed)) {
|
|
330
|
+
padWiz.baseWait = false;
|
|
331
|
+
padWiz.base = { b: gp.buttons.map((x) => !!(x && x.pressed)), a: gp.axes.slice() };
|
|
332
|
+
padWiz.step = 0;
|
|
333
|
+
wizPrompt();
|
|
334
|
+
}
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
const base = padWiz.base;
|
|
338
|
+
if (padWiz.release) {
|
|
339
|
+
const idle = !gp.buttons.some((b, i) => b && b.pressed && !base.b[i]) && gp.axes.every((v, i) => Math.abs((v || 0) - base.a[i]) < 0.35);
|
|
340
|
+
if (idle) {
|
|
341
|
+
padWiz.release = false;
|
|
342
|
+
wizPrompt();
|
|
343
|
+
}
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
// eixo em rastreio (~240ms): classifica pelo COMPORTAMENTO — varia continuamente = analógico (limiar por
|
|
347
|
+
// sinal); salta e FICA CONSTANTE = D-pad/POV hat (valor exato, ±0.13).
|
|
348
|
+
if (padWiz.axTrack) {
|
|
349
|
+
const t = padWiz.axTrack;
|
|
350
|
+
const v = gp.axes[t.i] || 0;
|
|
351
|
+
if (Math.abs(v - t.last) > 0.03)
|
|
352
|
+
t.changes++;
|
|
353
|
+
t.last = v;
|
|
354
|
+
if (Math.abs(v - base.a[t.i]) > Math.abs(t.v - base.a[t.i]))
|
|
355
|
+
t.v = v;
|
|
356
|
+
if (++t.ticks >= 8) {
|
|
357
|
+
const pv = t.v;
|
|
358
|
+
padWiz.axTrack = null;
|
|
359
|
+
wizBind(t.changes >= 2 ? { ax: t.i, s: pv > 0 ? 1 : -1 } : { av: t.i, v: Math.round(pv * 10000) / 10000 });
|
|
360
|
+
}
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
for (let i = 0; i < gp.buttons.length; i++) {
|
|
364
|
+
if (gp.buttons[i] && gp.buttons[i].pressed && !base.b[i]) {
|
|
365
|
+
wizBind({ b: i });
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
for (let i = 0; i < gp.axes.length; i++) {
|
|
370
|
+
const v = gp.axes[i] || 0;
|
|
371
|
+
if (Math.abs(v - base.a[i]) > 0.45) {
|
|
372
|
+
padWiz.axTrack = { i, v, last: v, changes: 0, ticks: 0 };
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
const cancelBtn = ctx.$('#padwiz-cancel');
|
|
378
|
+
if (cancelBtn)
|
|
379
|
+
cancelBtn.addEventListener('click', () => closePadWiz(false));
|
|
380
|
+
// ----- poll (chamado a cada frame do loop) -----
|
|
381
|
+
function pollPads() {
|
|
382
|
+
if (padWiz)
|
|
383
|
+
return; // durante o wizard, os pads falam só com ele
|
|
384
|
+
const pads = ctx.getGamepads();
|
|
385
|
+
if (!pads)
|
|
386
|
+
return;
|
|
387
|
+
if (ctx.isAttractActive()) {
|
|
388
|
+
for (const gp of pads) {
|
|
389
|
+
if (gp && gp.buttons.some((b) => b && b.pressed)) {
|
|
390
|
+
ctx.stopAttract();
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
} // botão de pad encerra a demo
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
for (const gp of pads) {
|
|
397
|
+
if (!gp)
|
|
398
|
+
continue;
|
|
399
|
+
const gi = gp.index;
|
|
400
|
+
// controle fora do padrão (DirectInput) SEM mapa salvo apertou algo -> pausa geral + wizard direto
|
|
401
|
+
if (gp.mapping !== 'standard' && !padMapFor(gp.id) && gp.buttons.some((b) => b && b.pressed)) {
|
|
402
|
+
padWizAutoResume = ctx.mundoRodando();
|
|
403
|
+
if (ctx.mundoRodando())
|
|
404
|
+
ctx.pausar();
|
|
405
|
+
openPadWizFor(gp);
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
const cur = actionsFor(gp);
|
|
409
|
+
const prev = padPrevAct[gi] || {};
|
|
410
|
+
if (ctx.isTouchMode() && (cur.left || cur.right || cur.up || cur.down || cur.action2 || cur.action1 || cur.action4 || cur.action3 || cur._start)) {
|
|
411
|
+
ctx.hideTouchControls(); // botão físico usado -> some o gamepad virtual (mesma regra do teclado)
|
|
412
|
+
}
|
|
413
|
+
const startEdge = cur._start && !padPrevStart[gi];
|
|
414
|
+
padPrevStart[gi] = cur._start;
|
|
415
|
+
const pauseEdge = cur._pause && !prev._pause;
|
|
416
|
+
const edge = (k) => cur[k] && !prev[k];
|
|
417
|
+
padCur[gi] = cur;
|
|
418
|
+
padPrevAct[gi] = cur;
|
|
419
|
+
const winOv = ctx.$('#win-overlay');
|
|
420
|
+
if (winOv && !winOv.hidden) { // Vitória: START/pulo fecham o modal (Jogar de novo)
|
|
421
|
+
if (startEdge) {
|
|
422
|
+
const b = ctx.$('#btn-again');
|
|
423
|
+
if (b)
|
|
424
|
+
b.click();
|
|
425
|
+
}
|
|
426
|
+
continue;
|
|
427
|
+
}
|
|
428
|
+
// Os três ramos abaixo pediam a FASE; hoje pedem os fatos. O `telaDeTitulo` é derivado por exclusão
|
|
429
|
+
// de propósito: numa cena que este módulo não conheça (um mapa, uma tela de resultados), o controle
|
|
430
|
+
// deve navegar como no título — que é o comportamento seguro — em vez de não fazer nada.
|
|
431
|
+
const rodando = ctx.mundoRodando(), pausado = ctx.menuDePausa();
|
|
432
|
+
const players = ctx.getPlayers();
|
|
433
|
+
if (!rodando && !pausado) {
|
|
434
|
+
const k = { yes: edge('action2') || startEdge, no: edge('action3'), up: edge('up'), down: edge('down'), left: edge('left'), right: edge('right') };
|
|
435
|
+
const any = k.yes || k.no || k.up || k.down || k.left || k.right;
|
|
436
|
+
const owner = players.findIndex((p) => p.pad === gi);
|
|
437
|
+
if (ctx.getNumPlayers() > 1 && owner > 0) {
|
|
438
|
+
if (any)
|
|
439
|
+
ctx.srSay(t('sr.title.waitP1'));
|
|
440
|
+
continue;
|
|
441
|
+
} // só o J1 escolhe
|
|
442
|
+
if (any)
|
|
443
|
+
ctx.navTitle(k); // menu inicial navegável pelo pad
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
if (pausado) {
|
|
447
|
+
const owner = players.findIndex((p) => p.pad === gi);
|
|
448
|
+
const pi = owner < 0 ? 0 : owner;
|
|
449
|
+
if (pauseEdge) {
|
|
450
|
+
ctx.retomar();
|
|
451
|
+
continue;
|
|
452
|
+
} // START retoma
|
|
453
|
+
const k = { yes: edge('action2'), no: edge('action3'), up: edge('up'), down: edge('down'), left: edge('left'), right: edge('right') };
|
|
454
|
+
if (k.yes || k.no || k.up || k.down || k.left || k.right) {
|
|
455
|
+
const dlg = ctx.sharedDialogOpen();
|
|
456
|
+
if (dlg)
|
|
457
|
+
ctx.navDialog(dlg, k);
|
|
458
|
+
else {
|
|
459
|
+
const menu = ctx.getPauseMenu(pi);
|
|
460
|
+
if (menu && !menu.hidden)
|
|
461
|
+
ctx.navPause(menu, pi, k);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
466
|
+
if (rodando) {
|
|
467
|
+
const owner = players.findIndex((p) => p.pad === gi);
|
|
468
|
+
// ===================== O MODO `accessibility` (ADR-0044, item 7) =====================
|
|
469
|
+
// Com o jogo ANDANDO, o direcional deste jogador dirige a BARRA RÁPIDA e não o personagem. Vem antes
|
|
470
|
+
// de tudo o que é de jogo, porque enquanto o modo está ligado nada mais deste controle é de jogo.
|
|
471
|
+
//
|
|
472
|
+
// As DUAS saídas chegam juntas: `especial` é o VOLTAR do projeto (X no PlayStation, B no Xbox, A no
|
|
473
|
+
// Nintendo) e `startEdge` é o botão que abre a pausa — de onde se entrou aqui. Quem se perde tenta
|
|
474
|
+
// voltar por onde veio, e quem já conhece o jogo tenta o voltar de sempre; as duas dão certo.
|
|
475
|
+
if (owner >= 0 && ctx.naBarraDe(owner)) {
|
|
476
|
+
const k = { yes: edge('action2'), no: edge('action3'), up: edge('up'), down: edge('down'), left: edge('left'), right: edge('right') };
|
|
477
|
+
if (startEdge || k.yes || k.no || k.up || k.down || k.left || k.right)
|
|
478
|
+
ctx.navBar(owner, k, !!startEdge);
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
if (owner < 0) { // atribuição POR ORDEM DE AÇÃO: qualquer botão associa -> 1º controle a agir -> 1º jogador sem pad
|
|
482
|
+
const anyEdge = edge('action2') || edge('action1') || edge('action4') || edge('action3') || startEdge || edge('left') || edge('right') || edge('up') || edge('down');
|
|
483
|
+
if (anyEdge) {
|
|
484
|
+
const waitI = players.findIndex((p) => p && p.waiting);
|
|
485
|
+
const free = waitI >= 0 ? waitI : players.findIndex((p) => p && p.pad < 0 && !p.quit);
|
|
486
|
+
if (free >= 0) {
|
|
487
|
+
players[free].pad = gi;
|
|
488
|
+
if (players[free].waiting) {
|
|
489
|
+
players[free].waiting = false;
|
|
490
|
+
ctx.clearWaitingBadge(free);
|
|
491
|
+
}
|
|
492
|
+
ctx.srSay(t('sr.pad.assigned', { n: free + 1 }));
|
|
493
|
+
}
|
|
494
|
+
else {
|
|
495
|
+
ctx.joinPlayer(gi);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
else if (players[owner].quit) {
|
|
500
|
+
if (startEdge)
|
|
501
|
+
ctx.respawnPlayer(owner); // tela abandonada -> recomeça SÓ ela
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
const p = players[owner];
|
|
505
|
+
if (pauseEdge) {
|
|
506
|
+
ctx.pausar();
|
|
507
|
+
ctx.setPauseActor(owner);
|
|
508
|
+
continue;
|
|
509
|
+
} // START pausa (todos pausam; cada tela navega a sua)
|
|
510
|
+
if (ctx.hasModal(owner)) { // o pad navega o modal do PRÓPRIO jogador (o jogo dos outros segue)
|
|
511
|
+
// A ORDEM é a do original: esquerda, direita, cima, baixo, confirmar, apagar. O que saiu foi o
|
|
512
|
+
// SIGNIFICADO — o ±1/±3 da grade e o desvio de Braille, que agora são decisão do jogo.
|
|
513
|
+
const intent = edge('left') ? 'left' : edge('right') ? 'right'
|
|
514
|
+
: edge('up') ? 'up' : edge('down') ? 'down'
|
|
515
|
+
: edge('action2') ? 'confirm' : edge('action3') ? 'erase' : null;
|
|
516
|
+
if (intent)
|
|
517
|
+
ctx.modalInput(owner, intent);
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
// A tabela e a guarda do Fácil vêm de input/edges.ts, as MESMAS que keydown e touch usam. Antes eram
|
|
521
|
+
// seis `if` à mão aqui, seis lá e seis no toque — e o do toque tinha esquecido o `!p.easy`.
|
|
522
|
+
for (const [act, flag] of EDGE_BY_ACTION) {
|
|
523
|
+
if (edgeAllowed(act, p.easy) && edge(act))
|
|
524
|
+
p[flag] = true;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
return { pollPads, openPadWiz, openPadWizFor, closePadWiz, padWizTick, padMapFor, getPadWiz: () => padWiz };
|
|
531
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
import type { ControlledPlayer } from '../core/entity.js';
|
|
3
|
+
import type { KeyScheme } from '../core/entity.js';
|
|
4
|
+
import type { KBDefaults } from '../input/keyboard.js';
|
|
5
|
+
/** action -> list of physical key codes (KeyboardEvent.code), e.g. {jump:['KeyJ','Space']}. Mirrors the shape
|
|
6
|
+
* ui/settings-controls.ts also defines locally (input/keyboard.ts's KeyScheme is not exported — each consumer
|
|
7
|
+
* keeps its own structural copy rather than reaching across layers for a type alias). */
|
|
8
|
+
export type { KeyScheme } from '../core/entity.js';
|
|
9
|
+
/** input/keyboard.ts's KBDefaults shape ({solo,p2,p3,p4}) — the live `KB` value in game.js. */
|
|
10
|
+
/** O `KBDefaults` de `input/keyboard`, que é o dono. O nome local sobrevive porque os consumidores o usam. */
|
|
11
|
+
export type KeyboardConfig = KBDefaults;
|
|
12
|
+
/** Minimal player shape this module needs: only `ctrl` is read/written — derived from core/entity.
|
|
13
|
+
* `ControlledPlayer` rather than `Player` because this module runs after assignControls, so `ctrl` is no
|
|
14
|
+
* longer null. That was already assumed here; now it is stated. */
|
|
15
|
+
export type KeyboardRuntimePlayer = Pick<ControlledPlayer, 'ctrl'>;
|
|
16
|
+
export interface KeyboardRuntimeCtx {
|
|
17
|
+
/** The live keyboard config (game.js's `KB`), read fresh on every call — never cached. */
|
|
18
|
+
getKB(): KeyboardConfig;
|
|
19
|
+
/** Current player count (core/state.ts's `numPlayers`, read live via game.js). */
|
|
20
|
+
getNumPlayers(): number;
|
|
21
|
+
/** The live players array (core/state.ts's `players`, mutated in place — never reassigned). */
|
|
22
|
+
getPlayers(): KeyboardRuntimePlayer[];
|
|
23
|
+
}
|
|
24
|
+
/** Result of the old `applyControls()` mutation, as a value: `controls` + its per-action aliases (game.js's
|
|
25
|
+
* KJUMP/KLEFT/KRIGHT/KUP/KDOWN/KRUN) + the flattened `GAME_KEYS` list. */
|
|
26
|
+
export interface ControlsState {
|
|
27
|
+
controls: KeyScheme;
|
|
28
|
+
action2: string[];
|
|
29
|
+
left: string[];
|
|
30
|
+
right: string[];
|
|
31
|
+
up: string[];
|
|
32
|
+
down: string[];
|
|
33
|
+
action1: string[];
|
|
34
|
+
gameKeys: string[];
|
|
35
|
+
}
|
|
36
|
+
export interface KeyboardRuntime {
|
|
37
|
+
/** The key scheme for player `playerIndex`, given the current KB config + player count (game.js's kbFor). */
|
|
38
|
+
kbFor(playerIndex: number): KeyScheme;
|
|
39
|
+
/** Which action (if any) `code` triggers FOR that player, given their scheme (game.js's actionOf). */
|
|
40
|
+
actionOf(code: string, playerIndex: number): string | null;
|
|
41
|
+
/** Which player (0-based) owns `code` among the active players; -1 if none (game.js's whichPlayer). */
|
|
42
|
+
whichPlayer(code: string): number;
|
|
43
|
+
/** Propagates `kb` -> each active player's `p.ctrl`, mutated in place (game.js's assignControls). */
|
|
44
|
+
assignControls(): void;
|
|
45
|
+
/** Computes the P1-alias `controls` + `GAME_KEYS` (game.js's applyControls, minus the reassignment). */
|
|
46
|
+
computeControlsState(): ControlsState;
|
|
47
|
+
/** O estado ATUAL, memorizado. Antes eram oito `let` no game.js (`controls`, `KJUMP`..`KRUN`, `GAME_KEYS`)
|
|
48
|
+
* copiados de `computeControlsState()` por um `applyControls()` que existia só para fazer a cópia. Eram
|
|
49
|
+
* derivados de `KB` + `players`, guardados em variável — a forma de estado que mais apodrece, porque nada
|
|
50
|
+
* obriga a cópia a acompanhar a origem. Aqui a memória fica com quem é dono da conta, e a invalidação é
|
|
51
|
+
* explícita e única: `refreshControls()`. NÃO recalcula a cada leitura de propósito — recalcular mudaria
|
|
52
|
+
* o comportamento (passaria a enxergar remapeamento que ainda não foi aplicado), e isto é refatoração. */
|
|
53
|
+
controlsState(): ControlsState;
|
|
54
|
+
/** Recalcula e memoriza. É o `applyControls()` do game.js, agora do lado de cá. */
|
|
55
|
+
refreshControls(): ControlsState;
|
|
56
|
+
}
|
|
57
|
+
/** Which action does `code` trigger in this ONE scheme? Pure — no player/KB involved. */
|
|
58
|
+
export declare function actionForCode(scheme: KeyScheme, code: string): string | null;
|
|
59
|
+
/** Which player owns `code`, given one scheme per player (index = player index)? -1 if none. Pure. */
|
|
60
|
+
export declare function ownerOfCode(schemesByPlayer: readonly KeyScheme[], code: string): number;
|
|
61
|
+
export declare function initKeyboardRuntime(ctx: KeyboardRuntimeCtx): KeyboardRuntime;
|