1 |
#include <stdlib.h> |
2 |
#include <stdarg.h> |
3 |
|
4 |
#include "../Daodan.h" |
5 |
#include "Input.h" |
6 |
#include "../Oni/Oni.h" |
7 |
#include "../Daodan_Config.h" |
8 |
#include "../Daodan_Patch.h" |
9 |
#include "Utility.h" |
10 |
|
11 |
typedef struct { |
12 |
LItActionDescription descr; |
13 |
DDtActionEventType eventType; |
14 |
|
15 |
DDtCustomActionCallback callback; |
16 |
intptr_t ctx; |
17 |
} DDtCustomAction; |
18 |
|
19 |
static DDtCustomAction DDgCustomActions[100] = { 0 }; |
20 |
|
21 |
// Extra keys (make sure these don't collide with Oni's LIc_* keys) |
22 |
enum { |
23 |
DDcKey_MouseButton5 = LIcKey_Max, |
24 |
DDcKey_ScrollUp, |
25 |
DDcKey_ScrollDown, |
26 |
DDcKey_ScrollLeft, |
27 |
DDcKey_ScrollRight, |
28 |
}; |
29 |
|
30 |
// Enhanced version of LIgInputNames from Oni with some extra keys |
31 |
static const LItInputName DDgInputNames[] = { |
32 |
// The following key names are mapped in Oni |
33 |
{"fkey1", LIcKey_FKey1}, {"fkey2", LIcKey_FKey2}, {"fkey3", LIcKey_FKey3}, |
34 |
{"fkey4", LIcKey_FKey4}, {"fkey5", LIcKey_FKey5}, {"fkey6", LIcKey_FKey6}, |
35 |
{"fkey7", LIcKey_FKey7}, {"fkey8", LIcKey_FKey8}, {"fkey9", LIcKey_FKey9}, |
36 |
{"fkey10", LIcKey_FKey10}, {"fkey11", LIcKey_FKey11}, |
37 |
{"fkey12", LIcKey_FKey12}, {"fkey13", LIcKey_FKey13}, |
38 |
{"fkey14", LIcKey_FKey14}, {"fkey15", LIcKey_FKey15}, |
39 |
{"backspace", LIcKey_Backspace}, {"tab", LIcKey_Tab}, |
40 |
{"capslock", LIcKey_CapsLock}, {"enter", LIcKey_Enter}, |
41 |
{"leftshift", LIcKey_LeftShift}, {"rightshift", LIcKey_RightShift}, |
42 |
{"leftcontrol", LIcKey_LeftControl}, |
43 |
{"leftwindows", 0x94}, {"leftoption", 0x94}, // Does nothing in Oni |
44 |
{"leftalt", LIcKey_LeftAlt}, {"space", ' '}, {"rightalt", LIcKey_RightAlt}, |
45 |
{"rightoption", 0x97}, {"rightwindows", 0x97}, // Does nothing in Oni |
46 |
{"rightcontrol", LIcKey_RightControl}, {"printscreen", LIcKey_PrintScreen}, |
47 |
{"scrolllock", LIcKey_ScrollLock}, {"pause", LIcKey_Pause}, |
48 |
{"insert", LIcKey_Insert}, {"home", LIcKey_Home}, {"pageup", LIcKey_PageUp}, |
49 |
{"delete", LIcKey_Delete}, {"end", LIcKey_End}, |
50 |
{"pagedown", LIcKey_PageDown}, {"uparrow", LIcKey_UpArrow}, |
51 |
{"leftarrow", LIcKey_LeftArrow}, {"downarrow", LIcKey_DownArrow}, |
52 |
{"rightarrow", LIcKey_RightArrow}, {"numlock", LIcKey_NumLock}, |
53 |
{"divide", LIcKey_Divide}, {"multiply", LIcKey_Multiply}, |
54 |
{"subtract", LIcKey_Subtract}, {"add", LIcKey_Add}, |
55 |
{"numpadequals", LIcKey_NumpadEquals}, {"numpadenter", LIcKey_NumpadEnter}, |
56 |
{"decimal", LIcKey_Decimal}, {"numpad0", LIcKey_Numpad0}, |
57 |
{"numpad1", LIcKey_Numpad1}, {"numpad2", LIcKey_Numpad2}, |
58 |
{"numpad3", LIcKey_Numpad3}, {"numpad4", LIcKey_Numpad4}, |
59 |
{"numpad5", LIcKey_Numpad5}, {"numpad6", LIcKey_Numpad6}, |
60 |
{"numpad7", LIcKey_Numpad7}, {"numpad8", LIcKey_Numpad8}, |
61 |
{"numpad9", LIcKey_Numpad9}, {"backslash", '\\'}, {"semicolon", ';'}, |
62 |
{"period", '.'}, {"apostrophe", '\''}, {"slash", '/'}, {"leftbracket", '['}, |
63 |
{"rightbracket", ']'}, {"comma", ','}, |
64 |
{"mousebutton1", LIcKey_MouseButton1}, |
65 |
{"mousebutton2", LIcKey_MouseButton2}, |
66 |
{"mousebutton3", LIcKey_MouseButton3}, |
67 |
{"mousebutton4", LIcKey_MouseButton4}, |
68 |
{"mousexaxis", LIcKey_MouseXAxis}, {"mouseyaxis", LIcKey_MouseYAxis}, |
69 |
{"mousezaxis", LIcKey_MouseZAxis}, |
70 |
|
71 |
// Extra keys for Daodan Input |
72 |
{"mousebutton5", DDcKey_MouseButton5}, |
73 |
{"scrollup", DDcKey_ScrollUp}, |
74 |
{"scrolldown", DDcKey_ScrollDown}, |
75 |
{"scrollleft", DDcKey_ScrollLeft}, |
76 |
{"scrollright", DDcKey_ScrollRight}, |
77 |
|
78 |
{"", 0} |
79 |
}; |
80 |
|
81 |
// Enhanced version of LIgPlatform_ScanCodeToChar from Oni |
82 |
static const uint8_t DDgPlatform_ScanCodeToChar[256] = { |
83 |
// The following scan codes are mapped in Oni |
84 |
[0x01] = LIcKey_Escape, [0x02] = '1', [0x03] = '2', [0x04] = '3', |
85 |
[0x05] = '4', [0x06] = '5', [0x07] = '6', [0x08] = '7', [0x09] = '8', |
86 |
[0x0a] = '9', [0x0b] = '0', [0x0c] = '-', [0x0d] = '=', |
87 |
[0x0e] = LIcKey_Backspace, [0x0f] = LIcKey_Tab, [0x10] = 'q', [0x11] = 'w', |
88 |
[0x12] = 'e', [0x13] = 'r', [0x14] = 't', [0x15] = 'y', [0x16] = 'u', |
89 |
[0x17] = 'i', [0x18] = 'o', [0x19] = 'p', [0x1a] = '[', [0x1b] = ']', |
90 |
[0x1c] = LIcKey_Enter, [0x1d] = LIcKey_LeftControl, [0x1e] = 'a', |
91 |
[0x1f] = 's', [0x20] = 'd', [0x21] = 'f', [0x22] = 'g', [0x23] = 'h', |
92 |
[0x24] = 'j', [0x25] = 'k', [0x26] = 'l', [0x27] = ';', [0x28] = '\'', |
93 |
[0x29] = '`', [0x2a] = LIcKey_LeftShift, [0x2b] = '\\', [0x2c] = 'z', |
94 |
[0x2d] = 'x', [0x2e] = 'c', [0x2f] = 'v', [0x30] = 'b', [0x31] = 'n', |
95 |
[0x32] = 'm', [0x33] = ',', [0x34] = '.', [0x35] = '/', |
96 |
[0x36] = LIcKey_RightShift, [0x37] = LIcKey_Multiply, |
97 |
[0x38] = LIcKey_LeftAlt, [0x39] = ' ', [0x3a] = LIcKey_CapsLock, |
98 |
[0x3b] = LIcKey_FKey1, [0x3c] = LIcKey_FKey2, [0x3d] = LIcKey_FKey3, |
99 |
[0x3e] = LIcKey_FKey4, [0x3f] = LIcKey_FKey5, [0x40] = LIcKey_FKey6, |
100 |
[0x41] = LIcKey_FKey7, [0x42] = LIcKey_FKey8, [0x43] = LIcKey_FKey9, |
101 |
[0x44] = LIcKey_FKey10, [0x45] = LIcKey_NumLock, [0x46] = LIcKey_ScrollLock, |
102 |
[0x47] = LIcKey_Numpad7, [0x48] = LIcKey_Numpad8, [0x49] = LIcKey_Numpad9, |
103 |
[0x4a] = LIcKey_Subtract, [0x4b] = LIcKey_Numpad4, [0x4c] = LIcKey_Numpad5, |
104 |
[0x4d] = LIcKey_Numpad6, [0x4e] = LIcKey_Add, [0x4f] = LIcKey_Numpad1, |
105 |
[0x50] = LIcKey_Numpad2, [0x51] = LIcKey_Numpad3, [0x52] = LIcKey_Numpad0, |
106 |
[0x53] = LIcKey_Decimal, [0x57] = LIcKey_FKey11, [0x58] = LIcKey_FKey12, |
107 |
[0x64] = LIcKey_FKey13, [0x65] = LIcKey_FKey14, [0x66] = LIcKey_FKey15, |
108 |
[0x8d] = LIcKey_NumpadEquals, [0x9c] = LIcKey_NumpadEnter, |
109 |
[0x9d] = LIcKey_RightControl, [0xb3] = LIcKey_NumpadComma, |
110 |
[0xb5] = LIcKey_Divide, [0xb8] = LIcKey_RightAlt, [0xc7] = LIcKey_Home, |
111 |
[0xc8] = LIcKey_UpArrow, [0xc9] = LIcKey_PageUp, [0xcb] = LIcKey_LeftArrow, |
112 |
[0xcd] = LIcKey_RightArrow, [0xcf] = LIcKey_End, [0xd0] = LIcKey_DownArrow, |
113 |
[0xd2] = LIcKey_Insert, [0xd3] = LIcKey_Delete, [0xdb] = LIcKey_LeftWindows, |
114 |
[0xdc] = LIcKey_RightWindows, [0xdd] = LIcKey_Apps, |
115 |
|
116 |
// Missing in Oni |
117 |
[0xd1] = LIcKey_PageDown, |
118 |
}; |
119 |
|
120 |
// Set in Patches.c if the Daodan input patches are applied. This just enables |
121 |
// the windows message handling for now |
122 |
bool DDgUseDaodanInput = false; |
123 |
|
124 |
// The Oni key codes that correspond to the togglable keys |
125 |
static uint8_t DDgCapsOniKey = 0; |
126 |
static uint8_t DDgScrollOniKey = 0; |
127 |
static uint8_t DDgNumLockOniKey = 0; |
128 |
|
129 |
// Multiplier for mouse values |
130 |
static float DDgMouseSensitivity = 1.0; |
131 |
|
132 |
// Accumulators for mouse scrolling. These are needed because some mice have |
133 |
// continuous scroll wheels (not to mention touchpads.) We should only add an |
134 |
// action to Oni's input if one of these accumulators exceeds +/-WHEEL_DELTA. |
135 |
static int DDgWheelDelta_V = 0; |
136 |
static int DDgWheelDelta_H = 0; |
137 |
|
138 |
// UUrMachineTime_High for the last update of the accumulators. Used so they can |
139 |
// be reset after a period of no scroll events. |
140 |
static int64_t DDgWheelDelta_Updated = 0; |
141 |
|
142 |
// Temporary action buffer that we build over the duration of a frame with the |
143 |
// input we're going to send to the engine. This includes the accumulated |
144 |
// movement of the mouse cursor and all actions (keyboard and mouse buttons) |
145 |
// that were pressed this frame (but not held down from previous frames - that |
146 |
// gets added later from DDgInputState.) |
147 |
static LItActionBuffer DDgActionBuffer = { 0 }; |
148 |
|
149 |
// Temporary buffer containing the current state of the keyboard and mouse |
150 |
// buttons, that is, if they're being held now |
151 |
static char DDgInputState[256] = { 0 }; |
152 |
|
153 |
static short ONICALL DDrBinding_Add(int key, const char *name) |
154 |
{ |
155 |
// First try to replace an existing binding for the same key |
156 |
LItBinding *binding = NULL; |
157 |
for (int i = 0; i < 100; i++) { |
158 |
if (LIgBindingArray[i].key == key) { |
159 |
binding = &LIgBindingArray[i]; |
160 |
break; |
161 |
} |
162 |
} |
163 |
|
164 |
// If there are no existing bindings for this key, find a free entry |
165 |
if (!binding) { |
166 |
for (int i = 0; i < 100; i++) { |
167 |
if (!LIgBindingArray[i].key) { |
168 |
binding = &LIgBindingArray[i]; |
169 |
break; |
170 |
} |
171 |
} |
172 |
} |
173 |
// No free entries, so give up |
174 |
if (!binding) |
175 |
return 2; |
176 |
|
177 |
// Now try to find the action to bind to. First check Oni's built-in list |
178 |
// of actions. |
179 |
LItActionDescription *descr = NULL; |
180 |
for (int i = 0; LIgActionDescriptions[i].name[0]; i++) { |
181 |
if (!UUrString_Compare_NoCase(name, LIgActionDescriptions[i].name)) { |
182 |
descr = &LIgActionDescriptions[i]; |
183 |
break; |
184 |
} |
185 |
} |
186 |
|
187 |
// Next, try Daodan's list of custom actions |
188 |
if (!descr) { |
189 |
for (int i = 0; i < ARRAY_SIZE(DDgCustomActions); i++) { |
190 |
if (!DDgCustomActions[i].descr.name[0]) |
191 |
break; |
192 |
|
193 |
if (!UUrString_Compare_NoCase(name, DDgCustomActions[i].descr.name)) { |
194 |
descr = &DDgCustomActions[i].descr; |
195 |
break; |
196 |
} |
197 |
} |
198 |
} |
199 |
if (!descr) |
200 |
return 0; |
201 |
|
202 |
binding->key = key; |
203 |
binding->descr = descr; |
204 |
return 0; |
205 |
} |
206 |
|
207 |
static void ONICALL DDrGameState_HandleUtilityInput(GameInput *input) |
208 |
{ |
209 |
// Mac Oni 1.2.1 checks the cheat binds here, so we should too. Note that |
210 |
// unlike Mac Oni, which hardcodes each cheat here, we use our flexible |
211 |
// custom action system. |
212 |
for (int i = 0; i < ARRAY_SIZE(DDgCustomActions); i++) { |
213 |
if (!DDgCustomActions[i].descr.name[0]) |
214 |
break; |
215 |
|
216 |
uint64_t action = 1ull << DDgCustomActions[i].descr.index; |
217 |
bool active = false; |
218 |
|
219 |
switch (DDgCustomActions[i].eventType) { |
220 |
case DDcEventType_KeyPress: |
221 |
if (input->ActionsPressed & action) |
222 |
active = true; |
223 |
break; |
224 |
case DDcEventType_KeyDown: |
225 |
if (input->ActionsDown & action) |
226 |
active = true; |
227 |
break; |
228 |
} |
229 |
|
230 |
if (active) |
231 |
DDgCustomActions[i].callback(DDgCustomActions[i].ctx); |
232 |
} |
233 |
|
234 |
// Now do everything Oni does in this function |
235 |
ONrGameState_HandleUtilityInput(input); |
236 |
|
237 |
// This is for show_triggervolumes. Mac Oni does this at the end of |
238 |
// HandleUtilityInput too. |
239 |
if (ONrDebugKey_WentDown(7)) |
240 |
OBJgTriggerVolume_Visible = !OBJgTriggerVolume_Visible; |
241 |
} |
242 |
|
243 |
static int GetLowestFreeDigitalAction(void) |
244 |
{ |
245 |
// Get the digital action indexes that Oni is using right now, plus any in |
246 |
// use by our custom actions |
247 |
uint64_t used = 0; |
248 |
for (int i = 0; LIgActionDescriptions[i].name[0]; i++) { |
249 |
if (LIgActionDescriptions[i].type != LIcActionType_Digital) |
250 |
continue; |
251 |
used |= 1ull << LIgActionDescriptions[i].index; |
252 |
} |
253 |
for (int i = 0; i < ARRAY_SIZE(DDgCustomActions); i++) { |
254 |
if (!DDgCustomActions[i].descr.name[0]) |
255 |
break; |
256 |
|
257 |
if (DDgCustomActions[i].descr.type != LIcActionType_Digital) |
258 |
continue; |
259 |
used |= 1ull << DDgCustomActions[i].descr.index; |
260 |
} |
261 |
|
262 |
// Get the lowest unused action index and use it. This isn't totally safe |
263 |
// since Oni _might_ have "orphaned" actions that are checked in the code |
264 |
// but not bindable, but Mac Oni 1.2.1 seems to have allocated its new |
265 |
// bindings this way, including filling the gaps between eg. f12 and |
266 |
// lookmode, so we're probably fine to do the same thing. |
267 |
unsigned long lowest; |
268 |
if (BitScanForward(&lowest, ~(unsigned long)used)) |
269 |
return lowest; |
270 |
if (BitScanForward(&lowest, ~(unsigned long)(used >> 32))) |
271 |
return lowest + 32; |
272 |
return -1; |
273 |
} |
274 |
|
275 |
void DDrInput_RegisterCustomAction(const char *name, DDtActionEventType type, |
276 |
DDtCustomActionCallback callback, |
277 |
intptr_t ctx) |
278 |
{ |
279 |
int index = GetLowestFreeDigitalAction(); |
280 |
if (index < 0) { |
281 |
STARTUPMESSAGE("Registering action %s failed, maximum actions reached", |
282 |
name); |
283 |
return; |
284 |
} |
285 |
|
286 |
DDtCustomAction *action; |
287 |
for (int i = 0; i < ARRAY_SIZE(DDgCustomActions); i++) { |
288 |
if (!DDgCustomActions[i].descr.name[0]) { |
289 |
action = &DDgCustomActions[i]; |
290 |
break; |
291 |
} |
292 |
} |
293 |
if (!action) { |
294 |
STARTUPMESSAGE("Registering action %s failed, maximum actions reached", |
295 |
name); |
296 |
return; |
297 |
} |
298 |
|
299 |
*action = (DDtCustomAction) { |
300 |
.descr = { |
301 |
.type = 1, |
302 |
.index = index, |
303 |
}, |
304 |
.callback = callback, |
305 |
.ctx = ctx, |
306 |
}; |
307 |
UUrString_Copy(action->descr.name, name, sizeof(action->descr.name)); |
308 |
} |
309 |
|
310 |
static uint8_t VKeyToChar(UINT vkey) |
311 |
{ |
312 |
int sc = MapVirtualKeyA(vkey, MAPVK_VK_TO_VSC_EX); |
313 |
if ((sc & 0xff00) == 0xe000) |
314 |
sc |= 0x80; |
315 |
sc &= 0xff; |
316 |
return DDgPlatform_ScanCodeToChar[sc]; |
317 |
} |
318 |
|
319 |
static int ONICALL DDrTranslate_InputName(char *name) |
320 |
{ |
321 |
// Mutate the source argument to convert to lowercase. It's ugly but Oni |
322 |
// does this too. Unlike Oni, we don't use tolower, since passing |
323 |
// potentially out-of-range values to tolower is undefined. |
324 |
for (char *c = name; *c; c++) { |
325 |
if (*c >= 'A' && *c <= 'Z') |
326 |
*c = *c - 0x20; |
327 |
} |
328 |
|
329 |
// Single character names just use that character as the key code. Unlike |
330 |
// Oni, we restrict this to printable ASCII. |
331 |
if (strlen(name) == 1 && name[0] >= ' ' && name[0] <= '~') |
332 |
return name[0]; |
333 |
|
334 |
// Otherwise, look up the name in DDgInputNames |
335 |
for (int i = 0; DDgInputNames[i].name[0]; i++) { |
336 |
if (!strcmp(name, DDgInputNames[i].name)) |
337 |
return DDgInputNames[i].key; |
338 |
} |
339 |
return 0; |
340 |
} |
341 |
|
342 |
static void CenterCursor(void) |
343 |
{ |
344 |
// This can be set to false by script. Not sure why you'd turn it off, but |
345 |
// let's respect it. |
346 |
if (!LIgCenterCursor) |
347 |
return; |
348 |
|
349 |
RECT rc; |
350 |
if (!GetClientRect(LIgPlatform_HWND, &rc)) |
351 |
return; |
352 |
POINT mid = { rc.right / 2, rc.bottom / 2 }; |
353 |
if (!ClientToScreen(LIgPlatform_HWND, &mid)) |
354 |
return; |
355 |
SetCursorPos(mid.x, mid.y); |
356 |
} |
357 |
|
358 |
static void ONICALL DDrPlatform_Mode_Set(int active) |
359 |
{ |
360 |
// Oni's input system uses LIgPlatform_HWND instead of |
361 |
// ONgPlatformData.Window, but they should both have the same value |
362 |
DDmAssert(LIgPlatform_HWND); |
363 |
|
364 |
// Clear the input state when switching input modes |
365 |
for (int i = 0; i < ARRAY_SIZE(DDgInputState); i++) |
366 |
DDgInputState[i] = 0; |
367 |
DDgActionBuffer = (LItActionBuffer) { 0 }; |
368 |
|
369 |
// Center the cursor before switching modes. Raw Input doesn't need the |
370 |
// cursor to be centered, but when switching modes, centering the cursor |
371 |
// means it will be in a predictable position for using the pause or F1 |
372 |
// menu, which are centered on the screen. Also, the cursor must be inside |
373 |
// the clip region when we call ClipCursor, otherwise it doesn't work. |
374 |
CenterCursor(); |
375 |
|
376 |
// If leaving input mode (switching from gameplay to menus,) unregister the |
377 |
// input device. Otherwise, register it. |
378 |
RegisterRawInputDevices(&(RAWINPUTDEVICE) { |
379 |
.usUsagePage = 0x01, // HID_USAGE_PAGE_GENERIC |
380 |
.usUsage = 0x02, // HID_USAGE_GENERIC_MOUSE |
381 |
.hwndTarget = LIgPlatform_HWND, |
382 |
.dwFlags = active ? 0 : RIDEV_REMOVE, |
383 |
}, 1, sizeof(RAWINPUTDEVICE)); |
384 |
|
385 |
if (active) { |
386 |
DDgMouseSensitivity = |
387 |
DDrConfig_GetOptOfType("windows.mousesensitivity", C_FLOAT)->value.floatVal; |
388 |
|
389 |
// Get the Oni key codes corresponding to the togglable keys |
390 |
DDgCapsOniKey = VKeyToChar(VK_CAPITAL); |
391 |
DDgScrollOniKey = VKeyToChar(VK_SCROLL); |
392 |
DDgNumLockOniKey = VKeyToChar(VK_NUMLOCK); |
393 |
|
394 |
// Clip the cursor to the window bounds when entering input mode to |
395 |
// prevent other programs being clicked in windowed mode |
396 |
RECT rc; |
397 |
if (GetClientRect(LIgPlatform_HWND, &rc)) { |
398 |
if (MapWindowRect(LIgPlatform_HWND, NULL, &rc)) |
399 |
ClipCursor(&rc); |
400 |
} |
401 |
} else { |
402 |
ClipCursor(NULL); |
403 |
} |
404 |
} |
405 |
|
406 |
static void ONICALL DDrPlatform_InputEvent_GetMouse(int active, |
407 |
LItInputEvent *event) |
408 |
{ |
409 |
POINT pt; |
410 |
if (!GetCursorPos(&pt)) |
411 |
goto error; |
412 |
|
413 |
// Unlike Oni's version of this function, we support windowed mode by |
414 |
// mapping the cursor coordinates to the window's client area |
415 |
if (!ScreenToClient(LIgPlatform_HWND, &pt)) |
416 |
goto error; |
417 |
|
418 |
*event = (LItInputEvent) { .mouse_pos = { pt.x, pt.y } }; |
419 |
return; |
420 |
|
421 |
error: |
422 |
*event = (LItInputEvent) { 0 }; |
423 |
return; |
424 |
} |
425 |
|
426 |
static UUtBool ONICALL DDrPlatform_TestKey(int ch, int active) |
427 |
{ |
428 |
// DDrPlatform_TestKey is always called with active = LIgMode_Internal |
429 |
|
430 |
if (active) { |
431 |
// The input system is running, which means DDgInputState will be up to |
432 |
// date, so just use that |
433 |
return DDgInputState[ch]; |
434 |
} else { |
435 |
// Use Oni's map from key codes to DirectInput scan codes to get the |
436 |
// scan code we want to test for |
437 |
int sc = 0; |
438 |
for (int i = 0; i < 256; i++) { |
439 |
if (DDgPlatform_ScanCodeToChar[i] == ch) { |
440 |
sc = i; |
441 |
break; |
442 |
} |
443 |
} |
444 |
if (!sc) |
445 |
return UUcFalse; |
446 |
|
447 |
// DirectInput scan codes have 0x80 set for extended keys. Replace this |
448 |
// with an 0xe0 prefix for MapVirtualKey. |
449 |
if (sc & 0x80) { |
450 |
sc &= 0x7f; |
451 |
sc |= 0xe000; |
452 |
} |
453 |
int vkey = MapVirtualKeyA(sc, MAPVK_VSC_TO_VK_EX); |
454 |
|
455 |
// Now check if the key is down. We must use GetAsyncKeyState here |
456 |
// because DDrPlatform_TestKey can be called from anywhere, even before |
457 |
// we have a message loop or game loop. For example, it's called from |
458 |
// ONiMain to test the state of the shift key on startup. |
459 |
return (GetAsyncKeyState(vkey) & 0x8000) ? UUcTrue : UUcFalse; |
460 |
} |
461 |
} |
462 |
|
463 |
// Update DDgInputState and DDgActionBuffer with a new key state |
464 |
static void SetKeyState(int key, bool down) |
465 |
{ |
466 |
// Keep track of held keys. Held keys are added to every buffer and they're |
467 |
// also checked in DDrPlatform_TestKey. |
468 |
DDgInputState[key] = down; |
469 |
|
470 |
if (down) { |
471 |
// Add the key to the next buffer. This is so key presses are never |
472 |
// dropped, even if the key is released before Oni checks the buffer. |
473 |
LIrActionBuffer_Add(&DDgActionBuffer, &(LItDeviceInput) { |
474 |
.input = key, |
475 |
.analog = 1.0, |
476 |
}); |
477 |
} |
478 |
} |
479 |
|
480 |
static void ProcessRawInputPacket(RAWINPUT *ri) |
481 |
{ |
482 |
if (ri->header.dwType != RIM_TYPEMOUSE) |
483 |
return; |
484 |
|
485 |
// We don't handle MOUSE_MOVE_ABSOLUTE at all yet |
486 |
if (!(ri->data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE)) { |
487 |
LIrActionBuffer_Add(&DDgActionBuffer, &(LItDeviceInput) { |
488 |
.input = LIcKey_MouseXAxis, |
489 |
.analog = (float)ri->data.mouse.lLastX * 0.25 * DDgMouseSensitivity, |
490 |
}); |
491 |
LIrActionBuffer_Add(&DDgActionBuffer, &(LItDeviceInput) { |
492 |
.input = LIcKey_MouseYAxis, |
493 |
.analog = (float)ri->data.mouse.lLastY * |
494 |
(LIgMode_InvertMouse ? -0.25 : 0.25) * DDgMouseSensitivity, |
495 |
}); |
496 |
} |
497 |
|
498 |
// Oni supports using the mouse wheel to look up and down or left and right |
499 |
// by binding mousezaxis to aim_lr or aim_ud. We don't support this |
500 |
// incredibly useful feature, but if you need it, let me know. Instead, we |
501 |
// allow scrolling to be bound to digital actions. |
502 |
if (ri->data.mouse.usButtonFlags & (RI_MOUSE_WHEEL | RI_MOUSE_HWHEEL)) { |
503 |
int64_t now = UUrMachineTime_High(); |
504 |
int64_t last_updated = now - DDgWheelDelta_Updated; |
505 |
DDgWheelDelta_Updated = now; |
506 |
|
507 |
// Reset the accumulators if too much time has passed since the last |
508 |
// scroll event. The player is assumed to have finished scrolling. |
509 |
if (last_updated / UUrMachineTime_High_Frequency() > 0.3) { |
510 |
DDgWheelDelta_V = 0; |
511 |
DDgWheelDelta_H = 0; |
512 |
} |
513 |
|
514 |
int neg_key, pos_key; |
515 |
int *delta; |
516 |
if (ri->data.mouse.usButtonFlags & RI_MOUSE_WHEEL) { |
517 |
neg_key = DDcKey_ScrollUp; |
518 |
pos_key = DDcKey_ScrollDown; |
519 |
delta = &DDgWheelDelta_V; |
520 |
} else { |
521 |
neg_key = DDcKey_ScrollLeft; |
522 |
pos_key = DDcKey_ScrollRight; |
523 |
delta = &DDgWheelDelta_H; |
524 |
} |
525 |
|
526 |
// To support touchpad scrolling and mice with continuous scroll wheels, |
527 |
// accumulate the wheel delta and only generate an input event once it |
528 |
// crosses the WHEEL_DELTA threshold |
529 |
*delta += (short)ri->data.mouse.usButtonData; |
530 |
if (*delta >= WHEEL_DELTA) { |
531 |
LIrActionBuffer_Add(&DDgActionBuffer, &(LItDeviceInput) { |
532 |
.input = neg_key, |
533 |
.analog = 1.0, |
534 |
}); |
535 |
|
536 |
*delta -= (*delta / WHEEL_DELTA) * WHEEL_DELTA; |
537 |
} else if (*delta <= -WHEEL_DELTA) { |
538 |
LIrActionBuffer_Add(&DDgActionBuffer, &(LItDeviceInput) { |
539 |
.input = pos_key, |
540 |
.analog = 1.0, |
541 |
}); |
542 |
|
543 |
*delta -= (*delta / -WHEEL_DELTA) * -WHEEL_DELTA; |
544 |
} |
545 |
} |
546 |
|
547 |
// This probably doesn't obey SM_SWAPBUTTON... should it? |
548 |
if (ri->data.mouse.usButtonFlags & RI_MOUSE_LEFT_BUTTON_DOWN) |
549 |
SetKeyState(LIcKey_MouseButton1, true); |
550 |
if (ri->data.mouse.usButtonFlags & RI_MOUSE_LEFT_BUTTON_UP) |
551 |
SetKeyState(LIcKey_MouseButton1, false); |
552 |
if (ri->data.mouse.usButtonFlags & RI_MOUSE_RIGHT_BUTTON_DOWN) |
553 |
SetKeyState(LIcKey_MouseButton2, true); |
554 |
if (ri->data.mouse.usButtonFlags & RI_MOUSE_RIGHT_BUTTON_UP) |
555 |
SetKeyState(LIcKey_MouseButton2, false); |
556 |
if (ri->data.mouse.usButtonFlags & RI_MOUSE_MIDDLE_BUTTON_DOWN) |
557 |
SetKeyState(LIcKey_MouseButton3, true); |
558 |
if (ri->data.mouse.usButtonFlags & RI_MOUSE_MIDDLE_BUTTON_UP) |
559 |
SetKeyState(LIcKey_MouseButton3, false); |
560 |
|
561 |
// Oni supports binding this button too. It's the back button on most mice. |
562 |
if (ri->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_DOWN) |
563 |
SetKeyState(LIcKey_MouseButton4, true); |
564 |
if (ri->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_UP) |
565 |
SetKeyState(LIcKey_MouseButton4, false); |
566 |
|
567 |
// Daodan supports binding the forward button too |
568 |
if (ri->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_DOWN) |
569 |
SetKeyState(DDcKey_MouseButton5, true); |
570 |
if (ri->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_UP) |
571 |
SetKeyState(DDcKey_MouseButton5, false); |
572 |
} |
573 |
|
574 |
static void DrainRawInput(void) |
575 |
{ |
576 |
if (!LIgMode_Internal) |
577 |
return; |
578 |
|
579 |
UINT ri_size = 10240; |
580 |
static RAWINPUT *ri_buf = NULL; |
581 |
if (!ri_buf) |
582 |
ri_buf = calloc(1, ri_size); |
583 |
|
584 |
BOOL wow_hack; |
585 |
IsWow64Process(GetCurrentProcess(), &wow_hack); |
586 |
|
587 |
for (;;) { |
588 |
UINT count = GetRawInputBuffer(ri_buf, &ri_size, sizeof ri_buf->header); |
589 |
if (count == 0 || count == (UINT)-1) |
590 |
return; |
591 |
|
592 |
RAWINPUT *ri = ri_buf; |
593 |
for (UINT i = 0; i < count; i++) { |
594 |
// In WOW64, these structures are aligned like in Win64 and they |
595 |
// have to be fixed to use from 32-bit code. Yes, really. |
596 |
if (wow_hack) { |
597 |
memmove(&ri->data, ((char *)&ri->data) + 8, |
598 |
ri->header.dwSize - offsetof(RAWINPUT, data) - 8); |
599 |
} |
600 |
|
601 |
ProcessRawInputPacket(ri); |
602 |
ri = NEXTRAWINPUTBLOCK(ri); |
603 |
} |
604 |
} |
605 |
} |
606 |
|
607 |
static UUtBool ONICALL DDiPlatform_InputEvent_GetEvent(void) |
608 |
{ |
609 |
// Center the cursor just in case. Raw Input doesn't need it, but sometimes |
610 |
// ClipCursor doesn't work for some reason and in that case we should still |
611 |
// prevent the user from accidentally clicking on other windows. |
612 |
if (LIgMode_Internal) |
613 |
CenterCursor(); |
614 |
|
615 |
// Do a buffered read of raw input. Apparently this is faster for high-res |
616 |
// mice. Note that we still have to handle WM_INPUT in our wndproc in case |
617 |
// a WM_INPUT message arrives during the message loop. |
618 |
DrainRawInput(); |
619 |
|
620 |
// Oni only processes a maximum of three messages here (for performance |
621 |
// reasons?) We're actually using Windows messages for input so we need to |
622 |
// process all of them. |
623 |
MSG msg; |
624 |
while (PeekMessageA(&msg, NULL, 0, 0, PM_REMOVE)) { |
625 |
TranslateMessage(&msg); |
626 |
DispatchMessageA(&msg); |
627 |
} |
628 |
|
629 |
// Oni returns true here if there are still messages to process, so that |
630 |
// LIrMode_Set and LIrMode_Set_Internal can call this repeatedly to drain |
631 |
// all messages. We always drain all messages so return false. |
632 |
return UUcFalse; |
633 |
} |
634 |
|
635 |
static bool HandleWmInput(HRAWINPUT hri, WPARAM wparam) |
636 |
{ |
637 |
if (!LIgMode_Internal) |
638 |
return false; |
639 |
|
640 |
static RAWINPUT* ri = NULL; |
641 |
static UINT ri_size = 0; |
642 |
UINT minsize = 0; |
643 |
|
644 |
GetRawInputData(hri, RID_INPUT, NULL, &minsize, sizeof ri->header); |
645 |
if (ri_size < minsize) { |
646 |
if (ri) |
647 |
free(ri); |
648 |
ri_size = minsize; |
649 |
ri = calloc(1, ri_size); |
650 |
} |
651 |
if (GetRawInputData(hri, RID_INPUT, ri, &ri_size, sizeof ri->header) == (UINT)-1) |
652 |
return false; |
653 |
|
654 |
ProcessRawInputPacket(ri); |
655 |
return true; |
656 |
} |
657 |
|
658 |
static void HandleWmWindowPosChanged(WINDOWPOS *pos) |
659 |
{ |
660 |
if (!LIgMode_Internal) |
661 |
return; |
662 |
|
663 |
CenterCursor(); |
664 |
|
665 |
RECT rc = { pos->x, pos->y, pos->x + pos->cx, pos->y + pos->cy }; |
666 |
ClipCursor(&rc); |
667 |
} |
668 |
|
669 |
static void HandleWmKeyboard(int vkey, WORD repeat_count, WORD flags) |
670 |
{ |
671 |
if (!LIgMode_Internal) |
672 |
return; |
673 |
|
674 |
bool is_extended = flags & KF_EXTENDED; |
675 |
bool is_repeat = flags & KF_REPEAT; |
676 |
bool is_up = flags & KF_UP; |
677 |
BYTE sc = LOBYTE(flags); |
678 |
|
679 |
// Ignore togglable keys since we handle them specially |
680 |
if (vkey == VK_CAPITAL || vkey == VK_SCROLL || vkey == VK_NUMLOCK) |
681 |
return; |
682 |
|
683 |
// Ignore key down messages sent because of key-repeat |
684 |
if (!is_up && is_repeat) |
685 |
return; |
686 |
|
687 |
// Apparently some synthetic keyboard messages can be missing the scancode, |
688 |
// so get it from the vkey |
689 |
if (!sc) |
690 |
sc = MapVirtualKeyA(vkey, MAPVK_VK_TO_VSC); |
691 |
|
692 |
// DirectInput scan codes have 0x80 set for extended keys, and we're using |
693 |
// a map based on Oni's DirectInput map to convert to key codes |
694 |
if (is_extended) |
695 |
sc |= 0x80; |
696 |
uint8_t ch = DDgPlatform_ScanCodeToChar[sc]; |
697 |
if (!ch) |
698 |
return; |
699 |
|
700 |
SetKeyState(ch, !is_up); |
701 |
} |
702 |
|
703 |
bool DDrInput_WindowProc(HWND window, UINT msg, WPARAM wparam, LPARAM lparam, |
704 |
LRESULT* res) |
705 |
{ |
706 |
// This is called from our own window proc for now, so we only want to use |
707 |
// it when Daodan input is enabled |
708 |
if (!DDgUseDaodanInput) |
709 |
return false; |
710 |
|
711 |
switch (msg) { |
712 |
case WM_INPUT: |
713 |
if (HandleWmInput((HRAWINPUT)lparam, wparam)) { |
714 |
*res = 0; |
715 |
return true; |
716 |
} |
717 |
break; |
718 |
case WM_WINDOWPOSCHANGED: |
719 |
HandleWmWindowPosChanged((WINDOWPOS *)lparam); |
720 |
break; |
721 |
case WM_KEYDOWN: |
722 |
case WM_SYSKEYDOWN: |
723 |
case WM_KEYUP: |
724 |
case WM_SYSKEYUP: |
725 |
HandleWmKeyboard(LOWORD(wparam), LOWORD(lparam), HIWORD(lparam)); |
726 |
break; |
727 |
} |
728 |
|
729 |
return false; |
730 |
} |
731 |
|
732 |
static void ONICALL DDrActionBuffer_Get(short* count, LItActionBuffer** buffers) |
733 |
{ |
734 |
// So, Oni's version of this function was totally different. In unpatched |
735 |
// Oni, action buffers were produced at 60Hz by a separate high-priority |
736 |
// input thread, LIiInterruptHandleProc, and consumed by |
737 |
// LIrActionBuffer_Get, which was called from the game loop and could |
738 |
// provide multiple outstanding action buffers to the engine at once. |
739 |
// |
740 |
// That was a problem for a couple of reasons. Firstly, the resolution of |
741 |
// Windows timers is limited by the timer frequency, which can be as low as |
742 |
// 15.6ms and in the worst case would cause a delay of 31.2ms between action |
743 |
// buffers. That meant that even if Oni was running at a steady 60 fps, the |
744 |
// input thread would provide no action buffers on a lot of frames. |
745 |
// |
746 |
// Secondly, even though Oni drained the list of pending action buffers by |
747 |
// calling DDrActionBuffer_Get on every frame, the engine only uses them |
748 |
// when the internal game time advances, and that happens on a separate 60Hz |
749 |
// timer which was totally unsynchronized with the 60Hz timer on the input |
750 |
// thread. That wasn't too much of a problem when the game loop was running |
751 |
// at less than 60 fps, but when it ran faster, the only action buffers that |
752 |
// got processed were the ones produced when the game timer and the input |
753 |
// thread timer happened to tick at the same time, meaning potentially a lot |
754 |
// of dropped input. |
755 |
// |
756 |
// Oni's input system was probably designed that way so that input would |
757 |
// still run at 60Hz even on PCs that weren't powerful enough to render at |
758 |
// 60 fps. It was a well-meaning design, but due to the aforementioned |
759 |
// flaws, we do something much different and simpler here. On the frames |
760 |
// that Oni will consume input, we generate a single action buffer inside |
761 |
// DDrActionBuffer_Get based on most up-to-date input. |
762 |
|
763 |
// Update ONgGameState->TargetGameTime. We use TargetGameTime to determine |
764 |
// if Oni is going to consume input on this frame. Unfortunately, in |
765 |
// unpatched Oni, the call to ONrGameState_UpdateServerTime happened after |
766 |
// LIrActionBuffer_Get. In Daodan, we NOOP out the original call and call it |
767 |
// here instead, so it runs before our code. |
768 |
ONrGameState_UpdateServerTime(ONgGameState); |
769 |
bool time_updated = ONgGameState->GameTime != ONgGameState->TargetGameTime; |
770 |
|
771 |
// Only produce input buffers when input is enabled. LIrActionBuffer_Get |
772 |
// does the same thing. Also only produce them when Oni will consume them. |
773 |
if (!LIgMode_Internal || !time_updated) { |
774 |
*count = 0; |
775 |
*buffers = NULL; |
776 |
return; |
777 |
} |
778 |
|
779 |
// Add held keys to the action buffer |
780 |
for (int i = 0; i < ARRAY_SIZE(DDgInputState); i++) { |
781 |
if (DDgInputState[i]) { |
782 |
LIrActionBuffer_Add(&DDgActionBuffer, &(LItDeviceInput) { |
783 |
.input = i, |
784 |
.analog = 1.0, |
785 |
}); |
786 |
} |
787 |
} |
788 |
|
789 |
// Add togglable keys to the action buffer |
790 |
if (DDgCapsOniKey && (GetKeyState(VK_CAPITAL) & 0x01)) { |
791 |
LIrActionBuffer_Add(&DDgActionBuffer, &(LItDeviceInput) { |
792 |
.input = DDgCapsOniKey, |
793 |
.analog = 1.0, |
794 |
}); |
795 |
} |
796 |
if (DDgScrollOniKey && (GetKeyState(VK_SCROLL) & 0x01)) { |
797 |
LIrActionBuffer_Add(&DDgActionBuffer, &(LItDeviceInput) { |
798 |
.input = DDgScrollOniKey, |
799 |
.analog = 1.0, |
800 |
}); |
801 |
} |
802 |
if (DDgNumLockOniKey && (GetKeyState(VK_NUMLOCK) & 0x01)) { |
803 |
LIrActionBuffer_Add(&DDgActionBuffer, &(LItDeviceInput) { |
804 |
.input = DDgNumLockOniKey, |
805 |
.analog = 1.0, |
806 |
}); |
807 |
} |
808 |
|
809 |
// Make a copy of our temporary action buffer with all the input we've |
810 |
// gathered this frame. This is the copy that Oni's engine will see. |
811 |
static LItActionBuffer buf = { 0 }; |
812 |
buf = DDgActionBuffer; |
813 |
DDgActionBuffer = (LItActionBuffer) { 0 }; |
814 |
|
815 |
*count = 1; |
816 |
*buffers = &buf; |
817 |
} |
818 |
|
819 |
void DDrInput_PatchUtilityInput(void) |
820 |
{ |
821 |
// Patch the call to ONrGameState_HandleUtilityInput in |
822 |
// ONrGameState_ProcessHeartbeat. This is where Oni checks a bunch of |
823 |
// miscellaneous inputs, and where Mac Oni 1.2.1 checks the cheat bindings. |
824 |
// It's also where Mac Oni toggles the show_triggervolumes flag. |
825 |
DDrPatch_MakeCall((void *)0x004fa91c, (void *)DDrGameState_HandleUtilityInput); |
826 |
} |
827 |
|
828 |
void DDrInput_PatchCustomActions(void) |
829 |
{ |
830 |
DDrInput_PatchUtilityInput(); |
831 |
|
832 |
// Replace the function which adds bindings with ours, which checks the list |
833 |
// of custom bindings as well |
834 |
DDrPatch_MakeJump((void *)LIrBinding_Add, (void *)DDrBinding_Add); |
835 |
} |
836 |
|
837 |
void DDrInput_PatchDaodanInput(void) |
838 |
{ |
839 |
// In LIrInitialize, NOOP the call to UUrInterruptProc_Install and |
840 |
// associated error checking |
841 |
DDrPatch_NOOP((char *)(OniExe + 0x421f), 106); |
842 |
|
843 |
// In LIrPlatform_Initialize, NOOP the Windows version checks so we never |
844 |
// use DirectInput |
845 |
DDrPatch_NOOP((char *)(OniExe + 0x2e64), 11); |
846 |
|
847 |
// Replace Oni's Windows message loop with one that does buffered raw input |
848 |
// reads and processes all messages |
849 |
DDrPatch_MakeJump((void *)LIiPlatform_InputEvent_GetEvent, (void *)DDiPlatform_InputEvent_GetEvent); |
850 |
|
851 |
// Replace the function that gets the latest input frames |
852 |
DDrPatch_MakeJump((void *)LIrActionBuffer_Get, (void *)DDrActionBuffer_Get); |
853 |
|
854 |
// Replace the function that gets the mouse cursor position |
855 |
DDrPatch_MakeJump((void *)LIrPlatform_InputEvent_GetMouse, (void *)DDrPlatform_InputEvent_GetMouse); |
856 |
|
857 |
// Replace the function that performs platform-specific actions when the |
858 |
// input mode changes |
859 |
DDrPatch_MakeJump((void *)LIrPlatform_Mode_Set, (void *)DDrPlatform_Mode_Set); |
860 |
|
861 |
// Replaces the function that tests the state of keyboard keys |
862 |
DDrPatch_MakeJump((void *)LIrPlatform_TestKey, (void *)DDrPlatform_TestKey); |
863 |
|
864 |
// Enable extra key names in key_config.txt |
865 |
DDrPatch_MakeJump((void *)LIrTranslate_InputName, (void *)DDrTranslate_InputName); |
866 |
|
867 |
// Patch out the call to ONrGameState_UpdateServerTime in ONiRunGame because |
868 |
// we want to do it earlier, in DDrActionBuffer_Get |
869 |
DDrPatch_NOOP((char *)(OniExe + 0xd4708), 11); |
870 |
|
871 |
DDgUseDaodanInput = true; |
872 |
} |