state("Animal Well") {} startup { refreshRate = 120; settings.Add("st", true, "Starting"); settings.Add("st-new", true, "Start on 'new game'", "st"); settings.Add("sp", true, "Splitting"); settings.Add("sp-end", true, "Split on endings", "sp"); settings.Add("sp-end-normal", true, "Normal ending / Fireworks", "sp-end"); settings.Add("sp-end-true", true, "True ending / Ride manticore", "sp-end"); settings.Add("sp-end-bdtp", true, "BDTP / Eaten by Big Chungus", "sp-end"); settings.Add("sp-end-popup", false, "Timer popup on fireworks or true ending (alternative)", "sp-end"); settings.SetToolTip("sp-end-popup", "Getting the accurate IGT requires 'show time' option enabled.\nTrue ending also requires to have picked up the Decorative Bunny fig (4h speedrun)."); settings.Add("sp-equipment", true, "Split on equipment", "sp"); settings.Add("sp-equipment-1", true, "Firecracker", "sp-equipment"); settings.Add("sp-equipment-2", true, "Animal Flute", "sp-equipment"); settings.Add("sp-equipment-3", true, "Lantern", "sp-equipment"); settings.Add("sp-equipment-4", true, "Top", "sp-equipment"); settings.Add("sp-equipment-5", true, "Disc", "sp-equipment"); settings.Add("sp-equipment-6", true, "B. Wand", "sp-equipment"); settings.Add("sp-equipment-7", true, "Yoyo", "sp-equipment"); settings.Add("sp-equipment-8", true, "Slink", "sp-equipment"); settings.Add("sp-equipment-9", true, "Remote", "sp-equipment"); settings.Add("sp-equipment-10", true, "Ball", "sp-equipment"); settings.Add("sp-equipment-11", true, "Wheel", "sp-equipment"); settings.Add("sp-equipment-12", true, "UV Light", "sp-equipment"); settings.Add("sp-items", true, "Split on items", "sp"); settings.Add("sp-items-0", true, "Mock Disc", "sp-items"); settings.Add("sp-items-1", true, "S. Medal", "sp-items"); settings.Add("sp-items-2", false, "Cake", "sp-items"); settings.Add("sp-items-3", true, "House Key", "sp-items"); settings.Add("sp-items-4", true, "Office Key", "sp-items"); settings.Add("sp-items-5", false, "Closet Key", "sp-items"); settings.Add("sp-items-6", true, "E. Medal", "sp-items"); settings.Add("sp-items-7", true, "F. Pack", "sp-items"); settings.Add("sp-flames", true, "Split on flames", "sp"); settings.Add("sp-flames-0", true, "Blue / Seahorse", "sp-flames"); settings.Add("sp-flames-1", true, "Purple / Dog", "sp-flames"); settings.Add("sp-flames-2", true, "Violet / Chameleon", "sp-flames"); settings.Add("sp-flames-3", true, "Green / Ostrich", "sp-flames"); settings.Add("sp-bunnies", false, "Split on bunnies", "sp"); settings.Add("rs", true, "Resetting"); settings.Add("rs-load", true, "Reset on opening 'load game' menu", "rs"); settings.SetToolTip("rs-load", "Also resets automatically if you start a new game\non a slot that doesn't have a save yet."); settings.Add("tm", true, "Timing"); settings.Add("tm-force", true, "Force LiveSplit timing method to Game Time", "tm"); settings.Add("tm-popup", false, "Show latest IGT from ending popup on layout (for SRC)", "tm"); settings.SetToolTip("tm-popup", "Getting the accurate IGT requires 'show time' option enabled.\nTrue ending also requires to have picked up the Decorative Bunny fig (4h speedrun)."); settings.Add("tm-format", false, "Format Game Time as H:M:S:frames (for SRC, weird)", "tm"); settings.SetToolTip("tm-format", "Using the term format very loosely here.\nIt just tricks the timer to skip .4s every second,\nwhich might make your deltas seem weird."); settings.Add("tm-pigt", false, "Use pauseless IGT (for practice etc)", "tm"); settings.Add("hc", true, "Hacks"); settings.SetToolTip("hc", "These options write to the game memory.\nDon't use if you're not sure you can use these.\nI'm just a tool, not the rules."); settings.Add("hc-credits-skip", false, "Allow pause menu during credits", "hc"); settings.SetToolTip("hc-credits-skip", "This writes to the game memory, but technically\nit happens after the run is over, probably.\nUse to GTFO back to the main menu after a run."); vars.done = new HashSet<string>(); vars.state = new MemoryWatcherList(); vars.slot = new MemoryWatcherList(); vars.igtText = null; vars.popup = (Action)(() => { if (vars.initDone) { if (vars.igtText == null) { foreach (dynamic component in timer.Layout.Components) { if (component.GetType().Name == "TextComponent" && component.Settings.Text1 == "Real IGT:") { vars.igtText = component.Settings; break; } } if (vars.igtText == null) vars.igtText = vars.CreateTextComponent("Real IGT:"); } if (vars.state["popup"].Current.Contains(":") && vars.igtText != null) vars.igtText.Text2 = vars.state["popup"].Current; } }); vars.CreateTextComponent = (Func<string, dynamic>)((name) => { var textComponentAssembly = Assembly.LoadFrom("Components\\LiveSplit.Text.dll"); dynamic textComponent = Activator.CreateInstance(textComponentAssembly.GetType("LiveSplit.UI.Components.TextComponent"), timer); timer.Layout.LayoutComponents.Add(new LiveSplit.UI.Components.LayoutComponent("LiveSplit.Text.dll", textComponent as LiveSplit.UI.Components.IComponent)); textComponent.Settings.Text1 = name; textComponent.Settings.Text2 = "?"; return textComponent.Settings; }); vars.ptr = null; vars.initDone = false; vars.slotDone = false; vars.fireworks = 0; } init { vars.offset = IntPtr.Zero; vars.ptr = null; vars.initDone = false; vars.slotDone = false; vars.fireworks = 0; vars.pattern = new SigScanTarget(0, "48 8b 05 ?? ?? ?? ?? 48 8b ?? ?? ?? ?? ?? 48 89 8c 1f ec 05 00 00"); Action initMemory = delegate() { vars.state.Clear(); foreach (var page in game.MemoryPages(true)) { var scanner = new SignatureScanner(game, page.BaseAddress, (int) page.RegionSize); IntPtr findptr = scanner.Scan(vars.pattern); if (findptr != IntPtr.Zero) { vars.offset = findptr + game.ReadValue<int>(findptr + 3) + 7; var slot_ptr = new DeepPointer(vars.offset); vars.ptr = slot_ptr.Deref<IntPtr>(game); print("[ANIMAL] Pointer: "+vars.offset.ToString("X")+" = "+vars.ptr.ToString("X")); if (vars.ptr != IntPtr.Zero) { vars.state.Add(new MemoryWatcher<byte>(vars.ptr + 0x40c) { Name = "num" }); vars.state.Add(new MemoryWatcher<byte>(vars.ptr + 0x93644) { Name = "menu" }); vars.state.Add(new MemoryWatcher<byte>(vars.ptr + 0x93608) { Name = "pause" }); vars.state.Add(new MemoryWatcher<byte>(vars.ptr + 0x754a8 + 0x33608) { Name = "game" }); vars.state.Add(new MemoryWatcher<byte>(vars.ptr + 0x93670 + 0x5d) { Name = "bean_state" }); vars.state.Add(new MemoryWatcher<int>(vars.ptr + 0x93670 + 0x20) { Name = "bean_room_x" }); vars.state.Add(new MemoryWatcher<int>(vars.ptr + 0x93670 + 0x24) { Name = "bean_room_y" }); vars.state.Add(new StringWatcher(vars.ptr + 0x754d0, 24) { Name = "popup" }); vars.state.Add(new MemoryWatcher<byte>(modules.First().BaseAddress + 0x2bd5a10 + 0x1b) { Name = "escape" }); vars.state.Add(new MemoryWatcher<byte>(modules.First().BaseAddress + 0x2bd5b14) { Name = "xinput" }); vars.state.Add(new MemoryWatcher<byte>(modules.First().BaseAddress + 0x2bd5b58) { Name = "ds" }); vars.initDone = true; } break; } } if (vars.offset == IntPtr.Zero) { throw new Exception("Could not find magic number for AutoSplitter!"); } }; vars.init = initMemory; Action initSlot = delegate() { vars.slot.Clear(); if (!vars.initDone) vars.init(); if (vars.initDone) { var num = vars.state["num"].Current; print("[ANIMAL] Slot number: "+num.ToString()); var offset = num * 0x27010 + 0x418; vars.slot.Add(new MemoryWatcher<int>(vars.ptr + offset + 0x1c0) { Name = "igt" }); vars.slot.Add(new MemoryWatcher<int>(vars.ptr + offset + 0x1bc) { Name = "pigt" }); vars.slot.Add(new MemoryWatcher<short>(vars.ptr + offset + 0x1dc) { Name = "equipment" }); vars.slot.Add(new MemoryWatcher<byte>(vars.ptr + offset + 0x1de) { Name = "items" }); vars.slot.Add(new MemoryWatcher<int>(vars.ptr + offset + 0x21e) { Name = "flames" }); vars.slot.Add(new MemoryWatcher<int>(vars.ptr + offset + 0x198) { Name = "bunnies" }); vars.slotDone = true; } }; vars.initSlot = initSlot; vars.init(); } update { if(!vars.initDone) { vars.init(); return false; } vars.state.UpdateAll(game); if(settings["tm-force"] && timer.CurrentTimingMethod != TimingMethod.GameTime) timer.CurrentTimingMethod = TimingMethod.GameTime; if (settings["tm-popup"] && vars.igtText == null) vars.popup(); if ((vars.state["menu"].Changed && vars.state["menu"].Current == 2) || vars.state["num"].Changed) vars.slotDone = false; if(!vars.slotDone) vars.initSlot(); if(vars.state["menu"].Changed) print("[ANIMAL] Menu: "+vars.state["menu"].Old.ToString()+" -> "+vars.state["menu"].Current.ToString()+" (frame "+vars.slot["igt"].Current.ToString()+")"); if(vars.state["game"].Changed) print("[ANIMAL] Game: "+vars.state["game"].Old.ToString()+" -> "+vars.state["game"].Current.ToString()+" (frame "+vars.slot["igt"].Current.ToString()+")"); if(vars.state["xinput"].Changed) print("[ANIMAL] XInput: "+vars.state["xinput"].Old.ToString("X")+" -> "+vars.state["xinput"].Current.ToString("X")+" (frame "+vars.slot["igt"].Current.ToString()+")"); if (vars.state["popup"].Changed) { vars.popup(); print("[ANIMAL] Popup: " + vars.state["popup"].Old.ToString() + " -> " + vars.state["popup"].Current.ToString() + " (frame " + vars.slot["igt"].Current.ToString() + ")"); } if (vars.slotDone) { vars.slot.UpdateAll(game); if (vars.state["game"].Changed && vars.state["game"].Current == 16) vars.fireworks = vars.slot["igt"].Current + 39; if (settings["hc-credits-skip"] && vars.state["game"].Current >= 16 && vars.state["menu"].Current == 0 && vars.state["menu"].Old == 0 && vars.state["pause"].Current == 0 && ((vars.state["escape"].Current > 0 && vars.state["escape"].Old == 0) || (vars.state["xinput"].Current == 0x10 && vars.state["xinput"].Old == 0) || (vars.state["ds"].Current == 0x08 && vars.state["ds"].Old == 0))) { memory.WriteValue<byte>((IntPtr)(vars.ptr + 0x93608), 1); memory.WriteValue<byte>((IntPtr)(vars.ptr + 0x93644), 8); //memory.WriteValue<byte>((IntPtr)(vars.ptr + 0x754a8 + 0x33608), 0); // skip credits } } } start { if(!vars.slotDone) return false; if(settings["st-new"] && vars.state["menu"].Current == 0) { print("Start: New Game"); return true; } } split { if(!vars.slotDone) return false; if (settings["sp-end-normal"] && vars.fireworks > 0 && vars.slot["igt"].Current >= vars.fireworks) { print("Split: Fireworks ending"); vars.fireworks = 0; return true; } else if (settings["sp-end-true"] && vars.state["bean_state"].Changed && vars.state["bean_state"].Current == 16 && vars.state["bean_room_x"].Current == 7 && vars.state["bean_room_y"].Current == 17) { print("Split: True ending"); return true; } else if (settings["sp-end-bdtp"] && vars.state["bean_state"].Changed && vars.state["bean_state"].Current == 13 && vars.state["bean_room_x"].Current == 9 && vars.state["bean_room_y"].Current == 8) { print("Split: BDTP"); return true; } else if(settings["sp-equipment"] && vars.slot["equipment"].Changed) { for (int i = 1; i <= 12; i++) { bool state = (vars.slot["equipment"].Current & (1 << i)) != 0; string setting = string.Format("sp-equipment-{0}", i); if (state && settings.ContainsKey(setting) && settings[setting] && vars.done.Add(setting)) { print("Split: Equipment " + i + " 0x" + (1 << i).ToString("X")); return true; } } } else if(settings["sp-items"] && vars.slot["items"].Changed) { for (int i = 0; i <= 7; i++) { bool state = (vars.slot["items"].Current & (1 << i)) != 0; string setting = string.Format("sp-items-{0}", i); if (state && settings.ContainsKey(setting) && settings[setting] && vars.done.Add(setting)){ print("Split: Item " + (i+1) + " 0x" + (1 << i).ToString("X")); return true; } } } else if(settings["sp-flames"] && vars.slot["flames"].Changed) { for (int i = 0; i <= 3; i++) { bool state = (vars.slot["flames"].Current & (4 << i*8)) != 0; string setting = string.Format("sp-flames-{0}", i); if (state && settings.ContainsKey(setting) && settings[setting] && vars.done.Add(setting)){ print("Split: Flame " + (i+1)); return true; } } } else if(settings["sp-bunnies"] && vars.slot["bunnies"].Changed) { print("Split: Bunny"); return true; } else if(settings["sp-end-popup"] && vars.state["popup"].Changed && vars.state["popup"].Current.Contains(":")) { print("Split: Timer popup"); return true; } } reset { if (settings["rs-load"] && ((vars.state["menu"].Changed && vars.state["menu"].Current == 2) || vars.state["num"].Changed)) { print("Reset: Load Game"); return true; } if (vars.slotDone) { if (vars.slot["igt"].Changed && vars.slot["igt"].Current < vars.slot["igt"].Old) { print("Reset: Restarted same slot"); return true; } } } onStart { vars.done.Clear(); vars.fireworks = 0; vars.slotDone = false; if (vars.igtText != null) vars.igtText.Text2 = "?"; } onReset { vars.done.Clear(); vars.fireworks = 0; if (vars.igtText != null) vars.igtText.Text2 = "?"; } gameTime { var gt = vars.slot["igt"].Current; if(settings["tm-pigt"]) gt = vars.slot["pigt"].Current; if(settings["tm-format"]) { int frames = gt % 60; int seconds = gt / 60; return TimeSpan.FromMilliseconds(seconds * 1000 + frames * 10); } else { return TimeSpan.FromSeconds(gt / 60.0); } } isLoading { return true; }