/* Autosplitter with In-Game Timer for Platform 8 Author: LuciferOfAstora Currently tested: -Platform 8 - Versions - 1.1.1. TODO: - test game / version compatibility - distinguish game save reset from immediate death - detect credits - customizable settings for categories */ state("Exit8-Win64-Shipping") { int anomsVal : "Exit8-Win64-Shipping.exe", 0x074ACFC0, 0x20, 0xEE0, 0x98, 0x20, 0xFC0, 0x220, 0x38; bool baseCleared : "Exit8-Win64-Shipping.exe", 0x074ACFC0, 0x20, 0xEE0, 0x98, 0x20, 0xFC0, 0x220, 0x28; bool allAnomalies : "Exit8-Win64-Shipping.exe", 0x074ACFC0, 0x20, 0xEE0, 0x98, 0x20, 0xFC0, 0x220, 0x29; bool announcement : "Exit8-Win64-Shipping.exe", 0x074ACFC0, 0x20, 0xFC0, 0x1E0, 0x78; int levelVal : "Exit8-Win64-Shipping.exe", 0x074ACFC0, 0x30, 0x858, 0xC0, 0x298, 0x3B8; float inGameTimer : "Exit8-Win64-Shipping.exe", 0x0702F350, 0x30, 0x18, 0x240, 0xE8, 0x3C4; double posX : "Exit8-Win64-Shipping.exe", 0x074ACFC0, 0x30, 0x338, 0x660, 0x58, 0x260; double posY : "Exit8-Win64-Shipping.exe", 0x074ACFC0, 0x30, 0x338, 0x660, 0x58, 0x268; double posZ : "Exit8-Win64-Shipping.exe", 0x074ACFC0, 0x30, 0x338, 0x660, 0x58, 0x270; } startup { vars.resetBlockedOnce = false; vars.resetBlocked = false; vars.splitOnFirstCredits = false; vars.splitOnSecondCredits = false; settings.Add("overrideCategory", false, "Override Category Default Splits"); settings.SetToolTip("overrideCategory", "Override the default splits for the selected category (Beat the Game / All Anomalies only)"); settings.Add("firstCredits", false, "First Credits", "overrideCategory"); settings.SetToolTip("firstCredits", "Split / Stop on reaching the first credits"); settings.Add("secondCredits", false, "Second Credits", "overrideCategory"); settings.SetToolTip("secondCredits", "Split / Stop on reaching the second credits"); } init { //There is a delay between the game's timer starting and the actual start of the time per the official leaderboard rules. //On my machine, that delay was measured to be 0.49s. If that should turn out to not be universal, people may have to make individual adjustments. //Additionally, the announcement adds another 13 seconds to the delay if it is active. vars.triggerOffset = current.announcement ? 13.49 : 0.49; } update { //Distinguish between a reset from All Anomaly completion, from death on first anomaly and from actual save reset //All Anomalies: Save (and flag) is reset when reaching credits -> Block reset once if (old.allAnomalies && !current.allAnomalies) { vars.resetBlockedOnce = true; } //TODO: Detect death vars.splitOnFirstCredits = settings["overrideCategory"] ? settings["firstCredits"] : timer.Run.CategoryName == "Beat The Game"; vars.splitOnSecondCredits = settings["overrideCategory"] ? settings["secondCredits"] : timer.Run.CategoryName == "All Anomalies"; } /* isLoading { //Unused due to consensus about using IGT / Real Time //May be used for automatically pause during credits and black screen for more convenient splitting using "Game Time" as a workaround for counting only the valid run time. } */ gameTime { //Returns the current in-game timer for debugging puposes. Usually differs from Real Time by some offset. //Use in runs discouraged as game time isn't supported by leaderboard and resets on certain events. return TimeSpan.FromSeconds((double) current.inGameTimer - vars.triggerOffset); } reset { current.resetCondition = current.inGameTimer <= 0 && current.levelVal == 0 && current.anomsVal == 31; if (vars.resetBlocked) { return false; } if (vars.resetBlockedOnce) { if (old.resetCondition && !current.resetCondition) //Once the reset condition is no longer given, clear the Blocked Once flag { vars.resetBlockedOnce = false; } return false; } else return current.resetCondition; } onReset //On manual reset, clear reset blocking flags { vars.resetBlockedOnce = false; vars.resetBlocked = false; } split { //Used for automatically splitting / stopping when reaching the respective credits double firstCreditThreshold = -147.0; return (vars.splitOnFirstCredits && current.levelVal == 9 && old.posY > firstCreditThreshold && current.posY <= firstCreditThreshold) || (vars.splitOnSecondCredits && old.allAnomalies && !current.allAnomalies); } start { vars.triggerOffset = current.announcement ? 13.49 : 0.49; //Only update while timer is not running //Start when // - remaining anomalies = 31 // - inGameTimer crosses offset return current.inGameTimer >= vars.triggerOffset && old.inGameTimer < vars.triggerOffset && current.levelVal == 0 && current.anomsVal == 31; }