From 0f0c661cceffe88b71da55ece49de2605ead56d9 Mon Sep 17 00:00:00 2001 From: LordMidas <55047920+LordMidas@users.noreply.github.com> Date: Sat, 4 May 2024 00:30:40 -0400 Subject: [PATCH 01/13] perf: add guard clauses to return early in setActiveEntityCostsPreview --- .../turn_sequence_bar/turn_sequence_bar.nut | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/msu/hooks/ui/screens/tactical/modules/turn_sequence_bar/turn_sequence_bar.nut b/msu/hooks/ui/screens/tactical/modules/turn_sequence_bar/turn_sequence_bar.nut index aa38e2558..bacb27069 100644 --- a/msu/hooks/ui/screens/tactical/modules/turn_sequence_bar/turn_sequence_bar.nut +++ b/msu/hooks/ui/screens/tactical/modules/turn_sequence_bar/turn_sequence_bar.nut @@ -32,25 +32,25 @@ q.setActiveEntityCostsPreview = @(__original) function( _costsPreview ) { - if (::MSU.Mod.ModSettings.getSetting("ExpandedSkillTooltips").getValue()) - { - local activeEntity = this.getActiveEntity(); - if (activeEntity != null) - { - local skillID = "SkillID" in _costsPreview ? _costsPreview.SkillID : ""; - local skill; - local movementTile; - if (skillID == "") - { - local movement = ::Tactical.getNavigator().getCostForPath(activeEntity, ::Tactical.getNavigator().getLastSettings(), activeEntity.getActionPoints(), activeEntity.getFatigueMax() - activeEntity.getFatigue()); - movementTile = movement.End; - } - else skill = activeEntity.getSkills().getSkillByID(skillID); + if (!::MSU.Mod.ModSettings.getSetting("ExpandedSkillTooltips").getValue()) + return __original(_costsPreview); - activeEntity.getSkills().m.IsPreviewing = true; - activeEntity.getSkills().onAffordablePreview(skill, movementTile); - } + local activeEntity = this.getActiveEntity(); + if (activeEntity == null) + return __original(_costsPreview); + + local skillID = "SkillID" in _costsPreview ? _costsPreview.SkillID : ""; + local skill; + local movementTile; + if (skillID == "") + { + local movement = ::Tactical.getNavigator().getCostForPath(activeEntity, ::Tactical.getNavigator().getLastSettings(), activeEntity.getActionPoints(), activeEntity.getFatigueMax() - activeEntity.getFatigue()); + movementTile = movement.End; } + else skill = activeEntity.getSkills().getSkillByID(skillID); + + activeEntity.getSkills().m.IsPreviewing = true; + activeEntity.getSkills().onAffordablePreview(skill, movementTile); this.m.MSU_JSHandle.__JSHandle = this.m.JSHandle; this.m.JSHandle = this.m.MSU_JSHandle; From c081e9ee1e5a5a2b6fef50e194e352ac19014de8 Mon Sep 17 00:00:00 2001 From: LordMidas <55047920+LordMidas@users.noreply.github.com> Date: Wed, 17 Apr 2024 23:29:36 -0400 Subject: [PATCH 02/13] perf: remove unused target variable --- msu/hooks/skills/skill_container.nut | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/msu/hooks/skills/skill_container.nut b/msu/hooks/skills/skill_container.nut index 138dba667..e87187fe4 100644 --- a/msu/hooks/skills/skill_container.nut +++ b/msu/hooks/skills/skill_container.nut @@ -292,18 +292,7 @@ { foreach (change in changes) { - local target; - local previewTable; - if (change.TargetSkill != null) - { - target = change.TargetSkill.m; - previewTable = change.TargetSkill.m.PreviewField; - } - else - { - target = this.getActor().getCurrentProperties(); - previewTable = this.m.PreviewProperty; - } + local previewTable = change.TargetSkill == null ? this.m.PreviewProperty : change.TargetSkill.m.PreviewField; if (!(change.Field in previewTable)) previewTable[change.Field] <- { Change = change.Multiplicative ? 1 : 0, Multiplicative = change.Multiplicative }; From 25fbbf9217a7aed56dc4174ea97e4b3207df64be Mon Sep 17 00:00:00 2001 From: LordMidas <55047920+LordMidas@users.noreply.github.com> Date: Wed, 17 Apr 2024 23:31:37 -0400 Subject: [PATCH 03/13] style: add new line after if condense if else block --- msu/hooks/skills/skill_container.nut | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/msu/hooks/skills/skill_container.nut b/msu/hooks/skills/skill_container.nut index e87187fe4..efa63586f 100644 --- a/msu/hooks/skills/skill_container.nut +++ b/msu/hooks/skills/skill_container.nut @@ -294,20 +294,15 @@ { local previewTable = change.TargetSkill == null ? this.m.PreviewProperty : change.TargetSkill.m.PreviewField; - if (!(change.Field in previewTable)) previewTable[change.Field] <- { Change = change.Multiplicative ? 1 : 0, Multiplicative = change.Multiplicative }; + if (!(change.Field in previewTable)) + previewTable[change.Field] <- { Change = change.Multiplicative ? 1 : 0, Multiplicative = change.Multiplicative }; if (change.Multiplicative) - { previewTable[change.Field].Change *= change.NewChange / (change.CurrChange == 0 ? 1 : change.CurrChange); - } else if (typeof change.NewChange == "bool") - { previewTable[change.Field].Change = change.NewChange; - } else - { previewTable[change.Field].Change += change.NewChange - change.CurrChange; - } } } From 81f327b3e9bc45d4d45d376dc1b3a7a248ba6446 Mon Sep 17 00:00:00 2001 From: LordMidas <55047920+LordMidas@users.noreply.github.com> Date: Wed, 17 Apr 2024 23:53:44 -0400 Subject: [PATCH 04/13] perf: iterate over skills only once to clear PreviewField and call event --- msu/hooks/skills/skill_container.nut | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/msu/hooks/skills/skill_container.nut b/msu/hooks/skills/skill_container.nut index efa63586f..593fff534 100644 --- a/msu/hooks/skills/skill_container.nut +++ b/msu/hooks/skills/skill_container.nut @@ -223,16 +223,14 @@ q.onAffordablePreview <- function( _skill, _movementTile ) { this.m.PreviewProperty.clear(); + foreach (skill in this.m.Skills) { skill.m.PreviewField.clear(); + if (!skill.isGarbage()) + skill.onAffordablePreview(_skill, _movementTile); } - this.callSkillsFunction("onAffordablePreview", [ - _skill, - _movementTile, - ], false); - if (::MSU.Skills.QueuedPreviewChanges.len() == 0) return; local propertiesClone = this.getActor().getBaseProperties().getClone(); From ea8d356a42d2e1fbf4d5bccb4838647bbcb1a3b6 Mon Sep 17 00:00:00 2001 From: LordMidas <55047920+LordMidas@users.noreply.github.com> Date: Sat, 4 May 2024 00:25:02 -0400 Subject: [PATCH 05/13] refactor: move the boolean for previewing from skill container to actor --- msu/hooks/entity/tactical/actor.nut | 2 ++ msu/hooks/skills/skill.nut | 4 ++-- msu/hooks/skills/skill_container.nut | 7 +++---- msu/hooks/states/tactical_state.nut | 4 ++-- .../modules/turn_sequence_bar/turn_sequence_bar.nut | 4 ++-- 5 files changed, 11 insertions(+), 10 deletions(-) diff --git a/msu/hooks/entity/tactical/actor.nut b/msu/hooks/entity/tactical/actor.nut index a8412adb9..411051bb1 100644 --- a/msu/hooks/entity/tactical/actor.nut +++ b/msu/hooks/entity/tactical/actor.nut @@ -1,4 +1,6 @@ ::MSU.MH.hook("scripts/entity/tactical/actor", function(q) { + q.m.MSU_IsPreviewing <- false; + q.onMovementStart = @(__original) function ( _tile, _numTiles ) { __original(_tile, _numTiles); diff --git a/msu/hooks/skills/skill.nut b/msu/hooks/skills/skill.nut index 9f0cbe013..c3aaf25d8 100644 --- a/msu/hooks/skills/skill.nut +++ b/msu/hooks/skills/skill.nut @@ -549,7 +549,7 @@ q.isAffordablePreview = @(__original) function() { - if (!this.getContainer().m.IsPreviewing) return __original(); + if (!this.getContainer().getActor().m.MSU_IsPreviewing) return __original(); this.m.IsApplyingPreview = true; local ret = __original(); this.m.IsApplyingPreview = false; @@ -558,7 +558,7 @@ q.getCostString = @(__original) function() { - if (!this.getContainer().m.IsPreviewing) return __original(); + if (!this.getContainer().getActor().m.MSU_IsPreviewing) return __original(); local preview = ::Tactical.TurnSequenceBar.m.ActiveEntityCostsPreview; if (preview != null && preview.id == this.getContainer().getActor().getID()) { diff --git a/msu/hooks/skills/skill_container.nut b/msu/hooks/skills/skill_container.nut index 593fff534..9dad37dcb 100644 --- a/msu/hooks/skills/skill_container.nut +++ b/msu/hooks/skills/skill_container.nut @@ -1,6 +1,5 @@ ::MSU.MH.hook("scripts/skills/skill_container", function(q) { q.m.ScheduledChangesSkills <- []; - q.m.IsPreviewing <- false; q.m.PreviewProperty <- {}; q.update = @(__original) function() @@ -365,13 +364,13 @@ q.onTurnEnd = @() function() { - this.m.IsPreviewing = false; + this.getActor().m.MSU_IsPreviewing = false; this.callSkillsFunctionWhenAlive("onTurnEnd"); } q.onWaitTurn = @() function() { - this.m.IsPreviewing = false; + this.getActor().m.MSU_IsPreviewing = false; this.callSkillsFunctionWhenAlive("onWaitTurn"); } @@ -468,7 +467,7 @@ q.onCombatFinished = @() function() { - this.m.IsPreviewing = false; + this.getActor().m.MSU_IsPreviewing = false; this.callSkillsFunction("onCombatFinished"); } diff --git a/msu/hooks/states/tactical_state.nut b/msu/hooks/states/tactical_state.nut index 299ab8c2b..092bb4429 100644 --- a/msu/hooks/states/tactical_state.nut +++ b/msu/hooks/states/tactical_state.nut @@ -1,13 +1,13 @@ ::MSU.MH.hook("scripts/states/tactical_state", function(q) { q.executeEntityTravel = @(__original) function( _activeEntity, _mouseEvent ) { - _activeEntity.getSkills().m.IsPreviewing = false; + _activeEntity.m.MSU_IsPreviewing = false; return __original(_activeEntity, _mouseEvent); } q.executeEntitySkill = @(__original) function( _activeEntity, _targetTile ) { - _activeEntity.getSkills().m.IsPreviewing = false; + _activeEntity.m.MSU_IsPreviewing = false; return __original(_activeEntity, _targetTile); } diff --git a/msu/hooks/ui/screens/tactical/modules/turn_sequence_bar/turn_sequence_bar.nut b/msu/hooks/ui/screens/tactical/modules/turn_sequence_bar/turn_sequence_bar.nut index bacb27069..84b1b2acd 100644 --- a/msu/hooks/ui/screens/tactical/modules/turn_sequence_bar/turn_sequence_bar.nut +++ b/msu/hooks/ui/screens/tactical/modules/turn_sequence_bar/turn_sequence_bar.nut @@ -49,7 +49,7 @@ } else skill = activeEntity.getSkills().getSkillByID(skillID); - activeEntity.getSkills().m.IsPreviewing = true; + activeEntity.m.MSU_IsPreviewing = true; activeEntity.getSkills().onAffordablePreview(skill, movementTile); this.m.MSU_JSHandle.__JSHandle = this.m.JSHandle; @@ -62,7 +62,7 @@ q.resetActiveEntityCostsPreview = @(__original) function() { local activeEntity = this.getActiveEntity(); - if (activeEntity != null) activeEntity.getSkills().m.IsPreviewing = false; + if (activeEntity != null) activeEntity.m.MSU_IsPreviewing = false; __original(); } }); From 8646d69bd88a6be8e30bb8d18cdf9c618addfa48 Mon Sep 17 00:00:00 2001 From: LordMidas <55047920+LordMidas@users.noreply.github.com> Date: Sat, 4 May 2024 00:27:47 -0400 Subject: [PATCH 06/13] refactor: move boolean for applying preview to skill container Instead of having it in every skill. This should be more memory efficient. --- msu/hooks/skills/skill.nut | 11 +++++------ msu/hooks/skills/skill_container.nut | 1 + 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/msu/hooks/skills/skill.nut b/msu/hooks/skills/skill.nut index c3aaf25d8..aade63e10 100644 --- a/msu/hooks/skills/skill.nut +++ b/msu/hooks/skills/skill.nut @@ -62,7 +62,6 @@ q.m.IsBaseValuesSaved <- false; q.m.ScheduledChanges <- []; - q.m.IsApplyingPreview <- false; q.m.PreviewField <- {}; q.isType = @() function( _t, _any = true, _only = false ) @@ -490,7 +489,7 @@ { q[func] = @(__original) function() { - if (!this.m.IsApplyingPreview) return __original(); + if (!this.getContainer().m.MSU_IsApplyingPreview) return __original(); local temp = {}; foreach (field, change in this.m.PreviewField) @@ -550,9 +549,9 @@ q.isAffordablePreview = @(__original) function() { if (!this.getContainer().getActor().m.MSU_IsPreviewing) return __original(); - this.m.IsApplyingPreview = true; + this.getContainer().m.MSU_IsApplyingPreview = true; local ret = __original(); - this.m.IsApplyingPreview = false; + this.getContainer().m.MSU_IsApplyingPreview = false; return ret; } @@ -562,9 +561,9 @@ local preview = ::Tactical.TurnSequenceBar.m.ActiveEntityCostsPreview; if (preview != null && preview.id == this.getContainer().getActor().getID()) { - this.m.IsApplyingPreview = true; + this.getContainer().m.MSU_IsApplyingPreview = true; local ret = __original(); - this.m.IsApplyingPreview = false; + this.getContainer().m.MSU_IsApplyingPreview = false; local skillID = this.getContainer().getActor().getPreviewSkillID(); local str = " after " + (skillID == "" ? "moving" : "using " + this.getContainer().getSkillByID(skillID).getName()); ret = ::MSU.String.replace(ret, "Fatigue[/color]", "Fatigue[/color]" + str); diff --git a/msu/hooks/skills/skill_container.nut b/msu/hooks/skills/skill_container.nut index 9dad37dcb..7d2dd9189 100644 --- a/msu/hooks/skills/skill_container.nut +++ b/msu/hooks/skills/skill_container.nut @@ -1,6 +1,7 @@ ::MSU.MH.hook("scripts/skills/skill_container", function(q) { q.m.ScheduledChangesSkills <- []; q.m.PreviewProperty <- {}; + q.m.MSU_IsApplyingPreview <- false; q.update = @(__original) function() { From d542330b9a904ce0c948736e6d3666634cc06d7d Mon Sep 17 00:00:00 2001 From: LordMidas <55047920+LordMidas@users.noreply.github.com> Date: Sat, 4 May 2024 02:07:26 -0400 Subject: [PATCH 07/13] feat: add an intuitive and flexible preview modification system - This allows directly modifying fields/properties inside the onUpdate and onAfterUpdate functions for the preview case. - Is extensible automatically to all functions e.g. is usable within onUpdate, onAfterUpdate, isUsable etc. --- msu/hooks/entity/tactical/actor.nut | 36 +++++++++++++++++++ msu/hooks/skills/skill.nut | 4 +++ msu/hooks/skills/skill_container.nut | 6 ++-- msu/hooks/states/tactical_state.nut | 4 +-- .../turn_sequence_bar/turn_sequence_bar.nut | 21 ++++++----- 5 files changed, 58 insertions(+), 13 deletions(-) diff --git a/msu/hooks/entity/tactical/actor.nut b/msu/hooks/entity/tactical/actor.nut index 411051bb1..2298b98f8 100644 --- a/msu/hooks/entity/tactical/actor.nut +++ b/msu/hooks/entity/tactical/actor.nut @@ -1,5 +1,19 @@ ::MSU.MH.hook("scripts/entity/tactical/actor", function(q) { q.m.MSU_IsPreviewing <- false; + q.m.MSU_PreviewSkill <- null; + q.m.MSU_PreviewMovement <- null; + + // Overwrite the vanilla function to not set the preview action points while the actor is previewing. + // Because of the way our affordability preview system is set up we call skill_container.update() multiple times + // and that function sets the AP of the character. But during the preview we don't want to reset the + // action points due to these update calls otherwise it sets the preview action points back + // to the current action points of the actor, leading to wrong info in the UI. - Midas + q.setActionPoints = @() function( _a ) + { + this.m.ActionPoints = ::Math.round(_a); + if (!this.isPreviewing()) + this.setPreviewActionPoints(_a); + } q.onMovementStart = @(__original) function ( _tile, _numTiles ) { @@ -29,6 +43,28 @@ return ret; } + q.isPreviewing <- function() + { + return this.m.MSU_IsPreviewing; + } + + q.getPreviewSkill <- function() + { + return this.m.MSU_PreviewSkill; + } + + q.getPreviewMovement <- function() + { + return this.m.MSU_PreviewMovement; + } + + q.resetPreview <- function() + { + this.m.MSU_IsPreviewing = false; + this.m.MSU_PreviewSkill = null; + this.m.MSU_PreviewMovement = null; + } + // VANILLAFIX: http://battlebrothersgame.com/forums/topic/oncombatstarted-is-not-called-for-ai-characters/ // This fix is spread out over 4 files: tactical_entity_manager, actor, player, standard_bearer q.onCombatStart <- function() diff --git a/msu/hooks/skills/skill.nut b/msu/hooks/skills/skill.nut index aade63e10..7b6a39f9c 100644 --- a/msu/hooks/skills/skill.nut +++ b/msu/hooks/skills/skill.nut @@ -561,9 +561,13 @@ local preview = ::Tactical.TurnSequenceBar.m.ActiveEntityCostsPreview; if (preview != null && preview.id == this.getContainer().getActor().getID()) { + this.getContainer().update(); // During this update actor.isPreviewing() is true this.getContainer().m.MSU_IsApplyingPreview = true; local ret = __original(); this.getContainer().m.MSU_IsApplyingPreview = false; + this.getContainer().getActor().m.MSU_IsPreviewing = false; + this.getContainer().update(); // Do a normal update i.e. where actor.isPreviewing() is false + this.getContainer().getActor().m.MSU_IsPreviewing = true; local skillID = this.getContainer().getActor().getPreviewSkillID(); local str = " after " + (skillID == "" ? "moving" : "using " + this.getContainer().getSkillByID(skillID).getName()); ret = ::MSU.String.replace(ret, "Fatigue[/color]", "Fatigue[/color]" + str); diff --git a/msu/hooks/skills/skill_container.nut b/msu/hooks/skills/skill_container.nut index 7d2dd9189..2b94aa2c5 100644 --- a/msu/hooks/skills/skill_container.nut +++ b/msu/hooks/skills/skill_container.nut @@ -365,13 +365,13 @@ q.onTurnEnd = @() function() { - this.getActor().m.MSU_IsPreviewing = false; + this.getActor().resetPreview(); this.callSkillsFunctionWhenAlive("onTurnEnd"); } q.onWaitTurn = @() function() { - this.getActor().m.MSU_IsPreviewing = false; + this.getActor().resetPreview(); this.callSkillsFunctionWhenAlive("onWaitTurn"); } @@ -468,7 +468,7 @@ q.onCombatFinished = @() function() { - this.getActor().m.MSU_IsPreviewing = false; + this.getActor().resetPreview(); this.callSkillsFunction("onCombatFinished"); } diff --git a/msu/hooks/states/tactical_state.nut b/msu/hooks/states/tactical_state.nut index 092bb4429..107f084dd 100644 --- a/msu/hooks/states/tactical_state.nut +++ b/msu/hooks/states/tactical_state.nut @@ -1,13 +1,13 @@ ::MSU.MH.hook("scripts/states/tactical_state", function(q) { q.executeEntityTravel = @(__original) function( _activeEntity, _mouseEvent ) { - _activeEntity.m.MSU_IsPreviewing = false; + _activeEntity.resetPreview(); return __original(_activeEntity, _mouseEvent); } q.executeEntitySkill = @(__original) function( _activeEntity, _targetTile ) { - _activeEntity.m.MSU_IsPreviewing = false; + _activeEntity.resetPreview(); return __original(_activeEntity, _targetTile); } diff --git a/msu/hooks/ui/screens/tactical/modules/turn_sequence_bar/turn_sequence_bar.nut b/msu/hooks/ui/screens/tactical/modules/turn_sequence_bar/turn_sequence_bar.nut index 84b1b2acd..1fef0e591 100644 --- a/msu/hooks/ui/screens/tactical/modules/turn_sequence_bar/turn_sequence_bar.nut +++ b/msu/hooks/ui/screens/tactical/modules/turn_sequence_bar/turn_sequence_bar.nut @@ -41,28 +41,33 @@ local skillID = "SkillID" in _costsPreview ? _costsPreview.SkillID : ""; local skill; - local movementTile; + local movement; if (skillID == "") - { - local movement = ::Tactical.getNavigator().getCostForPath(activeEntity, ::Tactical.getNavigator().getLastSettings(), activeEntity.getActionPoints(), activeEntity.getFatigueMax() - activeEntity.getFatigue()); - movementTile = movement.End; - } - else skill = activeEntity.getSkills().getSkillByID(skillID); + movement = ::Tactical.getNavigator().getCostForPath(activeEntity, ::Tactical.getNavigator().getLastSettings(), activeEntity.getActionPoints(), activeEntity.getFatigueMax() - activeEntity.getFatigue()); + else + skill = activeEntity.getSkills().getSkillByID(skillID); activeEntity.m.MSU_IsPreviewing = true; - activeEntity.getSkills().onAffordablePreview(skill, movementTile); + activeEntity.getSkills().onAffordablePreview(skill, movement == null ? null : movement.End); + activeEntity.m.MSU_PreviewSkill = skill; + activeEntity.m.MSU_PreviewMovement = movement; + activeEntity.getSkills().update(); // During this update actor.isPreviewing() is true this.m.MSU_JSHandle.__JSHandle = this.m.JSHandle; this.m.JSHandle = this.m.MSU_JSHandle; __original(_costsPreview); this.m.JSHandle = this.m.MSU_JSHandle.__JSHandle; this.m.JSHandle.asyncCall("updateCostsPreview", this.m.ActiveEntityCostsPreview); + + activeEntity.m.MSU_IsPreviewing = false; + activeEntity.getSkills().update(); // Do a normal update i.e. where actor.isPreviewing() is false + activeEntity.m.MSU_IsPreviewing = true; } q.resetActiveEntityCostsPreview = @(__original) function() { local activeEntity = this.getActiveEntity(); - if (activeEntity != null) activeEntity.m.MSU_IsPreviewing = false; + if (activeEntity != null) activeEntity.resetPreview(); __original(); } }); From c7a41728f492ea231b13bc44668736487b6a7be6 Mon Sep 17 00:00:00 2001 From: LordMidas <55047920+LordMidas@users.noreply.github.com> Date: Sat, 4 May 2024 01:54:44 -0400 Subject: [PATCH 08/13] perf: move application of old affordability preview system to skill This improves performance by hooking only the relevant skills which may still be using the older deprecated system instead of doing a full pseudo-update cycle on all skills. It leverages the fact that the newer system already does an update during preview. --- msu/hooks/skills/skill.nut | 65 ++++++++++++++++ msu/hooks/skills/skill_container.nut | 75 ------------------- .../turn_sequence_bar/turn_sequence_bar.nut | 1 + 3 files changed, 66 insertions(+), 75 deletions(-) diff --git a/msu/hooks/skills/skill.nut b/msu/hooks/skills/skill.nut index 7b6a39f9c..4728ca774 100644 --- a/msu/hooks/skills/skill.nut +++ b/msu/hooks/skills/skill.nut @@ -578,3 +578,68 @@ } }); }); + +::MSU.QueueBucket.VeryLate.push(function() { + ::MSU.MH.rawHookTree("scripts/skills/skill", function(p) { + local obj = p; + while (!("onAffordablePreview" in obj)) + { + obj = obj[obj.SuperName]; + } + + if (obj.ClassName == "skill") + return; + + local parentName = p.SuperName; + + local onUpdate = "onUpdate" in p ? p.onUpdate : null; + p.onUpdate <- function( _properties ) + { + if (this.getContainer().getActor().isPreviewing() && ::MSU.Skills.QueuedPreviewChanges.len() != 0) + { + foreach (change in ::MSU.Skills.QueuedPreviewChanges[this]) + { + change.ValueBefore = change.TargetSkill != null ? change.TargetSkill.m[change.Field] : _properties[change.Field]; + } + + // To ensure that the executeScheduledChanges function for this skill is called + if (this.getContainer().m.ScheduledChangesSkills.find(this) == null) + this.getContainer().m.ScheduledChangesSkills.push(this); + } + + if (onUpdate != null) onUpdate(_properties); + else this[parentName].onUpdate(_properties); + } + + local executeScheduledChanges = "executeScheduledChanges" in p ? p.executeScheduledChanges : null; + p.executeScheduledChanges <- function() + { + if (executeScheduledChanges != null) executeScheduledChanges(); + else this[parentName].executeScheduledChanges(); + + if (this.getContainer().getActor().isPreviewing() && ::MSU.Skills.QueuedPreviewChanges.len() != 0) + { + local currentProperties = this.getContainer().getActor().getCurrentProperties(); + foreach (change in ::MSU.Skills.QueuedPreviewChanges[this]) + { + local target = change.TargetSkill != null ? change.TargetSkill.m : currentProperties; + + if (change.Multiplicative) change.CurrChange *= target[change.Field] / change.ValueBefore; + else change.CurrChange += target[change.Field] - change.ValueBefore; + + local previewTable = change.TargetSkill == null ? this.getContainer().m.PreviewProperty : change.TargetSkill.m.PreviewField; + + if (!(change.Field in previewTable)) + previewTable[change.Field] <- { Change = change.Multiplicative ? 1 : 0, Multiplicative = change.Multiplicative }; + + if (change.Multiplicative) + previewTable[change.Field].Change *= change.NewChange / (change.CurrChange == 0 ? 1 : change.CurrChange); + else if (typeof change.NewChange == "bool") + previewTable[change.Field].Change = change.NewChange; + else + previewTable[change.Field].Change += change.NewChange - change.CurrChange; + } + } + } + }); +}); diff --git a/msu/hooks/skills/skill_container.nut b/msu/hooks/skills/skill_container.nut index 2b94aa2c5..33d15b3d0 100644 --- a/msu/hooks/skills/skill_container.nut +++ b/msu/hooks/skills/skill_container.nut @@ -230,81 +230,6 @@ if (!skill.isGarbage()) skill.onAffordablePreview(_skill, _movementTile); } - - if (::MSU.Skills.QueuedPreviewChanges.len() == 0) return; - - local propertiesClone = this.getActor().getBaseProperties().getClone(); - - local getChange = function( _function ) - { - local skills = _function == "executeScheduledChanges" ? this.m.ScheduledChangesSkills : this.m.Skills; - foreach (skill in skills) - { - if (!skill.isGarbage()) - { - foreach (caller, changes in ::MSU.Skills.QueuedPreviewChanges) - { - if (caller == skill) - { - foreach (change in changes) - { - local target = change.TargetSkill != null ? change.TargetSkill.m : propertiesClone; - change.ValueBefore = target[change.Field]; - } - } - } - - if (_function == "executeScheduledChanges") skill[_function](); - else skill[_function](propertiesClone); - - foreach (caller, changes in ::MSU.Skills.QueuedPreviewChanges) - { - if (caller == skill) - { - foreach (change in changes) - { - if (typeof change.NewChange == "bool") continue; - - local target = change.TargetSkill != null ? change.TargetSkill.m : propertiesClone; - if (target[change.Field] == change.ValueBefore) continue; - - if (change.Multiplicative) change.CurrChange *= target[change.Field] / change.ValueBefore; - else change.CurrChange += target[change.Field] - change.ValueBefore; - } - } - } - } - } - } - - foreach (skill in this.m.Skills) - { - skill.softReset(); - } - - getChange("onUpdate"); - getChange("onAfterUpdate"); - getChange("executeScheduledChanges"); - - foreach (changes in ::MSU.Skills.QueuedPreviewChanges) - { - foreach (change in changes) - { - local previewTable = change.TargetSkill == null ? this.m.PreviewProperty : change.TargetSkill.m.PreviewField; - - if (!(change.Field in previewTable)) - previewTable[change.Field] <- { Change = change.Multiplicative ? 1 : 0, Multiplicative = change.Multiplicative }; - - if (change.Multiplicative) - previewTable[change.Field].Change *= change.NewChange / (change.CurrChange == 0 ? 1 : change.CurrChange); - else if (typeof change.NewChange == "bool") - previewTable[change.Field].Change = change.NewChange; - else - previewTable[change.Field].Change += change.NewChange - change.CurrChange; - } - } - - ::MSU.Skills.QueuedPreviewChanges.clear(); } //Vanilla Overwrites start diff --git a/msu/hooks/ui/screens/tactical/modules/turn_sequence_bar/turn_sequence_bar.nut b/msu/hooks/ui/screens/tactical/modules/turn_sequence_bar/turn_sequence_bar.nut index 1fef0e591..db8bf18fa 100644 --- a/msu/hooks/ui/screens/tactical/modules/turn_sequence_bar/turn_sequence_bar.nut +++ b/msu/hooks/ui/screens/tactical/modules/turn_sequence_bar/turn_sequence_bar.nut @@ -52,6 +52,7 @@ activeEntity.m.MSU_PreviewSkill = skill; activeEntity.m.MSU_PreviewMovement = movement; activeEntity.getSkills().update(); // During this update actor.isPreviewing() is true + ::MSU.Skills.QueuedPreviewChanges.clear(); this.m.MSU_JSHandle.__JSHandle = this.m.JSHandle; this.m.JSHandle = this.m.MSU_JSHandle; From 60febdff6a771c686ebf82b938161831bbd2d4e5 Mon Sep 17 00:00:00 2001 From: LordMidas <55047920+LordMidas@users.noreply.github.com> Date: Fri, 24 May 2024 14:19:27 -0400 Subject: [PATCH 09/13] fix: don't allow setting actor to dirty while previewing Otherwise the vanilla preview system completely breaks down (i.e. doesn't even show selected skill or preview fatigue or etc.) while a skill is being previewed. This happens because we do skill_container.update calls during the setActiveEntityCostsPreview in turn_sequence_bar and the update function calls setDirty which forces the actor's UI to update. This somehow causes some conflicts. --- msu/hooks/entity/tactical/actor.nut | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/msu/hooks/entity/tactical/actor.nut b/msu/hooks/entity/tactical/actor.nut index 2298b98f8..bb884f728 100644 --- a/msu/hooks/entity/tactical/actor.nut +++ b/msu/hooks/entity/tactical/actor.nut @@ -15,6 +15,12 @@ this.setPreviewActionPoints(_a); } + q.setDirty = @(__original) function( _value ) + { + if (this.m.MSU_PreviewSkill == null && this.m.MSU_PreviewMovement == null) + __original(_value); + } + q.onMovementStart = @(__original) function ( _tile, _numTiles ) { __original(_tile, _numTiles); From 008dfa56f4f94e5c833ff8ee7396b67982eb2769 Mon Sep 17 00:00:00 2001 From: LordMidas <55047920+LordMidas@users.noreply.github.com> Date: Wed, 22 May 2024 17:46:56 -0400 Subject: [PATCH 10/13] fix: costs preview not calling the original early enough The entire point of the switcheroo on JSHandle is so that the original function is called before our skill container event (onAffordablePreview) so that the values set by the original function happen before our event. --- .../modules/turn_sequence_bar/turn_sequence_bar.nut | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/msu/hooks/ui/screens/tactical/modules/turn_sequence_bar/turn_sequence_bar.nut b/msu/hooks/ui/screens/tactical/modules/turn_sequence_bar/turn_sequence_bar.nut index db8bf18fa..0c373cebc 100644 --- a/msu/hooks/ui/screens/tactical/modules/turn_sequence_bar/turn_sequence_bar.nut +++ b/msu/hooks/ui/screens/tactical/modules/turn_sequence_bar/turn_sequence_bar.nut @@ -39,6 +39,15 @@ if (activeEntity == null) return __original(_costsPreview); + // The original function also updates the UI, but we don't want to do that yet, + // we want to do that after our skill container affordability event has run. + // If we don't switcheroo to disable it here, then we'd need to manually call `updateCostsPreview` + // on the JSHandle at the end again, and this can lead to slightly glitchy UI animation due to double updating. + this.m.MSU_JSHandle.__JSHandle = this.m.JSHandle; + this.m.JSHandle = this.m.MSU_JSHandle; + __original(_costsPreview); + this.m.JSHandle = this.m.MSU_JSHandle.__JSHandle; + local skillID = "SkillID" in _costsPreview ? _costsPreview.SkillID : ""; local skill; local movement; @@ -54,10 +63,6 @@ activeEntity.getSkills().update(); // During this update actor.isPreviewing() is true ::MSU.Skills.QueuedPreviewChanges.clear(); - this.m.MSU_JSHandle.__JSHandle = this.m.JSHandle; - this.m.JSHandle = this.m.MSU_JSHandle; - __original(_costsPreview); - this.m.JSHandle = this.m.MSU_JSHandle.__JSHandle; this.m.JSHandle.asyncCall("updateCostsPreview", this.m.ActiveEntityCostsPreview); activeEntity.m.MSU_IsPreviewing = false; From a30beac81beef6dd544755130a31f67f4ae2a002 Mon Sep 17 00:00:00 2001 From: LordMidas <55047920+LordMidas@users.noreply.github.com> Date: Tue, 28 May 2024 13:38:12 -0400 Subject: [PATCH 11/13] fix: affordability preview not working properly with preview fatigue Because it was getting reset during the skill_container.update call in setActiveEntityCostsPreview. --- msu/hooks/entity/tactical/actor.nut | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/msu/hooks/entity/tactical/actor.nut b/msu/hooks/entity/tactical/actor.nut index bb884f728..598599309 100644 --- a/msu/hooks/entity/tactical/actor.nut +++ b/msu/hooks/entity/tactical/actor.nut @@ -15,6 +15,13 @@ this.setPreviewActionPoints(_a); } + q.setFatigue = @() function( _f ) + { + this.m.Fatigue = ::Math.max(0, ::Math.round(_f)); + if (!this.isPreviewing()) + this.setPreviewFatigue(_f); + } + q.setDirty = @(__original) function( _value ) { if (this.m.MSU_PreviewSkill == null && this.m.MSU_PreviewMovement == null) From f91cf22b681877525d795403ba69ab34a7a80d52 Mon Sep 17 00:00:00 2001 From: LordMidas <55047920+LordMidas@users.noreply.github.com> Date: Tue, 28 May 2024 14:00:09 -0400 Subject: [PATCH 12/13] fix: reset actor preview fields after update so tooltips color properly So that in the cost string of skill tooltips the color of the property (e.g. Action points or Fatigue) which will make a skill unaffordable will be properly colored red. --- msu/hooks/skills/skill.nut | 7 +++++++ .../modules/turn_sequence_bar/turn_sequence_bar.nut | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/msu/hooks/skills/skill.nut b/msu/hooks/skills/skill.nut index 4728ca774..1a528d284 100644 --- a/msu/hooks/skills/skill.nut +++ b/msu/hooks/skills/skill.nut @@ -561,6 +561,9 @@ local preview = ::Tactical.TurnSequenceBar.m.ActiveEntityCostsPreview; if (preview != null && preview.id == this.getContainer().getActor().getID()) { + local previewFatigue = this.getContainer().getActor().getPreviewFatigue(); + local previewAP = this.getContainer().getActor().getPreviewActionPoints(); + this.getContainer().update(); // During this update actor.isPreviewing() is true this.getContainer().m.MSU_IsApplyingPreview = true; local ret = __original(); @@ -568,6 +571,10 @@ this.getContainer().getActor().m.MSU_IsPreviewing = false; this.getContainer().update(); // Do a normal update i.e. where actor.isPreviewing() is false this.getContainer().getActor().m.MSU_IsPreviewing = true; + + this.getContainer().getActor().setPreviewFatigue(previewFatigue); + this.getContainer().getActor().setPreviewActionPoints(previewAP); + local skillID = this.getContainer().getActor().getPreviewSkillID(); local str = " after " + (skillID == "" ? "moving" : "using " + this.getContainer().getSkillByID(skillID).getName()); ret = ::MSU.String.replace(ret, "Fatigue[/color]", "Fatigue[/color]" + str); diff --git a/msu/hooks/ui/screens/tactical/modules/turn_sequence_bar/turn_sequence_bar.nut b/msu/hooks/ui/screens/tactical/modules/turn_sequence_bar/turn_sequence_bar.nut index 0c373cebc..0227ab8c9 100644 --- a/msu/hooks/ui/screens/tactical/modules/turn_sequence_bar/turn_sequence_bar.nut +++ b/msu/hooks/ui/screens/tactical/modules/turn_sequence_bar/turn_sequence_bar.nut @@ -56,6 +56,9 @@ else skill = activeEntity.getSkills().getSkillByID(skillID); + local previewFatigue = activeEntity.getPreviewFatigue(); + local previewAP = activeEntity.getPreviewActionPoints(); + activeEntity.m.MSU_IsPreviewing = true; activeEntity.getSkills().onAffordablePreview(skill, movement == null ? null : movement.End); activeEntity.m.MSU_PreviewSkill = skill; @@ -68,6 +71,9 @@ activeEntity.m.MSU_IsPreviewing = false; activeEntity.getSkills().update(); // Do a normal update i.e. where actor.isPreviewing() is false activeEntity.m.MSU_IsPreviewing = true; + + activeEntity.setPreviewFatigue(previewFatigue); + activeEntity.setPreviewActionPoints(previewAP); } q.resetActiveEntityCostsPreview = @(__original) function() From 41d577a0fcafb9bc77216db4ec8181a10d35f891 Mon Sep 17 00:00:00 2001 From: LordMidas <55047920+LordMidas@users.noreply.github.com> Date: Mon, 15 Jul 2024 20:52:38 -0400 Subject: [PATCH 13/13] docs: add comments about deprecated first affordability preview system --- msu/hooks/skills/skill.nut | 8 +++++++- msu/hooks/skills/skill_container.nut | 1 + .../modules/turn_sequence_bar/turn_sequence_bar.nut | 2 +- msu/utils/skills.nut | 6 ++++-- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/msu/hooks/skills/skill.nut b/msu/hooks/skills/skill.nut index 1a528d284..ab5cacf23 100644 --- a/msu/hooks/skills/skill.nut +++ b/msu/hooks/skills/skill.nut @@ -62,7 +62,7 @@ q.m.IsBaseValuesSaved <- false; q.m.ScheduledChanges <- []; - q.m.PreviewField <- {}; + q.m.PreviewField <- {}; // Deprecated - part of the first Affordability Preview system q.isType = @() function( _t, _any = true, _only = false ) { @@ -334,15 +334,18 @@ { } + // Deprecated - part of the first Affordability Preview system q.onAffordablePreview <- function( _skill, _movementTile ) { } + // Deprecated - part of the first Affordability Preview system q.modifyPreviewField <- function( _skill, _field, _newChange, _multiplicative ) { ::MSU.Skills.modifyPreview(this, _skill, _field, _newChange, _multiplicative); } + // Deprecated - part of the first Affordability Preview system q.modifyPreviewProperty <- function( _skill, _field, _newChange, _multiplicative ) { ::MSU.Skills.modifyPreview(this, null, _field, _newChange, _multiplicative); @@ -485,6 +488,8 @@ ::MSU.QueueBucket.VeryLate.push(function() { ::MSU.MH.hook("scripts/skills/skill", function(q) { + // Deprecated - part of the first Affordability Preview system + // i.e. the hooks in this foreach loop are for the deprecated feature foreach (func in ::MSU.Skills.PreviewApplicableFunctions) { q[func] = @(__original) function() @@ -587,6 +592,7 @@ }); ::MSU.QueueBucket.VeryLate.push(function() { + // Legacy support for the first Affordablity Preview system which has been deprecated ::MSU.MH.rawHookTree("scripts/skills/skill", function(p) { local obj = p; while (!("onAffordablePreview" in obj)) diff --git a/msu/hooks/skills/skill_container.nut b/msu/hooks/skills/skill_container.nut index 33d15b3d0..6f00fd36d 100644 --- a/msu/hooks/skills/skill_container.nut +++ b/msu/hooks/skills/skill_container.nut @@ -220,6 +220,7 @@ ]); } + // Deprecated - part of the first Affordability Preview system q.onAffordablePreview <- function( _skill, _movementTile ) { this.m.PreviewProperty.clear(); diff --git a/msu/hooks/ui/screens/tactical/modules/turn_sequence_bar/turn_sequence_bar.nut b/msu/hooks/ui/screens/tactical/modules/turn_sequence_bar/turn_sequence_bar.nut index 0227ab8c9..1018e8ea2 100644 --- a/msu/hooks/ui/screens/tactical/modules/turn_sequence_bar/turn_sequence_bar.nut +++ b/msu/hooks/ui/screens/tactical/modules/turn_sequence_bar/turn_sequence_bar.nut @@ -60,7 +60,7 @@ local previewAP = activeEntity.getPreviewActionPoints(); activeEntity.m.MSU_IsPreviewing = true; - activeEntity.getSkills().onAffordablePreview(skill, movement == null ? null : movement.End); + activeEntity.getSkills().onAffordablePreview(skill, movement == null ? null : movement.End); // Deprecated - part of the first Affordability Preview system activeEntity.m.MSU_PreviewSkill = skill; activeEntity.m.MSU_PreviewMovement = movement; activeEntity.getSkills().update(); // During this update actor.isPreviewing() is true diff --git a/msu/utils/skills.nut b/msu/utils/skills.nut index 21ab9422d..e5eabaaf4 100644 --- a/msu/utils/skills.nut +++ b/msu/utils/skills.nut @@ -1,9 +1,9 @@ ::MSU.Skills <- { - PreviewApplicableFunctions = [ + PreviewApplicableFunctions = [ // Deprecated - part of the first Affordability Preview system "getActionPointCost", "getFatigueCost" ], - QueuedPreviewChanges = {}, + QueuedPreviewChanges = {}, // Deprecated - part of the first Affordability Preview system SoftResetFields = [ "ActionPointCost", "FatigueCost", @@ -48,6 +48,7 @@ }); } + // Deprecated - part of the first Affordability Preview system function addPreviewApplicableFunction( _name ) { ::MSU.requireString(_name); @@ -66,6 +67,7 @@ } // Private + // Deprecated - part of the first Affordability Preview system function modifyPreview( _caller, _targetSkill, _field, _newChange, _multiplicative ) { if (!(_caller in this.QueuedPreviewChanges)) this.QueuedPreviewChanges[_caller] <- [];