Created
February 15, 2026 17:41
-
-
Save CryZe/ff345df62a73e06f3ae32572e746c145 to your computer and use it in GitHub Desktop.
A Hat in Time LibreSplit Auto Splitter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| local function default_settings() | |
| local s = { | |
| settings_ILMode = false, | |
| settings_newFileStart = true, | |
| settings_gameTimeMsg = true, | |
| splits = true, | |
| splits_tp = true, | |
| splits_tp_new = true, | |
| splits_tp_any = true, | |
| splits_tp_std = true, | |
| splits_actEntry = false, | |
| splits_checkpoint = false, | |
| splits_yarn = false, | |
| splits_dwbth = false, | |
| splits_dwbth_doubleSplitNo = true, | |
| manySplits = false, | |
| manySplits_riftBlue = true, | |
| manySplits_riftPurple = true, | |
| manySplits_4_99_entry = false, | |
| manySplits_4_99_cp0_pause_pos = false, | |
| manySplits_pos_40 = false, | |
| manySplits_pos_41 = false, | |
| manySplits_pos_42 = false, | |
| manySplits_pos_43 = false, | |
| manySplits_pos_44 = false, | |
| manySplits_pos_45 = false, | |
| manySplits_5_1_cp1 = false, | |
| manySplits_5_1_cp2 = false, | |
| manySplits_5_1_cp3 = false, | |
| manySplits_pos_5 = false, | |
| manySplits_5_1_cp10 = false, | |
| manySplits_1_1_cp1 = false, | |
| manySplits_1_2_cp1 = false, | |
| manySplits_1_3_cp1 = false, | |
| manySplits_1_4_cp0_pause_pos = false, | |
| manySplits_1_4_cp1 = false, | |
| manySplits_1_4_cp2 = false, | |
| manySplits_1_6_cp59_pause = false, | |
| manySplits_2_1_cp5 = false, | |
| manySplits_2_4_cp1 = false, | |
| manySplits_2_4_cp4 = false, | |
| manySplits_2_5_cp1 = false, | |
| manySplits_2_5_cp2 = false, | |
| manySplits_2_5_cp3 = false, | |
| manySplits_2_6_cp0_pause_pos = false, | |
| manySplits_3_2_cp0_pause_pos = false, | |
| manySplits_3_3_cp1 = false, | |
| manySplits_3_4_cp0_pause_pos = false, | |
| manySplits_3_6_cp1 = false, | |
| manySplits_6_1_cp3 = false, | |
| manySplits_6_2_cp1 = false, | |
| manySplits_6_3_cp1 = false, | |
| manySplits_6_3_cp2 = false, | |
| manySplits_6_3_cp3 = false, | |
| } | |
| local act_names = { | |
| {"Welcome to Mafia Town", "Barrel Battle", "She Came From Outer Space", "Down With The Mafia!", "Cheating The Race", "Heating Up Mafia Town", "The Golden Vault"}, | |
| {"Dead Bird Studio", "Murder on the Owl Express", "Picture Perfect", "Train Rush", "The Big Parade", "Award Ceremony", ""}, | |
| {"Contractual Obligations", "The Subcon Well", "Toilet of Doom", "Queen Vanessa's Manor", "Mail Delivery Service", "Your Contract Has Expired", ""}, | |
| {"Bon Voyage!", "Ship Shape", "Rock The Boat", "", "", "", ""}, | |
| } | |
| for i = 1, 7 do | |
| s["manySplits_" .. i] = true | |
| end | |
| for j = 1, 7 do | |
| for i = 1, 7 do | |
| if j <= 4 and act_names[j][i] ~= "" then | |
| local chapter = (j == 4) and 6 or j | |
| s["manySplits_" .. chapter .. "_" .. i] = true | |
| s["manySplits_" .. chapter .. "_" .. i .. "_entry"] = false | |
| s["manySplits_" .. chapter .. "_" .. i .. "_tp"] = true | |
| s["manySplits_" .. chapter .. "_" .. i .. "_tpDelayed"] = false | |
| else | |
| break | |
| end | |
| end | |
| if j == 5 or j == 7 then | |
| s["manySplits_" .. j .. "_entry"] = false | |
| end | |
| if j == 4 or j == 5 or j == 7 then | |
| s["manySplits_" .. j .. "_tp"] = true | |
| end | |
| end | |
| local rift_ids = { | |
| {"gallery", "lab", "sewers", "bazaar", "owlExpress", "moon", "village", "pipe", "twilight", "curly", "balcony"}, | |
| {"moc", "dbs", "sleepy", "alpine", "deepSea", "rumbi", "tour", "", "", "", ""}, | |
| } | |
| for i = 1, 11 do | |
| local blue = rift_ids[1][i] | |
| s["manySplits_riftBlue_" .. blue] = true | |
| s["manySplits_riftBlue_" .. blue .. "_entry"] = false | |
| s["manySplits_riftBlue_" .. blue .. "_tp"] = true | |
| local purple = rift_ids[2][i] | |
| if purple ~= "" then | |
| s["manySplits_riftPurple_" .. purple] = true | |
| s["manySplits_riftPurple_" .. purple .. "_entry"] = false | |
| s["manySplits_riftPurple_" .. purple .. "_cp"] = false | |
| s["manySplits_riftPurple_" .. purple .. "_tp"] = true | |
| end | |
| end | |
| return s | |
| end | |
| local SETTINGS = default_settings() | |
| local function enabled(key) | |
| return SETTINGS[key] == true | |
| end | |
| local SIG_TIMER = "54 49 4D 52 ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? 45 4E 44 20" | |
| local SIG_SAVE_VACU = "48 8B 1D ?? ?? ?? ?? 48 85 DB 74 ?? 48 8B 5B ?? 48 85 DB 74 ??" | |
| local SIG_SAVE_DLC = "48 8B 05 ?? ?? ?? ?? 48 8B 74 24 ?? 48 83 C4 50" | |
| local SIG_SAVE_MOD = "48 8B 05 ?? ?? ?? ?? 48 8B D9 48 85 C0 75 ?? 48 89 7C 24 ??" | |
| local SIG_SAVE_REL = "48 8B 05 ?? ?? ?? ?? 48 8B 7C 24 ?? 48 83 C4 40" | |
| local SIG_ACTORS = "48 8B 05 ?? ?? ?? ?? 81 88 ?? ?? ?? ?? 00 00 80 00" | |
| local SIG_HATKID = "48 8B 81 ?? ?? ?? ?? 4C 8D 80 ?? ?? ?? ??" | |
| local vars = { | |
| initialized = false, | |
| saveDataPtrType = "none", | |
| timerPtr = nil, | |
| saveDataPtrAddress = nil, | |
| actorsPtrAddress = nil, | |
| coordsOffsetPtrAddress = nil, | |
| saveDataAddressFinal = nil, | |
| actorsAddressFinal = nil, | |
| hatKidOffset = nil, | |
| lastChapter = 0, | |
| gameTimeFinal = 0.0, | |
| savedGameTime = 0.0, | |
| splitInLoadScreen = false, | |
| currentRift = "none", | |
| justEnteredRift = false, | |
| posSplitKey = 0.0, | |
| splitLockMs = 0, | |
| current = {}, | |
| old = {}, | |
| nextScanAt = 0, | |
| } | |
| local posSplitKeysDict = { | |
| [4] = { | |
| {-11000, -9000, 32000, 34000, -1000, 500, 40}, | |
| {36500, 38000, -13000, -11000, 4500, 5500, 41}, | |
| {47000, 49000, 21000, 22000, 1000, 2500, 42}, | |
| {7000, 8500, 43000, 45000, -1000, 1000, 43}, | |
| {-15300, -14400, 47750, 49000, -1650, -1300, 44}, | |
| {41000, 42500, 29000, 30500, 2400, 2800, 45}, | |
| }, | |
| [5] = { | |
| {-30000, -28000, -2, -2, -2, -2, 5}, | |
| }, | |
| } | |
| local posSplitsDict = { | |
| [40] = {-24000, -23000, 29000, 30500, 4000, 6000}, | |
| [41] = {3000, 30400, -28500, -27353, 3200, 4000}, | |
| [42] = {71600, 72300, 21500, 22700, 1500, 2100}, | |
| [43] = {4600, 8700, 69000, 70000, 4000, 5400}, | |
| [44] = {-2, -2, -2, -2, -2, -9680}, | |
| [45] = {-2, -2, -2, -2, -2, -4230}, | |
| [5] = {-38300, -38190, -85000, -84000, -2, -2}, | |
| } | |
| local function now_ms() | |
| return math.floor(os.clock() * 1000) | |
| end | |
| local function rip_relative_target(addr) | |
| local rel = readAddress("int", addr) | |
| if rel == nil then | |
| return nil | |
| end | |
| return addr + rel + 4 | |
| end | |
| local function read_chain(ty, base, offsets) | |
| if base == nil then | |
| return nil | |
| end | |
| if offsets == nil or #offsets == 0 then | |
| return readAddress(ty, base) | |
| end | |
| local args = {ty, base} | |
| for i = 1, #offsets do | |
| args[#args + 1] = offsets[i] | |
| end | |
| return readAddress(table.unpack(args)) | |
| end | |
| local function deep_read(ty, root, ...) | |
| local offsets = {...} | |
| return read_chain(ty, root, offsets) | |
| end | |
| local function in_range(v, lo, hi) | |
| return v > lo and v < hi | |
| end | |
| local function pos_match(x, y, z, p) | |
| return (x > p[1] or p[1] == -2) and (x < p[2] or p[2] == -2) | |
| and (y > p[3] or p[3] == -2) and (y < p[4] or p[4] == -2) | |
| and (z > p[5] or p[5] == -2) and (z < p[6] or p[6] == -2) | |
| end | |
| local function current_rift_check(chapter, x, y, z) | |
| if (chapter == 1 and in_range(x, 4070, 4250) and in_range(y, -5550, -5350) and in_range(z, -900, -700)) | |
| or (chapter == 1 and in_range(x, 13200, 13250) and in_range(y, -120, -80) and z > -250) | |
| then | |
| return "manySplits_riftPurple_moc" | |
| elseif (chapter == 2 and in_range(x, 8350, 8500) and in_range(y, -2250, -2100) and in_range(z, 2700, 2870)) | |
| or (chapter == 2 and in_range(x, 7750, 7800) and in_range(y, -10500, -10400) and z > 1000) | |
| then | |
| return "manySplits_riftPurple_dbs" | |
| elseif (chapter == 3 and in_range(x, 13100, 13300) and in_range(y, 25150, 25350) and in_range(z, 2300, 2500)) | |
| or (chapter == 3 and in_range(x, 6100, 6150) and in_range(y, 32400, 32500) and z > 550) | |
| then | |
| return "manySplits_riftPurple_sleepy" | |
| elseif (chapter == 4 and in_range(x, 16700, 16850) and in_range(y, 28750, 28900) and in_range(z, -5100, -4800)) | |
| or (chapter == 4 and in_range(x, -11700, -11600) and in_range(y, -11700, -11600) and z > -4000) | |
| then | |
| return "manySplits_riftPurple_alpine" | |
| elseif (chapter == 6 and in_range(x, 1860, 3000) and in_range(y, 6400, 6550) and in_range(z, 4000, 4200)) | |
| or (chapter == 6 and in_range(x, -15700, -15600) and in_range(y, 870, 910) and z > 3200) | |
| then | |
| return "manySplits_riftPurple_deepSea" | |
| elseif (chapter == 7 and in_range(x, 750, 900) and in_range(y, 1200, 1350) and in_range(z, 1250, 1450)) | |
| or (chapter == 7 and in_range(x, -400, -300) and in_range(y, -8400, -8300) and z > 1000) | |
| then | |
| return "manySplits_riftPurple_rumbi" | |
| elseif (in_range(x, -4220, -4120) and in_range(y, -800, -710) and in_range(z, 1300, 1500)) | |
| or (in_range(x, 13100, 13200) and in_range(y, -250, -200) and z > 2650) | |
| then | |
| return "manySplits_riftPurple_tour" | |
| elseif (chapter == 1 and in_range(x, -200, -150) and in_range(y, -410, -360) and in_range(z, 60, 120)) | |
| or (chapter == 1 and in_range(x, 640, 850) and in_range(y, 4000, 4140) and in_range(z, 760, 960)) | |
| then | |
| return "manySplits_riftBlue_sewers" | |
| elseif (chapter == 1 and in_range(x, -200, -150) and in_range(y, -150, -120) and in_range(z, 60, 120)) | |
| or (chapter == 1 and in_range(x, -3150, -3030) and in_range(y, -2480, -2360) and in_range(z, 400, 600)) | |
| then | |
| return "manySplits_riftBlue_bazaar" | |
| elseif (chapter == 2 and in_range(x, -2710, -2500) and in_range(y, 1020, 1230) and in_range(z, 10, 100)) | |
| or (chapter == 2 and in_range(x, -53920, -53810) and in_range(y, -9260, -9150) and in_range(z, 100, 290)) | |
| then | |
| return "manySplits_riftBlue_owlExpress" | |
| elseif (chapter == 2 and in_range(x, 2600, 2850) and in_range(y, 850, 1100) and in_range(z, 0, 100)) | |
| or (chapter == 2 and in_range(x, -3950, -3830) and in_range(y, -100, 0) and in_range(z, 390, 570)) | |
| then | |
| return "manySplits_riftBlue_moon" | |
| elseif (chapter == 3 and in_range(x, -2800, -2400) and in_range(y, -1800, -1450) and in_range(z, 60, 120)) | |
| or (chapter == 3 and in_range(x, 1190, 1290) and in_range(y, 26250, 26350) and in_range(z, 3450, 3630)) | |
| then | |
| return "manySplits_riftBlue_village" | |
| elseif (chapter == 3 and in_range(x, 1780, 2000) and in_range(y, -11384, -9350) and in_range(z, 60, 120)) | |
| or (chapter == 3 and in_range(x, -130, -20) and in_range(y, 7250, 7360) and in_range(z, 500, 640)) | |
| then | |
| return "manySplits_riftBlue_pipe" | |
| elseif (chapter == 4 and in_range(x, 7600, 7900) and in_range(y, 1100, 1400) and in_range(z, -500, -100)) | |
| or (chapter == 4 and in_range(x, 6330, 6450) and in_range(y, 72060, 72180) and in_range(z, 5150, 5300)) | |
| then | |
| return "manySplits_riftBlue_twilight" | |
| elseif (chapter == 4 and in_range(x, 5700, 6000) and in_range(y, 3100, 3400) and in_range(z, -500, -100)) | |
| or (chapter == 4 and in_range(x, 56380, 56500) and in_range(y, 6740, 6840) and in_range(z, -1690, -1500)) | |
| then | |
| return "manySplits_riftBlue_curly" | |
| elseif (chapter == 6 and in_range(x, 3700, 3950) and in_range(y, 4300, 4550) and in_range(z, 1200, 1400)) | |
| or (chapter == 6 and in_range(x, -950, -840) and in_range(y, -1410, -1310) and in_range(z, 7690, 7850)) | |
| then | |
| return "manySplits_riftBlue_balcony" | |
| elseif (in_range(x, -1180, -920) and in_range(y, 4050, 4350) and in_range(z, 200, 300)) | |
| or (in_range(x, -3940, -3840) and in_range(y, -80, 20) and in_range(z, -520, -350)) | |
| then | |
| return "manySplits_riftBlue_lab" | |
| elseif (in_range(x, 7250, 7550) and in_range(y, 100, 400) and in_range(z, -500, -400)) | |
| or (in_range(x, 3100, 3220) and in_range(y, -2400, -2270) and in_range(z, 130, 310)) | |
| then | |
| return "manySplits_riftBlue_gallery" | |
| end | |
| return "none" | |
| end | |
| local function back_to_hub_check(chapter, x, y, z) | |
| return (in_range(x, -690, -660) and in_range(y, 1525, 1555) and in_range(z, 225, 260)) | |
| or (chapter == 1 and in_range(x, 195, 425) and in_range(y, 0, 200)) | |
| or (chapter == 2 and in_range(x, 2345, 2555) and in_range(y, 110, 320) and in_range(z, 0, 200)) | |
| or (chapter == 2 and in_range(x, 2300, 2550) and in_range(y, -160, 60) and in_range(z, 0, 200)) | |
| or (chapter == 3 and in_range(x, -2120, -1910) and in_range(y, -2010, -1800) and in_range(z, 0, 150)) | |
| or (chapter == 4 and in_range(x, -690, -480) and in_range(y, -4065, -3855) and in_range(z, -325, -240)) | |
| or (chapter == 5 and in_range(x, -1365, -1155) and in_range(y, -125, 85) and in_range(z, 280, 350)) | |
| or (chapter == 6 and in_range(x, 19365, 21375) and in_range(y, -24840, -22830) and in_range(z, 1200, 1400)) | |
| or (chapter == 7 and in_range(x, 25260, 27270) and in_range(y, -24015, -22005) and in_range(z, 1190, 1500)) | |
| or (chapter == 97 and in_range(x, -2410, -2200) and in_range(y, -2185, -1975) and in_range(z, -65, 200)) | |
| or (chapter == 99 and in_range(x, 1500, 1710) and in_range(y, 1525, 1735) and in_range(z, 400, 500)) | |
| end | |
| local function should_split_at_this_pos_pause(chapter, x, y, z) | |
| return (chapter == 1 and enabled("manySplits_1_4_cp0_pause_pos") and in_range(x, 3500, 4500) and in_range(y, -3500, -3000) and in_range(z, 8000, 9000)) | |
| or (chapter == 2 and enabled("manySplits_2_6_cp0_pause_pos") and in_range(x, 5500, 9500) and in_range(y, 5000, 12000) and z < 4000) | |
| or (chapter == 3 and enabled("manySplits_3_2_cp0_pause_pos") and in_range(x, 15800, 17000) and in_range(y, 10900, 11900) and z < 2000) | |
| or (chapter == 3 and enabled("manySplits_3_4_cp0_pause_pos") and in_range(x, -28000, -26000) and in_range(y, 2000, 3400) and in_range(z, 200, 1200)) | |
| or (chapter == 4 and enabled("manySplits_4_99_cp0_pause_pos") and in_range(x, 37000, 41000) and in_range(y, 47000, 51000) and in_range(z, -14000, -5000)) | |
| end | |
| local function emit_split() | |
| vars.splitLockMs = now_ms() | |
| vars.posSplitKey = 0 | |
| return true | |
| end | |
| local function elapsed_ms() | |
| return now_ms() - vars.splitLockMs | |
| end | |
| local function try_initialize() | |
| if now_ms() < vars.nextScanAt then | |
| return false | |
| end | |
| vars.timerPtr = vars.timerPtr or sig_scan(SIG_TIMER, 0) | |
| if vars.timerPtr == nil then | |
| vars.nextScanAt = now_ms() + 1000 | |
| return false | |
| end | |
| if vars.saveDataPtrAddress == nil then | |
| local addr = sig_scan(SIG_SAVE_VACU, 3) | |
| if addr ~= nil then | |
| vars.saveDataPtrType = "DLCPtr" | |
| vars.saveDataPtrAddress = addr | |
| end | |
| end | |
| if vars.saveDataPtrAddress == nil then | |
| local addr = sig_scan(SIG_SAVE_DLC, 3) | |
| if addr ~= nil then | |
| vars.saveDataPtrType = "DLCPtr" | |
| vars.saveDataPtrAddress = addr | |
| end | |
| end | |
| if vars.saveDataPtrAddress == nil then | |
| local addr = sig_scan(SIG_SAVE_MOD, 3) | |
| if addr ~= nil then | |
| vars.saveDataPtrType = "ModdingPtr" | |
| vars.saveDataPtrAddress = addr | |
| end | |
| end | |
| if vars.saveDataPtrAddress == nil then | |
| local addr = sig_scan(SIG_SAVE_REL, 3) | |
| if addr ~= nil then | |
| vars.saveDataPtrType = "1.0Ptr" | |
| vars.saveDataPtrAddress = addr | |
| end | |
| end | |
| vars.actorsPtrAddress = vars.actorsPtrAddress or sig_scan(SIG_ACTORS, 3) | |
| vars.coordsOffsetPtrAddress = vars.coordsOffsetPtrAddress or sig_scan(SIG_HATKID, 3) | |
| if vars.saveDataPtrAddress == nil or vars.actorsPtrAddress == nil or vars.coordsOffsetPtrAddress == nil then | |
| vars.nextScanAt = now_ms() + 1000 | |
| return false | |
| end | |
| vars.saveDataAddressFinal = rip_relative_target(vars.saveDataPtrAddress) | |
| vars.actorsAddressFinal = rip_relative_target(vars.actorsPtrAddress) | |
| vars.hatKidOffset = readAddress("int", vars.coordsOffsetPtrAddress) | |
| if vars.saveDataAddressFinal == nil or vars.actorsAddressFinal == nil or vars.hatKidOffset == nil then | |
| vars.nextScanAt = now_ms() + 1000 | |
| return false | |
| end | |
| local guard = readAddress("int", vars.saveDataAddressFinal) | |
| if guard == nil or guard == 0 then | |
| vars.nextScanAt = now_ms() + 1000 | |
| return false | |
| end | |
| local guard_actors = readAddress("int", vars.actorsAddressFinal) | |
| if guard_actors == nil or guard_actors == 0 then | |
| vars.nextScanAt = now_ms() + 1000 | |
| return false | |
| end | |
| vars.initialized = true | |
| print("[ahit] initialized memory signatures") | |
| return true | |
| end | |
| local function read_game_state() | |
| vars.old = shallow_copy_tbl(vars.current) | |
| vars.current = {} | |
| local c = vars.current | |
| c.timerState = readAddress("int", vars.timerPtr + 0x04) | |
| c.unpauseTime = readAddress("double", vars.timerPtr + 0x08) | |
| c.gameTimerIsPaused = readAddress("int", vars.timerPtr + 0x10) | |
| c.actTimerIsPaused = readAddress("int", vars.timerPtr + 0x14) | |
| c.actTimerIsVisible = readAddress("int", vars.timerPtr + 0x18) | |
| c.unpauseTimeIsDirty = readAddress("int", vars.timerPtr + 0x1C) | |
| c.justGotTimePiece = readAddress("int", vars.timerPtr + 0x20) | |
| c.gameTime = readAddress("double", vars.timerPtr + 0x24) | |
| c.actTime = readAddress("double", vars.timerPtr + 0x2C) | |
| c.realGameTime = readAddress("double", vars.timerPtr + 0x34) | |
| c.realActTime = readAddress("double", vars.timerPtr + 0x3C) | |
| c.timePieceCount = readAddress("int", vars.timerPtr + 0x44) | |
| if vars.saveDataPtrType == "DLCPtr" then | |
| c.yarn = deep_read("int", vars.saveDataAddressFinal, 0x68, 0xF0) | |
| c.chapter = deep_read("int", vars.saveDataAddressFinal, 0x68, 0x108) | |
| c.act = deep_read("int", vars.saveDataAddressFinal, 0x68, 0x10C) | |
| c.checkpoint = deep_read("int", vars.saveDataAddressFinal, 0x68, 0x110) | |
| elseif vars.saveDataPtrType == "ModdingPtr" then | |
| c.yarn = deep_read("int", vars.saveDataAddressFinal, 0x64, 0xE0) | |
| c.chapter = deep_read("int", vars.saveDataAddressFinal, 0x64, 0xF8) | |
| c.act = deep_read("int", vars.saveDataAddressFinal, 0x64, 0xFC) | |
| c.checkpoint = deep_read("int", vars.saveDataAddressFinal, 0x64, 0x100) | |
| else | |
| c.yarn = deep_read("int", vars.saveDataAddressFinal, 0x64, 0xE0) | |
| c.chapter = deep_read("int", vars.saveDataAddressFinal, 0x64, 0xF4) | |
| c.act = deep_read("int", vars.saveDataAddressFinal, 0x64, 0xF8) | |
| c.checkpoint = deep_read("int", vars.saveDataAddressFinal, 0x64, 0xFC) | |
| end | |
| c.x = deep_read("float", vars.actorsAddressFinal, 0x6DC, 0x00, 0x68, vars.hatKidOffset, 0x80) | |
| c.y = deep_read("float", vars.actorsAddressFinal, 0x6DC, 0x00, 0x68, vars.hatKidOffset, 0x84) | |
| c.z = deep_read("float", vars.actorsAddressFinal, 0x6DC, 0x00, 0x68, vars.hatKidOffset, 0x88) | |
| end | |
| local function all_position_values_available(c) | |
| return c.chapter ~= nil and c.x ~= nil and c.y ~= nil and c.z ~= nil | |
| end | |
| function startup() | |
| process("HatinTimeGame", "first") | |
| useGameTime = true | |
| mapsCacheCycles = 2 | |
| end | |
| function state() | |
| if not vars.initialized and not try_initialize() then | |
| return | |
| end | |
| read_game_state() | |
| local c = vars.current | |
| local o = vars.old | |
| if c.chapter ~= nil and o.chapter ~= nil and c.chapter ~= o.chapter then | |
| vars.lastChapter = o.chapter | |
| end | |
| vars.gameTimeFinal = c.realGameTime or 0.0 | |
| if c.actTimerIsVisible == 1 and o.actTimerIsVisible == 0 and c.gameTime ~= nil then | |
| vars.savedGameTime = c.gameTime | |
| end | |
| if (c.timePieceCount ~= nil and o.timePieceCount ~= nil and c.timePieceCount == o.timePieceCount + 1) | |
| or (c.justGotTimePiece == 1 and o.justGotTimePiece == 0) | |
| then | |
| if enabled("manySplits_" .. tostring(c.chapter) .. "_" .. tostring(c.act) .. "_tpDelayed") then | |
| vars.splitInLoadScreen = true | |
| end | |
| if c.realActTime ~= nil and c.realGameTime ~= nil then | |
| local candidate = vars.savedGameTime + c.realActTime | |
| if candidate > c.realGameTime - 0.1 and candidate < c.realGameTime + 0.1 then | |
| vars.gameTimeFinal = candidate | |
| end | |
| end | |
| end | |
| if c.gameTimerIsPaused == 0 and o.gameTimerIsPaused == 1 then | |
| vars.splitInLoadScreen = false | |
| vars.posSplitKey = 0 | |
| if all_position_values_available(c) and back_to_hub_check(c.chapter, c.x, c.y, c.z) then | |
| vars.currentRift = "none" | |
| end | |
| end | |
| if c.timerState == 0 then | |
| vars.currentRift = "none" | |
| end | |
| if vars.justEnteredRift then | |
| vars.justEnteredRift = false | |
| end | |
| if c.gameTimerIsPaused ~= o.gameTimerIsPaused and vars.currentRift == "none" and enabled("manySplits") and all_position_values_available(c) then | |
| vars.currentRift = current_rift_check(c.chapter, c.x, c.y, c.z) | |
| if vars.currentRift ~= "none" then | |
| vars.justEnteredRift = true | |
| end | |
| end | |
| if (c.chapter == 4 or c.chapter == 5) and all_position_values_available(c) then | |
| local chapter_positions = posSplitKeysDict[c.chapter] | |
| if chapter_positions ~= nil then | |
| for i = 1, #chapter_positions do | |
| local position = chapter_positions[i] | |
| if pos_match(c.x, c.y, c.z, position) then | |
| vars.posSplitKey = position[7] | |
| end | |
| end | |
| end | |
| end | |
| end | |
| function gameTime() | |
| if enabled("settings_ILMode") then | |
| return math.floor((vars.current.realActTime or 0) * 1000) | |
| end | |
| return math.floor((vars.gameTimeFinal or 0) * 1000) | |
| end | |
| function start() | |
| local c = vars.current | |
| local o = vars.old | |
| if c.timerState == nil or o.timerState == nil then | |
| return false | |
| end | |
| if enabled("settings_newFileStart") and c.timerState == 1 and o.timerState == 0 and (c.timePieceCount == 0 or c.timePieceCount == -1) then | |
| return true | |
| end | |
| if (not enabled("settings_newFileStart")) and c.timerState == 1 and o.timerState == 0 then | |
| return true | |
| end | |
| if enabled("settings_ILMode") and c.actTimerIsVisible == 1 and (c.realActTime or -1) == 0 then | |
| return true | |
| end | |
| return false | |
| end | |
| function split() | |
| local c = vars.current | |
| local o = vars.old | |
| if c.timerState == nil or o.timerState == nil then | |
| return false | |
| end | |
| if elapsed_ms() <= 1000 or c.timerState == 0 then | |
| return false | |
| end | |
| local tp_new = c.timePieceCount ~= nil and o.timePieceCount ~= nil and c.timePieceCount == o.timePieceCount + 1 | |
| local tp_any = c.justGotTimePiece == 1 and o.justGotTimePiece == 0 | |
| if enabled("splits") then | |
| if tp_new and enabled("splits_tp_new") then | |
| return emit_split() | |
| end | |
| if tp_any and enabled("splits_tp_any") then | |
| return emit_split() | |
| end | |
| if c.actTimerIsVisible == 1 and o.actTimerIsVisible == 0 and vars.currentRift == "none" and enabled("splits_actEntry") and not enabled("settings_ILMode") then | |
| return emit_split() | |
| end | |
| if tp_any and c.chapter == 3 and vars.lastChapter == 5 and enabled("splits_tp_std") then | |
| return emit_split() | |
| end | |
| if c.chapter == 97 and o.chapter ~= 97 and enabled("splits_dwbth") then | |
| if (enabled("splits_dwbth_doubleSplitNo") and elapsed_ms() > 9000) or (not enabled("splits_dwbth_doubleSplitNo")) then | |
| return emit_split() | |
| end | |
| end | |
| if c.yarn ~= nil and o.yarn ~= nil and c.yarn == o.yarn + 1 and enabled("splits_yarn") then | |
| return emit_split() | |
| end | |
| if c.checkpoint ~= nil and o.checkpoint ~= nil and c.checkpoint ~= o.checkpoint and c.checkpoint ~= 0 and enabled("splits_checkpoint") then | |
| return emit_split() | |
| end | |
| end | |
| if enabled("manySplits") and vars.currentRift == "none" then | |
| if (tp_new or tp_any) and (enabled("manySplits_" .. tostring(c.chapter) .. "_" .. tostring(c.act) .. "_tp") or enabled("manySplits_" .. tostring(c.chapter) .. "_tp")) then | |
| return emit_split() | |
| end | |
| if c.checkpoint ~= nil and o.checkpoint ~= nil and c.checkpoint ~= o.checkpoint and enabled("manySplits_" .. tostring(c.chapter) .. "_" .. tostring(c.act) .. "_cp" .. tostring(c.checkpoint)) then | |
| return emit_split() | |
| end | |
| if vars.posSplitKey ~= 0 and enabled("manySplits_pos_" .. tostring(vars.posSplitKey)) then | |
| local p = posSplitsDict[vars.posSplitKey] | |
| if p ~= nil and all_position_values_available(c) and pos_match(c.x, c.y, c.z, p) then | |
| return emit_split() | |
| end | |
| end | |
| if vars.splitInLoadScreen and c.gameTimerIsPaused == 1 and o.gameTimerIsPaused == 0 then | |
| return emit_split() | |
| end | |
| if c.actTimerIsVisible == 1 and o.actTimerIsVisible == 0 and not enabled("settings_ILMode") | |
| and (enabled("manySplits_" .. tostring(c.chapter) .. "_" .. tostring(c.act) .. "_entry") or enabled("manySplits_" .. tostring(c.chapter) .. "_entry")) | |
| then | |
| return emit_split() | |
| end | |
| if c.gameTimerIsPaused == 1 and o.gameTimerIsPaused == 0 and enabled("manySplits_" .. tostring(c.chapter) .. "_" .. tostring(c.act) .. "_cp" .. tostring(c.checkpoint) .. "_pause") then | |
| return emit_split() | |
| end | |
| if c.gameTimerIsPaused ~= o.gameTimerIsPaused and all_position_values_available(c) and should_split_at_this_pos_pause(c.chapter, c.x, c.y, c.z) then | |
| return emit_split() | |
| end | |
| end | |
| if enabled("manySplits") and vars.currentRift ~= "none" then | |
| if (tp_new or tp_any) and enabled(vars.currentRift .. "_tp") then | |
| return emit_split() | |
| end | |
| if c.checkpoint ~= nil and o.checkpoint ~= nil and c.checkpoint > o.checkpoint and enabled(vars.currentRift .. "_cp") then | |
| return emit_split() | |
| end | |
| if vars.justEnteredRift and enabled(vars.currentRift .. "_entry") and not enabled("settings_ILMode") and elapsed_ms() > 3000 then | |
| return emit_split() | |
| end | |
| end | |
| return false | |
| end | |
| function reset() | |
| local c = vars.current | |
| local o = vars.old | |
| if c.timerState == nil or o.timerState == nil then | |
| return false | |
| end | |
| if c.timerState == 0 and o.timerState == 1 then | |
| return true | |
| end | |
| if enabled("settings_ILMode") then | |
| if c.actTimerIsVisible == 0 and o.actTimerIsVisible == 1 then | |
| return true | |
| end | |
| if c.realActTime ~= nil and o.realActTime ~= nil and c.realActTime < o.realActTime and c.realActTime == 0 then | |
| return true | |
| end | |
| end | |
| return false | |
| end | |
| function isLoading() | |
| return true | |
| end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment