@muhammedaksam/opentui-doom 0.3.5 → 0.3.7
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/README.md +8 -0
- package/package.json +13 -6
- package/src/debug.ts +3 -3
- package/src/doom-audio.ts +146 -146
- package/src/doom-engine.ts +299 -304
- package/src/doom-input.ts +173 -173
- package/src/doom-mouse.ts +106 -106
- package/src/doom-saves.ts +60 -60
- package/src/index.ts +27 -28
package/src/doom-input.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* DOOM Input Handler
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Maps OpenTUI keyboard events to DOOM key codes
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -9,47 +9,47 @@ import type { DoomEngine } from "./doom-engine";
|
|
|
9
9
|
|
|
10
10
|
// DOOM key codes (from doomkeys.h)
|
|
11
11
|
export const DoomKeys = {
|
|
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
|
-
|
|
12
|
+
KEY_RIGHTARROW: 0xae,
|
|
13
|
+
KEY_LEFTARROW: 0xac,
|
|
14
|
+
KEY_UPARROW: 0xad,
|
|
15
|
+
KEY_DOWNARROW: 0xaf,
|
|
16
|
+
KEY_STRAFE_L: 0xa0,
|
|
17
|
+
KEY_STRAFE_R: 0xa1,
|
|
18
|
+
KEY_USE: 0xa2,
|
|
19
|
+
KEY_FIRE: 0xa3,
|
|
20
|
+
KEY_ESCAPE: 27,
|
|
21
|
+
KEY_ENTER: 13,
|
|
22
|
+
KEY_TAB: 9,
|
|
23
|
+
KEY_F1: 0x80 + 0x3b,
|
|
24
|
+
KEY_F2: 0x80 + 0x3c,
|
|
25
|
+
KEY_F3: 0x80 + 0x3d,
|
|
26
|
+
KEY_F4: 0x80 + 0x3e,
|
|
27
|
+
KEY_F5: 0x80 + 0x3f,
|
|
28
|
+
KEY_F6: 0x80 + 0x40,
|
|
29
|
+
KEY_F7: 0x80 + 0x41,
|
|
30
|
+
KEY_F8: 0x80 + 0x42,
|
|
31
|
+
KEY_F9: 0x80 + 0x43,
|
|
32
|
+
KEY_F10: 0x80 + 0x44,
|
|
33
|
+
KEY_F11: 0x80 + 0x57,
|
|
34
|
+
KEY_F12: 0x80 + 0x58,
|
|
35
|
+
KEY_BACKSPACE: 127,
|
|
36
|
+
KEY_PAUSE: 0xff,
|
|
37
|
+
KEY_EQUALS: 0x3d,
|
|
38
|
+
KEY_MINUS: 0x2d,
|
|
39
|
+
KEY_RSHIFT: 0x80 + 0x36,
|
|
40
|
+
KEY_RCTRL: 0x80 + 0x1d,
|
|
41
|
+
KEY_RALT: 0x80 + 0x38,
|
|
42
|
+
KEY_LALT: 0x80 + 0x38,
|
|
43
|
+
KEY_CAPSLOCK: 0x80 + 0x3a,
|
|
44
|
+
KEY_NUMLOCK: 0x80 + 0x45,
|
|
45
|
+
KEY_SCRLCK: 0x80 + 0x46,
|
|
46
|
+
KEY_PRTSCR: 0x80 + 0x59,
|
|
47
|
+
KEY_HOME: 0x80 + 0x47,
|
|
48
|
+
KEY_END: 0x80 + 0x4f,
|
|
49
|
+
KEY_PGUP: 0x80 + 0x49,
|
|
50
|
+
KEY_PGDN: 0x80 + 0x51,
|
|
51
|
+
KEY_INS: 0x80 + 0x52,
|
|
52
|
+
KEY_DEL: 0x80 + 0x53,
|
|
53
53
|
} as const;
|
|
54
54
|
|
|
55
55
|
// Key state tracking for press/release
|
|
@@ -62,68 +62,68 @@ const keyStates = new Map<string, boolean>();
|
|
|
62
62
|
* so both gameplay movement and text input work.
|
|
63
63
|
*/
|
|
64
64
|
function mapKeyToDoom(key: KeyEvent): number[] {
|
|
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
|
-
|
|
65
|
+
const name = key.name?.toLowerCase() ?? "";
|
|
66
|
+
|
|
67
|
+
// Arrow keys
|
|
68
|
+
if (name === "up" || key.sequence === "\x1b[A") return [DoomKeys.KEY_UPARROW];
|
|
69
|
+
if (name === "down" || key.sequence === "\x1b[B") return [DoomKeys.KEY_DOWNARROW];
|
|
70
|
+
if (name === "left" || key.sequence === "\x1b[D") return [DoomKeys.KEY_LEFTARROW];
|
|
71
|
+
if (name === "right" || key.sequence === "\x1b[C") return [DoomKeys.KEY_RIGHTARROW];
|
|
72
|
+
|
|
73
|
+
// WASD movement - send BOTH movement key AND character
|
|
74
|
+
// Movement key ensures gameplay works, character ensures text input works
|
|
75
|
+
if (name === "w") return [DoomKeys.KEY_UPARROW, "w".charCodeAt(0)];
|
|
76
|
+
if (name === "s") return [DoomKeys.KEY_DOWNARROW, "s".charCodeAt(0)];
|
|
77
|
+
if (name === "a") return [DoomKeys.KEY_STRAFE_L, "a".charCodeAt(0)];
|
|
78
|
+
if (name === "d") return [DoomKeys.KEY_STRAFE_R, "d".charCodeAt(0)];
|
|
79
|
+
|
|
80
|
+
// Action keys
|
|
81
|
+
if (name === "space") return [" ".charCodeAt(0)]; // Use
|
|
82
|
+
if (name === "return" || name === "enter") return [DoomKeys.KEY_ENTER];
|
|
83
|
+
if (name === "escape") return [DoomKeys.KEY_ESCAPE];
|
|
84
|
+
if (name === "tab") return [DoomKeys.KEY_TAB];
|
|
85
|
+
if (name === "backspace") return [DoomKeys.KEY_BACKSPACE];
|
|
86
|
+
|
|
87
|
+
// Fire (Ctrl) - but not Ctrl+C which should exit
|
|
88
|
+
if (key.ctrl && key.name !== "c") return [DoomKeys.KEY_FIRE];
|
|
89
|
+
|
|
90
|
+
// Alt for strafe
|
|
91
|
+
if (key.meta || key.name === "alt") return [DoomKeys.KEY_LALT];
|
|
92
|
+
|
|
93
|
+
// Shift for run
|
|
94
|
+
if (key.shift) return [DoomKeys.KEY_RSHIFT];
|
|
95
|
+
|
|
96
|
+
// Function keys
|
|
97
|
+
if (name === "f1") return [DoomKeys.KEY_F1];
|
|
98
|
+
if (name === "f2") return [DoomKeys.KEY_F2];
|
|
99
|
+
if (name === "f3") return [DoomKeys.KEY_F3];
|
|
100
|
+
if (name === "f4") return [DoomKeys.KEY_F4];
|
|
101
|
+
if (name === "f5") return [DoomKeys.KEY_F5];
|
|
102
|
+
if (name === "f6") return [DoomKeys.KEY_F6];
|
|
103
|
+
if (name === "f7") return [DoomKeys.KEY_F7];
|
|
104
|
+
if (name === "f8") return [DoomKeys.KEY_F8];
|
|
105
|
+
if (name === "f9") return [DoomKeys.KEY_F9];
|
|
106
|
+
if (name === "f10") return [DoomKeys.KEY_F10];
|
|
107
|
+
if (name === "f11") return [DoomKeys.KEY_F11];
|
|
108
|
+
if (name === "f12") return [DoomKeys.KEY_F12];
|
|
109
|
+
|
|
110
|
+
// Weapon selection (1-9, 0)
|
|
111
|
+
if (name >= "0" && name <= "9") return [name.charCodeAt(0)];
|
|
112
|
+
|
|
113
|
+
// Plus/minus for gamma/zoom
|
|
114
|
+
if (name === "+" || name === "=") return [DoomKeys.KEY_EQUALS];
|
|
115
|
+
if (name === "-") return [DoomKeys.KEY_MINUS];
|
|
116
|
+
|
|
117
|
+
// Y/N for prompts
|
|
118
|
+
if (name === "y") return ["y".charCodeAt(0)];
|
|
119
|
+
if (name === "n") return ["n".charCodeAt(0)];
|
|
120
|
+
|
|
121
|
+
// Other letter keys (for cheats, etc)
|
|
122
|
+
if (name.length === 1 && name >= "a" && name <= "z") {
|
|
123
|
+
return [name.charCodeAt(0)];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return [];
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
/**
|
|
@@ -133,89 +133,89 @@ function mapKeyToDoom(key: KeyEvent): number[] {
|
|
|
133
133
|
const keyTimers = new Map<string, ReturnType<typeof setTimeout>>();
|
|
134
134
|
|
|
135
135
|
export interface DoomInputOptions {
|
|
136
|
-
|
|
137
|
-
|
|
136
|
+
engine: DoomEngine;
|
|
137
|
+
onExit?: () => void;
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
export function createDoomInputHandler(options: DoomInputOptions) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
141
|
+
const { engine, onExit } = options;
|
|
142
|
+
|
|
143
|
+
return (key: KeyEvent) => {
|
|
144
|
+
// Handle Ctrl+C for exit
|
|
145
|
+
if (key.ctrl && (key.name === "c" || key.sequence === "\x03")) {
|
|
146
|
+
if (onExit) {
|
|
147
|
+
onExit();
|
|
148
|
+
}
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
151
|
|
|
152
|
-
|
|
152
|
+
const doomKeys = mapKeyToDoom(key);
|
|
153
153
|
|
|
154
|
-
|
|
154
|
+
if (doomKeys.length === 0) return;
|
|
155
155
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
156
|
+
const keyId = key.name || key.sequence || "";
|
|
157
|
+
const wasPressed = keyStates.get(keyId) ?? false;
|
|
158
|
+
const keyName = key.name?.toLowerCase() ?? "";
|
|
159
159
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
160
|
+
// Menu confirmation keys (y/n) should always send keydown on every press
|
|
161
|
+
// These are used for quit dialogs and other prompts
|
|
162
|
+
const isMenuConfirmKey = keyName === "y" || keyName === "n";
|
|
163
163
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
164
|
+
// Clear any existing release timer for this key
|
|
165
|
+
const existingTimer = keyTimers.get(keyId);
|
|
166
|
+
if (existingTimer) {
|
|
167
|
+
clearTimeout(existingTimer);
|
|
168
|
+
keyTimers.delete(keyId);
|
|
169
|
+
}
|
|
170
170
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
171
|
+
// Key press - send if not already pressed, OR if it's a menu confirmation key
|
|
172
|
+
if (!wasPressed || isMenuConfirmKey) {
|
|
173
|
+
keyStates.set(keyId, true);
|
|
174
|
+
// Send all mapped keys (for WASD this includes both movement and character)
|
|
175
|
+
for (const doomKey of doomKeys) {
|
|
176
|
+
engine.pushKey(true, doomKey);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// For menu confirmation keys, immediately send release too
|
|
180
|
+
// since DOOM only cares about the keydown event
|
|
181
|
+
if (isMenuConfirmKey) {
|
|
182
|
+
setTimeout(() => {
|
|
183
|
+
for (const doomKey of doomKeys) {
|
|
184
|
+
engine.pushKey(false, doomKey);
|
|
185
|
+
}
|
|
186
|
+
keyStates.set(keyId, false);
|
|
187
|
+
}, 50);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
191
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
192
|
+
// Schedule key release after 300ms of no input (for non-menu keys)
|
|
193
|
+
const timer = setTimeout(() => {
|
|
194
|
+
if (keyStates.get(keyId)) {
|
|
195
|
+
keyStates.set(keyId, false);
|
|
196
|
+
for (const doomKey of doomKeys) {
|
|
197
|
+
engine.pushKey(false, doomKey);
|
|
198
|
+
}
|
|
199
|
+
keyTimers.delete(keyId);
|
|
200
|
+
}
|
|
201
|
+
}, 300);
|
|
202
|
+
keyTimers.set(keyId, timer);
|
|
203
|
+
};
|
|
204
204
|
}
|
|
205
205
|
|
|
206
206
|
/**
|
|
207
207
|
* Get help text for controls
|
|
208
208
|
*/
|
|
209
209
|
export function getControlsHelp(): string {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
210
|
+
return [
|
|
211
|
+
"Controls:",
|
|
212
|
+
" Movement: Arrow Keys or WASD",
|
|
213
|
+
" Fire: Ctrl",
|
|
214
|
+
" Use/Open: Space",
|
|
215
|
+
" Run: Shift",
|
|
216
|
+
" Strafe: A/D or Alt+Arrows",
|
|
217
|
+
" Weapons: 1-7",
|
|
218
|
+
" Menu: Escape",
|
|
219
|
+
" Map: Tab",
|
|
220
|
+
].join("\n");
|
|
221
221
|
}
|
package/src/doom-mouse.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* DOOM Mouse Input Handler
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Provides mouse-based turning and firing for DOOM.
|
|
5
5
|
* Mouse horizontal movement translates to left/right turning.
|
|
6
6
|
* Left click fires the weapon.
|
|
@@ -11,119 +11,119 @@ import { DoomKeys } from "./doom-input";
|
|
|
11
11
|
import { debugLog } from "./debug";
|
|
12
12
|
|
|
13
13
|
export interface DoomMouseOptions {
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
engine: DoomEngine;
|
|
15
|
+
sensitivity?: number; // Cells of movement before triggering turn (default: 2)
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export interface DoomMouseHandler {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
onMouseMove: (x: number, y: number) => void;
|
|
20
|
+
onMouseDown: (button: number) => void;
|
|
21
|
+
onMouseUp: (button: number) => void;
|
|
22
|
+
reset: () => void;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* Create a mouse handler that forwards mouse events to DOOM
|
|
27
27
|
*/
|
|
28
28
|
export function createDoomMouseHandler(options: DoomMouseOptions): DoomMouseHandler {
|
|
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
|
-
engine.pushKey(true, newTurnKey);
|
|
67
|
-
currentTurnKey = newTurnKey;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Schedule release after delay (will be cancelled if more movement comes)
|
|
71
|
-
releaseTimer = setTimeout(() => {
|
|
72
|
-
if (currentTurnKey !== null) {
|
|
73
|
-
engine.pushKey(false, currentTurnKey);
|
|
74
|
-
currentTurnKey = null;
|
|
75
|
-
}
|
|
76
|
-
releaseTimer = null;
|
|
77
|
-
}, RELEASE_DELAY);
|
|
78
|
-
},
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Handle mouse button press
|
|
82
|
-
*/
|
|
83
|
-
onMouseDown(button: number): void {
|
|
84
|
-
// Left click (button 0) = fire
|
|
85
|
-
if (button === 0 && !isLeftMouseDown) {
|
|
86
|
-
isLeftMouseDown = true;
|
|
87
|
-
engine.pushKey(true, DoomKeys.KEY_FIRE);
|
|
88
|
-
debugLog("Mouse", "Fire pressed");
|
|
89
|
-
}
|
|
90
|
-
},
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Handle mouse button release
|
|
94
|
-
*/
|
|
95
|
-
onMouseUp(button: number): void {
|
|
96
|
-
// Left click release = stop firing
|
|
97
|
-
if (button === 0 && isLeftMouseDown) {
|
|
98
|
-
isLeftMouseDown = false;
|
|
99
|
-
engine.pushKey(false, DoomKeys.KEY_FIRE);
|
|
100
|
-
debugLog("Mouse", "Fire released");
|
|
101
|
-
}
|
|
102
|
-
},
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Reset mouse state (useful when window loses focus)
|
|
106
|
-
*/
|
|
107
|
-
reset(): void {
|
|
108
|
-
lastMouseX = null;
|
|
109
|
-
|
|
110
|
-
// Clear pending release timer
|
|
111
|
-
if (releaseTimer) {
|
|
112
|
-
clearTimeout(releaseTimer);
|
|
113
|
-
releaseTimer = null;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Release any held turn key
|
|
117
|
-
if (currentTurnKey !== null) {
|
|
118
|
-
engine.pushKey(false, currentTurnKey);
|
|
119
|
-
currentTurnKey = null;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Release fire if held
|
|
123
|
-
if (isLeftMouseDown) {
|
|
124
|
-
isLeftMouseDown = false;
|
|
125
|
-
engine.pushKey(false, DoomKeys.KEY_FIRE);
|
|
126
|
-
}
|
|
29
|
+
const { engine } = options;
|
|
30
|
+
|
|
31
|
+
let lastMouseX: number | null = null;
|
|
32
|
+
let isLeftMouseDown = false;
|
|
33
|
+
let currentTurnKey: number | null = null;
|
|
34
|
+
let releaseTimer: ReturnType<typeof setTimeout> | null = null;
|
|
35
|
+
|
|
36
|
+
const RELEASE_DELAY = 100; // Release key 100ms after last movement
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
/**
|
|
40
|
+
* Handle mouse movement - hold turn key while moving
|
|
41
|
+
*/
|
|
42
|
+
onMouseMove(x: number, _y: number): void {
|
|
43
|
+
if (lastMouseX === null) {
|
|
44
|
+
lastMouseX = x;
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const delta = x - lastMouseX;
|
|
49
|
+
lastMouseX = x;
|
|
50
|
+
|
|
51
|
+
if (delta === 0) return;
|
|
52
|
+
|
|
53
|
+
const newTurnKey = delta > 0 ? DoomKeys.KEY_RIGHTARROW : DoomKeys.KEY_LEFTARROW;
|
|
54
|
+
|
|
55
|
+
// Clear any pending release
|
|
56
|
+
if (releaseTimer) {
|
|
57
|
+
clearTimeout(releaseTimer);
|
|
58
|
+
releaseTimer = null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// If direction changed, release old and press new
|
|
62
|
+
if (currentTurnKey !== newTurnKey) {
|
|
63
|
+
if (currentTurnKey !== null) {
|
|
64
|
+
engine.pushKey(false, currentTurnKey);
|
|
127
65
|
}
|
|
128
|
-
|
|
66
|
+
engine.pushKey(true, newTurnKey);
|
|
67
|
+
currentTurnKey = newTurnKey;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Schedule release after delay (will be cancelled if more movement comes)
|
|
71
|
+
releaseTimer = setTimeout(() => {
|
|
72
|
+
if (currentTurnKey !== null) {
|
|
73
|
+
engine.pushKey(false, currentTurnKey);
|
|
74
|
+
currentTurnKey = null;
|
|
75
|
+
}
|
|
76
|
+
releaseTimer = null;
|
|
77
|
+
}, RELEASE_DELAY);
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Handle mouse button press
|
|
82
|
+
*/
|
|
83
|
+
onMouseDown(button: number): void {
|
|
84
|
+
// Left click (button 0) = fire
|
|
85
|
+
if (button === 0 && !isLeftMouseDown) {
|
|
86
|
+
isLeftMouseDown = true;
|
|
87
|
+
engine.pushKey(true, DoomKeys.KEY_FIRE);
|
|
88
|
+
debugLog("Mouse", "Fire pressed");
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Handle mouse button release
|
|
94
|
+
*/
|
|
95
|
+
onMouseUp(button: number): void {
|
|
96
|
+
// Left click release = stop firing
|
|
97
|
+
if (button === 0 && isLeftMouseDown) {
|
|
98
|
+
isLeftMouseDown = false;
|
|
99
|
+
engine.pushKey(false, DoomKeys.KEY_FIRE);
|
|
100
|
+
debugLog("Mouse", "Fire released");
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Reset mouse state (useful when window loses focus)
|
|
106
|
+
*/
|
|
107
|
+
reset(): void {
|
|
108
|
+
lastMouseX = null;
|
|
109
|
+
|
|
110
|
+
// Clear pending release timer
|
|
111
|
+
if (releaseTimer) {
|
|
112
|
+
clearTimeout(releaseTimer);
|
|
113
|
+
releaseTimer = null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Release any held turn key
|
|
117
|
+
if (currentTurnKey !== null) {
|
|
118
|
+
engine.pushKey(false, currentTurnKey);
|
|
119
|
+
currentTurnKey = null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Release fire if held
|
|
123
|
+
if (isLeftMouseDown) {
|
|
124
|
+
isLeftMouseDown = false;
|
|
125
|
+
engine.pushKey(false, DoomKeys.KEY_FIRE);
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
129
|
}
|