diff --git a/.gitignore b/.gitignore index f17f831..e69de29 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +0,0 @@ -/Phasmophobia \ No newline at end of file diff --git a/Cuphead/Cuphead.Splits.xml b/Cuphead/Cuphead.Splits.xml new file mode 100644 index 0000000..cc9da71 --- /dev/null +++ b/Cuphead/Cuphead.Splits.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Cuphead/Cuphead.asl b/Cuphead/Cuphead.asl new file mode 100644 index 0000000..99826ac --- /dev/null +++ b/Cuphead/Cuphead.asl @@ -0,0 +1,283 @@ +state("Cuphead") {} + +startup +{ + vars.Log = (Action)(output => print("[Cuphead] " + output)); + + var bytes = File.ReadAllBytes(@"Components\LiveSplit.ASLHelper.bin"); + var type = Assembly.Load(bytes).GetType("ASLHelper.Unity"); + vars.Helper = Activator.CreateInstance(type, timer, this); + + vars.Splits = new Dictionary(); + + settings.Add("splits", false, "Splits:"); + + var xml = System.Xml.Linq.XDocument.Load(@"Components\Cuphead.Splits.xml"); + foreach (var split in xml.Element("Splits").Elements("Split")) + { + string id = split.Attribute("ID").Value, name = split.Attribute("Name").Value; + string tt = split.Attribute("ToolTip").Value, splitType = split.Attribute("Type").Value; + + settings.Add(id, false, name, "splits"); + settings.SetToolTip(id, tt); + + vars.Splits[id] = splitType; + } + + vars.CompletedSplits = new List(); +} + +onStart +{ + timer.IsGameTimePaused = true; + vars.CompletedSplits.Clear(); +} + +onSplit +{ + // Set final split's time to accurate in-game time when player is doing IL attempts. + // This (like `reset`) assumes the runner only has 1 split. + // DevilSquirrel's code. /shrug + if (timer.CurrentPhase != TimerPhase.Ended || timer.Run.Count != 1) + return; + + var time = timer.Run[0].SplitTime; + timer.Run[0].SplitTime = new Time(time.RealTime, TimeSpan.FromSeconds(current.Time)); +} + +onReset +{} + +init +{ + int PTR_SIZE = game.Is64Bit() ? 0x8 : 0x4; + + current.SaveSlot = IntPtr.Zero; + + vars.Helper.TryOnLoad = (Func)(mono => + { + #region PlayerData + var pd = mono.GetClass("PlayerData"); + + vars.Helper["inGame"] = pd.Make("inGame"); + vars.Helper["saveSlotIndex"] = pd.Make("_CurrentSaveFileIndex"); + vars.Helper["saveFiles"] = pd.MakeArray("_saveFiles"); + + vars.GetCurrentSave = (Func)(() => + { + var slot = vars.Helper["saveSlotIndex"].Current; + return vars.Helper["saveFiles"].Current[slot]; + }); + + // Level Completion + var pldm = mono.GetClass("PlayerLevelDataManager"); + var pldo = mono.GetClass("PlayerLevelDataObject"); + + vars.GetAllLevelsData = (Func>)(() => + { + var levels = vars.Helper.ReadList(current.SaveSlot + pd["levelDataManager"], pldm["levelObjects"]); + var ret = new List(); + + foreach (var level in levels) + { + var id = vars.Helper.Read(level + pldo["levelID"]); + var completed = vars.Helper.Read(level + pldo["completed"]); + var grade = vars.Helper.Read(level + pldo["grade"]); + var difficulty = vars.Helper.Read(level + pldo["difficultyBeaten"]); + + ret.Add(new + { + ID = id, + Completed = completed, + Grade = grade, + Difficulty = difficulty + }); + } + + return ret; + }); + + vars.IsLevelCompleted = (Func)((levelId, targetDifficulty, targetGrade) => + { + foreach (var level in vars.GetAllLevelsData()) + { + if (level.ID == levelId) + return level.Completed && level.Grade >= targetGrade && level.Difficulty >= targetDifficulty; + } + + return false; + }); + #endregion // PlayerData + + #region Level + var lvl = mono.GetClass("Level"); + + vars.Helper["lvl"] = lvl.Make("PreviousLevel"); + vars.Helper["lvlTime"] = lvl.Make("Current", "LevelTime"); + vars.Helper["lvlDifficulty"] = lvl.Make("Current", "mode"); + vars.Helper["lvlEnding"] = lvl.Make("Current", "Ending"); + vars.Helper["lvlWon"] = lvl.Make("Won"); + #endregion // Level + + #region SceneLoader + var sl = mono.GetClass("SceneLoader"); + + vars.Helper["sceneName"] = sl.MakeString("SceneName"); + vars.Helper["doneLoading"] = sl.Make("_instance", "doneLoadingSceneAsync"); + #endregion // SceneLoader + + var x = mono.GetClass("PlayerStatsManager"); + + return true; + }); + + vars.Helper.Load(); +} + +update +{ + if (!vars.Helper.Update()) + return false; + + current.SaveSlot = vars.GetCurrentSave(); + + current.InGame = vars.Helper["inGame"].Current; + + current.Level = vars.Helper["lvl"].Current; + current.Time = vars.Helper["lvlTime"].Current; + current.Difficulty = vars.Helper["lvlDifficulty"].Current; + current.IsEnding = vars.Helper["lvlEnding"].Current; + current.HasWon = vars.Helper["lvlWon"].Current; + + current.Loading = !vars.Helper["doneLoading"].Current; + current.Scene = vars.Helper["sceneName"].Current; + + // vars.Log("InGame: " + current.InGame); + // vars.Log("Level: " + current.Level); + // vars.Log("Time: " + current.Time); + // vars.Log("Difficulty: " + current.Difficulty); + // vars.Log("IsEnding: " + current.IsEnding); + // vars.Log("HasWon: " + current.HasWon); + // vars.Log("Loading: " + current.Loading); + // vars.Log("Scene: " + current.Scene); + // vars.Log("---------------------------------"); +} + +start +{ + return current.Scene == "scene_cutscene_intro" && current.InGame && current.Loading; +} + +split +{ + foreach (var split in vars.Splits) + { + string id = split.Key, type = split.Value; + + if (!settings[id] || vars.CompletedSplits.Contains(id)) + continue; + + switch (type) + { + case "SCENE_ENTER": + { + if (old.Scene != id && current.Scene == id) + { + vars.Log("SCENE_ENTER | " + id); + + vars.CompletedSplits.Add(id); + return true; + } + + continue; + } + + case "SCENE_LEAVE": + { + if (old.Scene == id && current.Scene != id) + { + vars.Log("SCENE_LEAVE | " + id); + + vars.CompletedSplits.Add(id); + return true; + } + + continue; + } + + case "LEVEL_COMPLETE": + { + if (current.Scene == id && vars.IsLevelCompleted(current.Level, -1, -1)) + { + vars.Log("LEVEL_COMPLETE | " + id); + + vars.CompletedSplits.Add(id); + return true; + } + + continue; + } + + case "ENDING": + { + var scene = id.Substring(0, id.Length - 2); + var diff = int.Parse(id[id.Length - 1].ToString()); + if (!old.IsEnding && current.IsEnding && current.Scene == scene && current.Difficulty == diff) + { + vars.Log("ENDING | " + id); + + vars.CompletedSplits.Add(id); + return true; + } + + continue; + } + } + + // case "CUSTOM" + switch (id) + { + case "ilEnter": + { + if (old.Time == 0f && current.Time > 0f) + return true; + + continue; + } + + case "ilEnd": + { + if (current.Time > 0f && current.IsEnding) + return true; + + continue; + } + } + } +} + +reset +{ + // Reset only when the runner is doing IL attempts. + // Kind of a big assumption, don't you think? Runners can do full game runs with only 1 split, too. + // DevilSquirrel's code. /shrug + return timer.Run.Count == 1 && current.Loading && current.Time == 0f; +} + +gameTime +{} + +isLoading +{ + return current.Loading; +} + +exit +{ + vars.Helper.Dispose(); +} + +shutdown +{ + vars.Helper.Dispose(); +} diff --git a/Cuphead/readme.md b/Cuphead/readme.md new file mode 100644 index 0000000..74b1d42 --- /dev/null +++ b/Cuphead/readme.md @@ -0,0 +1,15 @@ +# Auto Splitter for ***Cuphead*** +## Disclaimer +This is a temporary attempt at fixing [DevilSquirrel's component](https://github.com/ShootMe/LiveSplit.Cuphead) for the new DLC. + +## Features +Starts the timer when choosing any save file. +Splits are available in the settings. +Resets the timer when the runner only has one split. +Removes loading times. + +## Resources +*Leaderboards: [speedrun.com/cuphead](https://speedrun.com/cuphead)* +*Discord: [discord.gg/2BcMwEd](https://discord.gg/2BcMwEd)* +*Website: [cupheadgame.com](https://cupheadgame.com)* +*Game: [s.team/a/268910](https://s.team/a/268910) ($19.99)* \ No newline at end of file diff --git a/Donut County/readme.md b/Donut County/readme.md index ece2e0c..e250da3 100644 --- a/Donut County/readme.md +++ b/Donut County/readme.md @@ -2,10 +2,10 @@ ## Features Starts the timer when starting any level. Splits are available in the settings. -Does not reset the timer. +Does not reset the timer. +Removes loading times. ## Resources *Leaderboards: [speedrun.com/donutcounty](https://speedrun.com/donutcounty)* *Discord: [discord.gg/V3chU5S](https://discord.gg/V3chU5S)* -*Website: [donutcounty.com](https://donutcounty.com)* *Game: [s.team/a/702670](https://s.team/a/702670) ($12.99)* \ No newline at end of file