[1017] | 1 | #include <stdlib.h>
|
---|
| 2 | #include <stdarg.h>
|
---|
| 3 |
|
---|
| 4 | #include "../Daodan.h"
|
---|
| 5 | #include "Input.h"
|
---|
| 6 | #include "../Oni/Oni.h"
|
---|
[1163] | 7 | #include "../Daodan_Config.h"
|
---|
[1017] | 8 | #include "../Daodan_Patch.h"
|
---|
| 9 | #include "Utility.h"
|
---|
| 10 |
|
---|
| 11 | typedef struct {
|
---|
[1163] | 12 | LItActionDescription descr;
|
---|
| 13 | DDtActionEventType eventType;
|
---|
[1017] | 14 |
|
---|
[1163] | 15 | DDtCustomActionCallback callback;
|
---|
| 16 | intptr_t ctx;
|
---|
| 17 | } DDtCustomAction;
|
---|
[1017] | 18 |
|
---|
[1163] | 19 | static DDtCustomAction DDgCustomActions[100] = { 0 };
|
---|
[1017] | 20 |
|
---|
[1163] | 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 | }
|
---|
[1017] | 162 | }
|
---|
[1163] | 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 | }
|
---|
[1017] | 401 | } else {
|
---|
[1163] | 402 | ClipCursor(NULL);
|
---|
[1017] | 403 | }
|
---|
| 404 | }
|
---|
| 405 |
|
---|
[1163] | 406 | static void ONICALL DDrPlatform_InputEvent_GetMouse(int active,
|
---|
| 407 | LItInputEvent *event)
|
---|
| 408 | {
|
---|
| 409 | POINT pt;
|
---|
| 410 | if (!GetCursorPos(&pt))
|
---|
| 411 | goto error;
|
---|
[1017] | 412 |
|
---|
[1163] | 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 | }
|
---|
[1017] | 443 | }
|
---|
[1163] | 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;
|
---|
[1017] | 460 | }
|
---|
[1163] | 461 | }
|
---|
[1017] | 462 |
|
---|
[1163] | 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 | }
|
---|
[1017] | 478 | }
|
---|
| 479 |
|
---|
[1163] | 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);
|
---|
[1017] | 599 | }
|
---|
[1163] | 600 |
|
---|
| 601 | ProcessRawInputPacket(ri);
|
---|
| 602 | ri = NEXTRAWINPUTBLOCK(ri);
|
---|
[1017] | 603 | }
|
---|
| 604 | }
|
---|
| 605 | }
|
---|
| 606 |
|
---|
[1163] | 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;
|
---|
[1017] | 633 | }
|
---|
| 634 |
|
---|
[1163] | 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 | }
|
---|