From 8daeddae5174bec7e7984625ddbec55ba22be561 Mon Sep 17 00:00:00 2001 From: Alex Knauth Date: Tue, 17 Sep 2024 22:56:22 -0400 Subject: [PATCH 1/2] TargetFrameworkVersion: 4.8.1 --- LiveSplit.HollowKnight.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LiveSplit.HollowKnight.csproj b/LiveSplit.HollowKnight.csproj index c595a59..eda0ce5 100644 --- a/LiveSplit.HollowKnight.csproj +++ b/LiveSplit.HollowKnight.csproj @@ -9,7 +9,7 @@ Properties LiveSplit.HollowKnight LiveSplit.HollowKnight - v4.6.2 + v4.8.1 512 latest From 6c70a77194a10ef177cc242623c42370ff835307 Mon Sep 17 00:00:00 2001 From: Alex Knauth Date: Tue, 17 Sep 2024 23:08:39 -0400 Subject: [PATCH 2/2] Add a Hit counter --- HollowKnightComponent.cs | 105 +++++++++++++++++++++++++++++++ HollowKnightMemory.cs | 14 ++++- HollowKnightSettings.Designer.cs | 38 ++++++++--- HollowKnightSettings.cs | 79 +++++++++++++++++++++++ HollowKnightStoredData.cs | 30 +++++++++ 5 files changed, 258 insertions(+), 8 deletions(-) diff --git a/HollowKnightComponent.cs b/HollowKnightComponent.cs index 7219017..11db325 100644 --- a/HollowKnightComponent.cs +++ b/HollowKnightComponent.cs @@ -1,5 +1,6 @@ #if !Info using LiveSplit.Model; +using LiveSplit.TimeFormatters; using LiveSplit.UI; using LiveSplit.UI.Components; #endif @@ -52,6 +53,10 @@ public class HollowKnightComponent { private List menuingSceneNames = new List { "Menu_Title", "Quit_To_Menu", "PermaDeath" }; private List debugSaveStateSceneNames = new List { "Room_Mender_House", "Room_Sly_Storeroom" }; + private string lastExitingLevel; + private int hits = 0; + private List segmentHits = new List(); + enum SplitterAction { Pass, Split, @@ -85,6 +90,11 @@ public HollowKnightComponent(LiveSplitState state) { state.OnSplit += OnSplit; state.OnUndoSplit += OnUndoSplit; state.OnSkipSplit += OnSkipSplit; + state.Run.Metadata.SetCustomVariable("hits", "0"); + state.Run.Metadata.SetCustomVariable("segment hits", "0"); + state.Run.Metadata.SetCustomVariable("pb hits", TimeFormatConstants.DASH); + state.Run.Metadata.SetCustomVariable("comparison hits", TimeFormatConstants.DASH); + state.Run.Metadata.SetCustomVariable("delta hits", TimeFormatConstants.DASH); if (state.CurrentTimingMethod == TimingMethod.RealTime) { var timingMessage = MessageBox.Show( @@ -172,6 +182,7 @@ private void HandleSplits() { } } LoadRemoval(gameState, uIState, nextScene, sceneName); + HandleHits(gameState, uIState, nextScene, sceneName); } store.Update(); @@ -212,6 +223,95 @@ private void LoadRemoval(GameState gameState, UIState uIState, string nextScene, lastGameState = gameState; } + private void HitsReset() { + hits = 0; + segmentHits.Clear(); + Model.CurrentState.Run.Metadata.SetCustomVariable("hits", "0"); + Model.CurrentState.Run.Metadata.SetCustomVariable("segment hits", "0"); + } + + private void HitsIndexChanged() { + while (segmentHits.Count < currentSplit + 1) { + segmentHits.Add(0); + } + Model.CurrentState.Run.Metadata.SetCustomVariable("segment hits", segmentHits[currentSplit].ToString()); + if (currentSplit >= 0 && currentSplit < settings.ComparisonHits.Count) { + Model.CurrentState.Run.Metadata.SetCustomVariable("comparison hits", settings.ComparisonHits[currentSplit].ToString()); + Model.CurrentState.Run.Metadata.SetCustomVariable("delta hits", DeltaString(hits - settings.ComparisonHits[currentSplit])); + } else { + Model.CurrentState.Run.Metadata.SetCustomVariable("comparison hits", TimeFormatConstants.DASH); + Model.CurrentState.Run.Metadata.SetCustomVariable("delta hits", TimeFormatConstants.DASH); + } + while (settings.ComparisonHits.Count < currentSplit) { + settings.ComparisonHits.Add(hits); + } + if (currentSplit >= 1) { + settings.ComparisonHits[currentSplit - 1] = Math.Min(settings.ComparisonHits[currentSplit - 1], hits); + if (Model.CurrentState.CurrentPhase == TimerPhase.Ended) { + Model.CurrentState.Run.Metadata.SetCustomVariable("pb hits", settings.ComparisonHits[settings.ComparisonHits.Count - 1].ToString()); + } + } + } + + private void HandleHits(GameState gameState, UIState _uIState, string _nextScene, string sceneName) { + // TODO: set segment hits to 0 when moving to a new segment, also reset delta hits at the same time + // TODO: also reset these when resetting the run + if (settings.HitCounter is HollowKnightSettings.HitsMethod.None) { + return; + } + + if (store.RecoilFrozenToggledTrue()) { + AddHit(); + } + if (store.HazardDeathToggledTrue()) { + AddHit(); + } + if (store.CheckDecreasedTo(Offset.health, 0)) { + AddHit(); + } + + if (settings.HitCounter is HollowKnightSettings.HitsMethod.HitsDreamFalls) { + if (gameState == GameState.ENTERING_LEVEL && lastExitingLevel == sceneName && IsDream(sceneName)) { + AddHit(); + } + if (gameState == GameState.EXITING_LEVEL) { + lastExitingLevel ??= sceneName; + } else { + lastExitingLevel = null; + } + } + } + + private bool IsDream(string sceneName) { + return sceneName.StartsWith("Dream_") + || (sceneName.StartsWith("GG_") && !sceneName.Contains("way") && !sceneName.Contains("Lurker")); + } + + private void AddHit() { + hits += 1; + Model.CurrentState.Run.Metadata.SetCustomVariable("hits", hits.ToString()); + while (segmentHits.Count < currentSplit + 1) { + segmentHits.Add(0); + } + segmentHits[currentSplit] += 1; + Model.CurrentState.Run.Metadata.SetCustomVariable("segment hits", segmentHits[currentSplit].ToString()); + if (currentSplit < settings.ComparisonHits.Count) { + Model.CurrentState.Run.Metadata.SetCustomVariable("delta hits", DeltaString(hits - settings.ComparisonHits[currentSplit])); + } else { + Model.CurrentState.Run.Metadata.SetCustomVariable("delta hits", TimeFormatConstants.DASH); + } + } + + private string DeltaString(int delta) { + if (delta > 0) { + return "+" + delta.ToString(); + } else if (delta < 0) { + return TimeFormatConstants.MINUS + (-delta).ToString(); + } else { + return delta.ToString(); + } + } + private SplitterAction NotOrderedSplits(GameState gameState, UIState uIState, string nextScene, string sceneName) { foreach (SplitName Split in settings.Splits) { @@ -1929,6 +2029,7 @@ public void OnReset(object sender, TimerPhase e) { Model.CurrentState.IsGameTimePaused = true; splitsDone.Clear(); store.Reset(); + HitsReset(); if (failedValues.Count > 0) { WriteLog("---------Splits without match-------------------"); foreach (var value in failedValues) { @@ -1949,6 +2050,7 @@ public void OnStart(object sender, EventArgs e) { state = 0; Model.CurrentState.IsGameTimePaused = true; Model.CurrentState.SetGameTime(Model.CurrentState.CurrentTime.RealTime); + HitsIndexChanged(); splitsDone.Clear(); store.Reset(); failedValues.Clear(); @@ -1958,15 +2060,18 @@ public void OnStart(object sender, EventArgs e) { } public void OnUndoSplit(object sender, EventArgs e) { currentSplit--; + HitsIndexChanged(); //if (!settings.Ordered) splitsDone.Remove(lastSplitDone); Reminder of THIS BREAKS THINGS state = 0; } public void OnSkipSplit(object sender, EventArgs e) { currentSplit++; + HitsIndexChanged(); state = 0; } public void OnSplit(object sender, EventArgs e) { currentSplit++; + HitsIndexChanged(); store.SplitThisTransition = true; store.Update(); diff --git a/HollowKnightMemory.cs b/HollowKnightMemory.cs index fae50d6..9fc04fa 100644 --- a/HollowKnightMemory.cs +++ b/HollowKnightMemory.cs @@ -13,7 +13,7 @@ public partial class HollowKnightMemory { public bool IsHooked { get; set; } private DateTime lastHooked; private int uiManager, inputHandler, cameraCtrl, gameState, heroController, camTarget, camMode, camTMode, camDest, menuState, uiState, achievementHandler; - private int heroAccepting, actorState, transistionState, camTeleport, playerData, debugInfo, tilemapDirty, cState, sceneName, nextSceneName, hazardRespawning, onGround, spellquake; + private int heroAccepting, actorState, transistionState, camTeleport, playerData, debugInfo, tilemapDirty, cState, sceneName, nextSceneName, hazardDeath, hazardRespawning, onGround, recoilFrozen, spellquake; //private int sceneData, awardAchievementEvent; private Version lastVersion; @@ -59,8 +59,10 @@ private void UpdatedPointer(ProgramPointer pointer) { heroAccepting = 0x457; actorState = 0x374; transistionState = 0x37c; + hazardDeath = 0x25; // best guess, TODO actually check hazardRespawning = 0x26; onGround = 0x9; + recoilFrozen = 0x28; // best guess, TODO actually check spellquake = 0x37; int versionString = 0x1c; @@ -100,8 +102,10 @@ private void UpdatedPointer(ProgramPointer pointer) { heroAccepting = 0x6e7; //HeroControllerStates + hazardDeath = 0x2d; hazardRespawning = 0x2e; onGround = 0x11; + recoilFrozen = 0x30; spellquake = 0x3f; versionString = 0x38; @@ -430,6 +434,10 @@ public bool TileMapDirty() { //GameManager._instance.tileMapDirty return gameManager.Read(Program, 0x0, tilemapDirty); } + public bool HazardDeath() { + //GameManager._instance.hero_ctrl.cState.hazardDeath + return gameManager.Read(Program, 0x0, heroController, cState, hazardDeath); + } public bool HazardRespawning() { //GameManager._instance.hero_ctrl.cState.hazardRespawning return gameManager.Read(Program, 0x0, heroController, cState, hazardRespawning); @@ -438,6 +446,10 @@ public bool OnGround() { //GameManager._instance.hero_ctrl.cState.onGround return gameManager.Read(Program, 0x0, heroController, cState, onGround); } + public bool RecoilFrozen() { + //GameManager._instance.hero_ctrl.cState.recoilFrozen + return gameManager.Read(Program, 0x0, heroController, cState, recoilFrozen); + } public bool Spellquake() { //GameManager._instance.hero_ctrl.cState.spellquake return gameManager.Read(Program, 0x0, heroController, cState, spellquake); diff --git a/HollowKnightSettings.Designer.cs b/HollowKnightSettings.Designer.cs index feedce1..a78610e 100644 --- a/HollowKnightSettings.Designer.cs +++ b/HollowKnightSettings.Designer.cs @@ -34,6 +34,8 @@ private void InitializeComponent() { this.chkAutosplitStartRuns = new System.Windows.Forms.CheckBox(); this.chkOrdered = new System.Windows.Forms.CheckBox(); this.chkAutosplitEndRuns = new System.Windows.Forms.CheckBox(); + this.hitCounterLabel = new System.Windows.Forms.Label(); + this.cboHitCounter = new System.Windows.Forms.ComboBox(); this.SortBy_GroupBox = new System.Windows.Forms.GroupBox(); this.rdAlpha = new System.Windows.Forms.RadioButton(); this.rdType = new System.Windows.Forms.RadioButton(); @@ -47,7 +49,7 @@ private void InitializeComponent() { // // btnAddSplit // - this.btnAddSplit.Location = new System.Drawing.Point(6, 92); + this.btnAddSplit.Location = new System.Drawing.Point(6, 119); this.btnAddSplit.Name = "btnAddSplit"; this.btnAddSplit.Size = new System.Drawing.Size(57, 21); this.btnAddSplit.TabIndex = 0; @@ -67,7 +69,7 @@ private void InitializeComponent() { this.flowMain.Location = new System.Drawing.Point(0, 0); this.flowMain.Margin = new System.Windows.Forms.Padding(0); this.flowMain.Name = "flowMain"; - this.flowMain.Size = new System.Drawing.Size(456, 129); + this.flowMain.Size = new System.Drawing.Size(456, 156); this.flowMain.TabIndex = 0; this.flowMain.WrapContents = false; this.flowMain.DragDrop += new System.Windows.Forms.DragEventHandler(this.flowMain_DragDrop); @@ -82,7 +84,7 @@ private void InitializeComponent() { this.flowOptions.Location = new System.Drawing.Point(0, 0); this.flowOptions.Margin = new System.Windows.Forms.Padding(0); this.flowOptions.Name = "flowOptions"; - this.flowOptions.Size = new System.Drawing.Size(456, 129); + this.flowOptions.Size = new System.Drawing.Size(456, 156); this.flowOptions.TabIndex = 0; // // Options_GroupBox @@ -93,14 +95,14 @@ private void InitializeComponent() { this.Options_GroupBox.Controls.Add(this.SortBy_GroupBox); this.Options_GroupBox.Location = new System.Drawing.Point(3, 3); this.Options_GroupBox.Name = "Options_GroupBox"; - this.Options_GroupBox.Size = new System.Drawing.Size(450, 123); + this.Options_GroupBox.Size = new System.Drawing.Size(450, 150); this.Options_GroupBox.TabIndex = 6; this.Options_GroupBox.TabStop = false; this.Options_GroupBox.Text = "Options"; // // versionLabel // - this.versionLabel.Location = new System.Drawing.Point(262, 89); + this.versionLabel.Location = new System.Drawing.Point(262, 116); this.versionLabel.Name = "versionLabel"; this.versionLabel.Size = new System.Drawing.Size(182, 24); this.versionLabel.TabIndex = 7; @@ -113,9 +115,11 @@ private void InitializeComponent() { this.RunBehaviour_GroupBox.Controls.Add(this.chkAutosplitStartRuns); this.RunBehaviour_GroupBox.Controls.Add(this.chkOrdered); this.RunBehaviour_GroupBox.Controls.Add(this.chkAutosplitEndRuns); + this.RunBehaviour_GroupBox.Controls.Add(this.hitCounterLabel); + this.RunBehaviour_GroupBox.Controls.Add(this.cboHitCounter); this.RunBehaviour_GroupBox.Location = new System.Drawing.Point(143, 15); this.RunBehaviour_GroupBox.Name = "RunBehaviour_GroupBox"; - this.RunBehaviour_GroupBox.Size = new System.Drawing.Size(301, 71); + this.RunBehaviour_GroupBox.Size = new System.Drawing.Size(301, 98); this.RunBehaviour_GroupBox.TabIndex = 7; this.RunBehaviour_GroupBox.TabStop = false; this.RunBehaviour_GroupBox.Text = "Run behaviour"; @@ -165,6 +169,24 @@ private void InitializeComponent() { this.chkAutosplitEndRuns.UseVisualStyleBackColor = true; this.chkAutosplitEndRuns.CheckedChanged += new System.EventHandler(this.AutosplitEndChanged); // + // hitCounterLabel + // + this.hitCounterLabel.Location = new System.Drawing.Point(6, 69); + this.hitCounterLabel.Name = "hitCounterLabel"; + this.hitCounterLabel.Size = new System.Drawing.Size(139, 21); + this.hitCounterLabel.TabIndex = 7; + this.hitCounterLabel.Text = "Hit counter:"; + this.hitCounterLabel.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // cboHitCounter + // + this.cboHitCounter.FormattingEnabled = true; + this.cboHitCounter.Location = new System.Drawing.Point(148, 69); + this.cboHitCounter.Name = "cboHitCounter"; + this.cboHitCounter.Size = new System.Drawing.Size(145, 21); + this.cboHitCounter.TabIndex = 7; + this.cboHitCounter.SelectedIndexChanged += new System.EventHandler(this.cboHitCounter_SelectedIndexChanged); + // // SortBy_GroupBox // this.SortBy_GroupBox.Controls.Add(this.rdAlpha); @@ -214,7 +236,7 @@ private void InitializeComponent() { this.Controls.Add(this.flowMain); this.Margin = new System.Windows.Forms.Padding(0); this.Name = "HollowKnightSettings"; - this.Size = new System.Drawing.Size(456, 129); + this.Size = new System.Drawing.Size(456, 150); this.Load += new System.EventHandler(this.Settings_Load); this.flowMain.ResumeLayout(false); this.flowMain.PerformLayout(); @@ -244,5 +266,7 @@ private void InitializeComponent() { private System.Windows.Forms.GroupBox SortBy_GroupBox; private System.Windows.Forms.CheckBox chkAutosplitStartRuns; private System.Windows.Forms.ComboBox cboStartTriggerName; + private System.Windows.Forms.Label hitCounterLabel; + private System.Windows.Forms.ComboBox cboHitCounter; } } diff --git a/HollowKnightSettings.cs b/HollowKnightSettings.cs index f7bba88..29e3ec6 100644 --- a/HollowKnightSettings.cs +++ b/HollowKnightSettings.cs @@ -12,9 +12,12 @@ public partial class HollowKnightSettings : UserControl { public bool Ordered { get; set; } public bool AutosplitEndRuns { get; set; } public SplitName? AutosplitStartRuns { get; set; } + public HitsMethod HitCounter { get; set; } + public List ComparisonHits { get; set; } private bool isLoading; private List availableSplits = new List(); private List availableSplitsAlphaSorted = new List(); + private List hitCounters = new List(); public HollowKnightSettings() { isLoading = true; @@ -26,6 +29,7 @@ public HollowKnightSettings() { this.versionLabel.Text = "Autosplitter Version: " + version; Splits = new List(); + ComparisonHits = new List(); isLoading = false; } @@ -48,6 +52,11 @@ public void LoadSettings() { chkAutosplitEndRuns.Checked = AutosplitEndRuns; chkAutosplitStartRuns.Checked = AutosplitStartRuns != null; + cboHitCounter.DataSource = GetHitCounters(); + MemberInfo hitCounterInfo = typeof(HitsMethod).GetMember(HitCounter.ToString())[0]; + DescriptionAttribute hitCounterDescription = (DescriptionAttribute)hitCounterInfo.GetCustomAttributes(typeof(DescriptionAttribute), false)[0]; + cboHitCounter.Text = hitCounterDescription.Description; + foreach (SplitName split in Splits) { MemberInfo info = typeof(SplitName).GetMember(split.ToString())[0]; DescriptionAttribute description = (DescriptionAttribute)info.GetCustomAttributes(typeof(DescriptionAttribute), false)[0]; @@ -142,6 +151,8 @@ public void UpdateSplits() { AutosplitStartRuns = chkAutosplitStartRuns.Checked ? HollowKnightSplitSettings.GetSplitName(cboStartTriggerName.Text) : null; + HitCounter = GetHitsMethod(cboHitCounter.Text); + Splits.Clear(); foreach (Control c in flowMain.Controls) { if (c is HollowKnightSplitSettings) { @@ -168,6 +179,10 @@ public XmlNode UpdateSettings(XmlDocument document) { xmlAutosplitStartRuns.InnerText = AutosplitStartRuns.ToString(); xmlSettings.AppendChild(xmlAutosplitStartRuns); + XmlElement xmlHitCounter = document.CreateElement("HitCounter"); + xmlHitCounter.InnerText = HitCounter.ToString(); + xmlSettings.AppendChild(xmlHitCounter); + XmlElement xmlSplits = document.CreateElement("Splits"); xmlSettings.AppendChild(xmlSplits); @@ -178,12 +193,23 @@ public XmlNode UpdateSettings(XmlDocument document) { xmlSplits.AppendChild(xmlSplit); } + XmlElement xmlComparisonHits = document.CreateElement("ComparisonHits"); + xmlSettings.AppendChild(xmlComparisonHits); + + foreach (int item in ComparisonHits) { + XmlElement xmlItem = document.CreateElement("Item"); + xmlItem.InnerText = item.ToString(); + + xmlComparisonHits.AppendChild(xmlItem); + } + return xmlSettings; } public void SetSettings(XmlNode settings) { XmlNode orderedNode = settings.SelectSingleNode(".//Ordered"); XmlNode AutosplitEndRunsNode = settings.SelectSingleNode(".//AutosplitEndRuns"); XmlNode AutosplitStartRunsNode = settings.SelectSingleNode(".//AutosplitStartRuns"); + XmlNode HitCounterNode = settings.SelectSingleNode(".//HitCounter"); bool isOrdered = false; bool isAutosplitEndRuns = false; @@ -207,6 +233,14 @@ public void SetSettings(XmlNode settings) { Ordered = isOrdered; AutosplitEndRuns = isAutosplitEndRuns; + if (HitCounterNode != null) { + string hitCounterDescription = HitCounterNode.InnerText.Trim(); + HitCounter = GetHitsMethod(hitCounterDescription); + MemberInfo info = typeof(HitsMethod).GetMember(HitCounter.ToString())[0]; + DescriptionAttribute description = (DescriptionAttribute)info.GetCustomAttributes(typeof(DescriptionAttribute), false)[0]; + cboHitCounter.Text = description.Description; + } + Splits.Clear(); XmlNodeList splitNodes = settings.SelectNodes(".//Splits/Split"); foreach (XmlNode splitNode in splitNodes) { @@ -214,6 +248,15 @@ public void SetSettings(XmlNode settings) { SplitName split = HollowKnightSplitSettings.GetSplitName(splitDescription); Splits.Add(split); } + + ComparisonHits.Clear(); + XmlNodeList comparisonHitsNodes = settings.SelectNodes(".//ComparisonHits/Item"); + foreach (XmlNode itemNode in comparisonHitsNodes) { + string item = itemNode.InnerText.Trim(); + if (int.TryParse(item, out int i)) { + ComparisonHits.Add(i); + } + } } private HollowKnightSplitSettings createSetting() { HollowKnightSplitSettings setting = new HollowKnightSplitSettings(); @@ -249,6 +292,16 @@ private List GetAvailableSplits() { } return rdAlpha.Checked ? availableSplitsAlphaSorted : availableSplits; } + private List GetHitCounters() { + if (hitCounters.Count == 0) { + foreach (HitsMethod hm in Enum.GetValues(typeof(HitsMethod))) { + MemberInfo info = typeof(HitsMethod).GetMember(hm.ToString())[0]; + DescriptionAttribute description = (DescriptionAttribute)info.GetCustomAttributes(typeof(DescriptionAttribute), false)[0]; + hitCounters.Add(description.Description); + } + } + return hitCounters; + } private void radio_CheckedChanged(object sender, EventArgs e) { foreach (Control c in flowMain.Controls) { if (c is HollowKnightSplitSettings) { @@ -311,5 +364,31 @@ private void AutosplitStartChanged(object sender, EventArgs e) { private void cboStartTriggerName_SelectedIndexChanged(object sender, EventArgs e) { UpdateSplits(); } + + private void cboHitCounter_SelectedIndexChanged(object sender, EventArgs e) { + UpdateSplits(); + } + + public enum HitsMethod { + [Description("None")] + None, + [Description("Hits / dream falls")] + HitsDreamFalls, + [Description("Hits / damage")] + HitsDamage, + } + + public static HitsMethod GetHitsMethod(string text) { + foreach (HitsMethod hm in Enum.GetValues(typeof(HitsMethod))) { + string name = hm.ToString(); + MemberInfo info = typeof(HitsMethod).GetMember(name)[0]; + DescriptionAttribute description = (DescriptionAttribute)info.GetCustomAttributes(typeof(DescriptionAttribute), false)[0]; + + if (name.Equals(text, StringComparison.OrdinalIgnoreCase) || description.Description.Equals(text, StringComparison.OrdinalIgnoreCase)) { + return hm; + } + } + return HitsMethod.None; + } } } diff --git a/HollowKnightStoredData.cs b/HollowKnightStoredData.cs index 1fa1b40..e7af7a5 100644 --- a/HollowKnightStoredData.cs +++ b/HollowKnightStoredData.cs @@ -21,6 +21,8 @@ public void Update(T val) { private ConcurrentDictionary> pdInts = new ConcurrentDictionary>(); private ConcurrentDictionary> pdBools = new ConcurrentDictionary>(); + private Tracked hazardDeath = new Tracked(false); + private Tracked recoilFrozen = new Tracked(false); public bool TraitorLordDeadOnEntry { get; private set; } = false; public bool DungDefenderAwakeConvoOnEntry { get; private set; } = false; /// @@ -153,6 +155,17 @@ public bool CheckIncreased(Offset offset) { Tracked tracked = GetValue(offset); return tracked.current > tracked.previous; } + /// + /// Checks if the PD int given by offset has decreased to value since the last update + /// + /// + /// + /// + public bool CheckDecreasedTo(Offset offset, int value) { + Tracked tracked = GetValue(offset); + return tracked.current == value && tracked.previous > value; + } + /// /// Checks if the PD bool given by offset has toggled since the last update @@ -194,6 +207,21 @@ public bool CheckBeenTrue(Offset offset) { return tracked.previous && tracked.current; } + /// + /// Checks if hazardDeath has toggled from False to True since the last update + /// + /// + public bool HazardDeathToggledTrue() { + return hazardDeath.current && !hazardDeath.previous; + } + /// + /// Checks if recoilFrazen has toggled from False to True since the last update + /// + /// + public bool RecoilFrozenToggledTrue() { + return recoilFrozen.current && !recoilFrozen.previous; + } + public HollowKnightStoredData(HollowKnightMemory mem) { this.mem = mem; } @@ -208,6 +236,8 @@ public void Update() { foreach (Offset offset in pdBools.Keys) { pdBools[offset].Update(mem.PlayerData(offset)); } + hazardDeath.Update(mem.HazardDeath()); + recoilFrozen.Update(mem.RecoilFrozen()); if (mem.HeroTransitionState() != HeroTransitionState.WAITING_TO_TRANSITION || mem.GameState() is GameState.EXITING_LEVEL or GameState.LOADING || mem.SceneName() != mem.NextSceneName()) {