From 86c5607b5a615ef0dbc66cb550d18f275f53008a Mon Sep 17 00:00:00 2001 From: SoytheProton Date: Wed, 15 Apr 2026 17:33:45 -0700 Subject: [PATCH 1/4] Add files via upload --- Commands/VitalityCmd.cs | 164 ++++++++++++ Commands/VitalityCmd.cs.uid | 1 + Hooks/IVitalityAmountModifier.cs | 27 ++ Hooks/IVitalityAmountModifier.cs.uid | 1 + Hooks/IVitalityHooks.cs | 34 +++ Patches/VitalityPatch.cs | 382 +++++++++++++++++++++++++++ Patches/VitalityPatch.cs.uid | 1 + 7 files changed, 610 insertions(+) create mode 100644 Commands/VitalityCmd.cs create mode 100644 Commands/VitalityCmd.cs.uid create mode 100644 Hooks/IVitalityAmountModifier.cs create mode 100644 Hooks/IVitalityAmountModifier.cs.uid create mode 100644 Hooks/IVitalityHooks.cs create mode 100644 Patches/VitalityPatch.cs create mode 100644 Patches/VitalityPatch.cs.uid diff --git a/Commands/VitalityCmd.cs b/Commands/VitalityCmd.cs new file mode 100644 index 00000000..ee62ed2f --- /dev/null +++ b/Commands/VitalityCmd.cs @@ -0,0 +1,164 @@ +using MegaCrit.Sts2.Core.Combat; +using MegaCrit.Sts2.Core.Combat.History; +using MegaCrit.Sts2.Core.Combat.History.Entries; +using MegaCrit.Sts2.Core.Commands; +using MegaCrit.Sts2.Core.Entities.Cards; +using MegaCrit.Sts2.Core.Entities.Creatures; +using MegaCrit.Sts2.Core.Models; +using MegaCrit.Sts2.Core.Runs; +using MegaCrit.Sts2.Core.ValueProps; +using TestMod.TestModCode.Hooks; +using TestMod.TestModCode.Patches; + +namespace TestMod.TestModCode.Commands; + +public class VitalityCmd +{ + public static async Task GainVitality( + Creature creature, + Decimal amount, + CardPlay? cardPlay, + bool fast = false) + { + if (CombatManager.Instance.IsOverOrEnding) + return 0M; + CombatState combatState = creature.CombatState; + await BeforeVitalityGained(combatState, creature, amount, cardPlay?.Card); + Decimal modifiedAmount = amount; + IEnumerable modifiers; + modifiedAmount = ModifyVitality(combatState, creature, modifiedAmount, cardPlay.Card, cardPlay, out modifiers); + modifiedAmount = Math.Max(modifiedAmount, 0M); + await AfterModifyingVitalityAmount(combatState, modifiedAmount, cardPlay?.Card, cardPlay, modifiers); + if (modifiedAmount > 0M) + { + SfxCmd.Play("event:/sfx/heal"); + VfxCmd.PlayOnCreatureCenter(creature, "vfx/vfx_cross_heal"); + VitalityPatch.VitalityField.SetVitality(creature, (int) amount + VitalityPatch.VitalityField.GetVitality(creature)); + CombatManager.Instance.History.Add(new VitalityGainedEntry((int)modifiedAmount, cardPlay, creature, combatState.RoundNumber, combatState.CurrentSide, CombatManager.Instance.History)); + if (fast) + await Cmd.CustomScaledWait(0.0f, 0.03f); + else + await Cmd.CustomScaledWait(0.1f, 0.25f); + } + await AfterVitalityGained(combatState, creature, modifiedAmount, cardPlay?.Card); + return modifiedAmount; + } + + static decimal ModifyVitality( + CombatState combatState, + Creature creature, + Decimal amount, + CardModel? cardSource, + CardPlay? cardPlay, + out IEnumerable modifiers) + { + decimal num = amount; + List abstractModelList = new List(); + + foreach (var item in combatState.IterateHookListeners()) + { + if (item is IVitalityAmountModifier mod) + { + var num2 = mod.ModifyVitalityAdditive(creature, num, cardSource, cardPlay); + num += num2; + if (num2 != 0M) + abstractModelList.Add(item); + } + } + + foreach (var item in combatState.IterateHookListeners()) + { + if (item is IVitalityAmountModifier mod) + { + var num2 = mod.ModifyVitalityMultiplicative(creature, num, cardSource, cardPlay); + num *= num2; + if (num2 != 0M) + abstractModelList.Add(item); + } + } + + modifiers = abstractModelList; + return Math.Max(0m, num); + } + + static async Task BeforeVitalityGained( + CombatState combatState, + Creature creature, + Decimal amount, + CardModel? cardSource) + { + foreach (var item in combatState.IterateHookListeners()) + { + if (item is IVitalityHooks mod) + { + await mod.BeforeVitalityGained(creature, amount, cardSource); + item.InvokeExecutionFinished(); + } + } + } + + static async Task AfterModifyingVitalityAmount( + CombatState combatState, + Decimal amount, + CardModel? cardSource, + CardPlay? cardPlay, + IEnumerable modifiers) + { + foreach (var item in combatState.IterateHookListeners()) + { + if (item is IVitalityHooks mod && modifiers.Contains(item)) + { + await mod.AfterModifyingVitalityAmount(amount, cardSource, cardPlay); + item.InvokeExecutionFinished(); + } + } + } + + static async Task AfterVitalityGained( + CombatState combatState, + Creature creature, + Decimal amount, + CardModel? cardSource) + { + foreach (var item in combatState.IterateHookListeners()) + { + if (item is IVitalityHooks mod) + { + await mod.AfterVitalityGained(creature, amount, cardSource); + item.InvokeExecutionFinished(); + } + } + } + + public class VitalityGainedEntry : CombatHistoryEntry + { + public int Amount { get; } + + public Creature Receiver => Actor; + + public CardPlay? CardPlay { get; } + + public override string Description + { + get => $"{GetId(Receiver)} gained {Amount} vitality"; + } + + public VitalityGainedEntry( + int amount, + CardPlay? cardPlay, + Creature receiver, + int roundNumber, + CombatSide currentSide, + CombatHistory history) + : base(receiver, roundNumber, currentSide, history) + { + Amount = amount; + CardPlay = cardPlay; + } + + public static string GetId(Creature creature) + { + return !creature.IsPlayer ? creature.Monster.Id.Entry : creature.Player.Character.Id.Entry; + } + } +} \ No newline at end of file diff --git a/Commands/VitalityCmd.cs.uid b/Commands/VitalityCmd.cs.uid new file mode 100644 index 00000000..ceb1b8c7 --- /dev/null +++ b/Commands/VitalityCmd.cs.uid @@ -0,0 +1 @@ +uid://dq3c3rm1cl23x diff --git a/Hooks/IVitalityAmountModifier.cs b/Hooks/IVitalityAmountModifier.cs new file mode 100644 index 00000000..106f416e --- /dev/null +++ b/Hooks/IVitalityAmountModifier.cs @@ -0,0 +1,27 @@ +using MegaCrit.Sts2.Core.Entities.Cards; +using MegaCrit.Sts2.Core.Entities.Creatures; +using MegaCrit.Sts2.Core.Models; + +namespace TestMod.TestModCode.Hooks; + +public interface IVitalityAmountModifier +{ + /// + /// Return the amount to add. + /// + /// + /// + /// + /// + /// + decimal ModifyVitalityAdditive(Creature creature, decimal amount, CardModel cardSource, CardPlay? cardPlay) => 0m; + /// + /// Return the amount to multiply by. + /// + /// + /// + /// + /// + /// + decimal ModifyVitalityMultiplicative(Creature creature, decimal amount, CardModel cardSource, CardPlay? cardPlay) => 1m; +} \ No newline at end of file diff --git a/Hooks/IVitalityAmountModifier.cs.uid b/Hooks/IVitalityAmountModifier.cs.uid new file mode 100644 index 00000000..e96c3160 --- /dev/null +++ b/Hooks/IVitalityAmountModifier.cs.uid @@ -0,0 +1 @@ +uid://bwf5fpvjk8j0x diff --git a/Hooks/IVitalityHooks.cs b/Hooks/IVitalityHooks.cs new file mode 100644 index 00000000..2f956876 --- /dev/null +++ b/Hooks/IVitalityHooks.cs @@ -0,0 +1,34 @@ +using MegaCrit.Sts2.Core.Entities.Cards; +using MegaCrit.Sts2.Core.Entities.Creatures; +using MegaCrit.Sts2.Core.Models; + +namespace TestMod.TestModCode.Hooks; + +public interface IVitalityHooks +{ + /// + /// Called before Vitality is gained. + /// + /// + /// + /// + /// + Task BeforeVitalityGained (Creature creature, decimal amount, CardModel cardSource) => Task.CompletedTask; + /// + /// Called after if Vitality was modified by Model, but before Vitality is gained. + /// + /// + /// + /// + /// + Task AfterModifyingVitalityAmount(decimal amount, CardModel cardSource, CardPlay? cardPlay) => Task.CompletedTask; + + /// + /// Called after Vitality is gained. + /// + /// + /// + /// + /// + Task AfterVitalityGained(Creature creature, decimal amount, CardModel cardSource) => Task.CompletedTask; +} \ No newline at end of file diff --git a/Patches/VitalityPatch.cs b/Patches/VitalityPatch.cs new file mode 100644 index 00000000..8aae1931 --- /dev/null +++ b/Patches/VitalityPatch.cs @@ -0,0 +1,382 @@ +using System.Reflection; +using System.Reflection.Emit; +using BaseLib.Hooks; +using BaseLib.Utils; +using Godot; +using HarmonyLib; +using MegaCrit.Sts2.addons.mega_text; +using MegaCrit.Sts2.Core.Combat; +using MegaCrit.Sts2.Core.Commands; +using MegaCrit.Sts2.Core.Entities.Cards; +using MegaCrit.Sts2.Core.Entities.Creatures; +using MegaCrit.Sts2.Core.Entities.Players; +using MegaCrit.Sts2.Core.Helpers; +using MegaCrit.Sts2.Core.Models; +using MegaCrit.Sts2.Core.Nodes.Combat; +using MegaCrit.Sts2.Core.Nodes.Multiplayer; +using MegaCrit.Sts2.Core.Runs; +using MegaCrit.Sts2.Core.Saves; +using MegaCrit.Sts2.Core.Settings; +using TestMod.TestModCode.Hooks; +using TestMod.TestModCode.Ui; + +namespace TestMod.TestModCode.Patches; + +public static class VitalityPatch +{ + private static readonly Color VitalityOutlineColor = new Color("FFC800"); + private static readonly Color VitalityTextOutlineColor = new Color("998000"); + + public static class VitalityField + { + /// + /// !!IMPORTANT!! If intending to change this value, use SetVitality to avoid issues. + /// + private static readonly SpireField TemporaryHp = new(() => 0); + + public static readonly SpireField?> VitalityChanged = new(() => null); + public static readonly SpireField> VitalityChanged2 = new(() => null); // this exists only for CombatStateTracker. + public static readonly SpireField VitalityTween = new(() => null); + public static void SetVitality(Creature creature, int value) + { + if (value < 0) + throw new ArgumentException("Block must be positive", nameof (value)); + if (TemporaryHp.Get(creature) == value) + return; + int tempHp = TemporaryHp.Get(creature); + TemporaryHp.Set(creature, value); + Action? vitalityChanged = VitalityChanged.Get(creature); + vitalityChanged?.Invoke(tempHp, TemporaryHp.Get(creature), creature); + Action vitalityChanged2 = VitalityChanged2.Get(creature); + vitalityChanged?.Invoke(tempHp, TemporaryHp.Get(creature), creature); + } + + public static int GetVitality(Creature creature) + { + return TemporaryHp.Get(creature); + } + } + + [HarmonyPatch(typeof(Creature))] + [HarmonyPatch("LoseHpInternal")] + public class HpInterceptPatch + { + private static int temporaryHp; + + static IEnumerable Transpiler(IEnumerable instructions) + { + var codeMatcher = new CodeMatcher(instructions); + MethodInfo getCurrentHpInfo = AccessTools.PropertyGetter(typeof(Creature), nameof(Creature.CurrentHp)); + + MethodInfo tempHp = AccessTools.Method(typeof(HpInterceptPatch), nameof(TemporaryHpHandler)); + MethodInfo unblockedOverride = AccessTools.Method(typeof(HpInterceptPatch), nameof(UnblockedDamageOverride)); + + codeMatcher.MatchStartForward( + new CodeMatch(OpCodes.Ldarg_0), + new CodeMatch(OpCodes.Ldarg_0), + new CodeMatch(OpCodes.Call, getCurrentHpInfo) + ) + .ThrowIfInvalid("Couldn't find getCurrentHp method for TemporaryHpHandler") + .InsertAndAdvance( + new CodeInstruction(OpCodes.Ldarg_0), + new CodeInstruction(OpCodes.Ldloc_2), + new CodeInstruction(OpCodes.Call, tempHp), + new CodeInstruction(OpCodes.Stloc_2) + ); + + codeMatcher.MatchStartForward( + new CodeMatch(OpCodes.Ldloc_1), + new CodeMatch(OpCodes.Ldarg_0), + new CodeMatch(OpCodes.Call, getCurrentHpInfo), + new CodeMatch(OpCodes.Sub) + ) + .ThrowIfInvalid("Couldn't find getCurrentHp method for TemporaryHpConfig") + .InsertAfterAndAdvance( + new CodeMatch(OpCodes.Ldarg_0), + new CodeInstruction(OpCodes.Call, unblockedOverride) + ); + + return codeMatcher.InstructionEnumeration(); + } + + private static int TemporaryHpHandler(Creature c, int num) + { + int tempHp = temporaryHp = (int) VitalityField.GetVitality(c); + if (num >= tempHp) + { + num -= tempHp; + VitalityField.SetVitality(c, 0); + } + else + { + VitalityField.SetVitality(c, tempHp - num); + num = 0; + } + return num; + } + + private static int UnblockedDamageOverride(int unblockedDamage, Creature c) + { + if (TestModConfig.TriggerHpLoss) + { + temporaryHp -= (int) VitalityField.GetVitality(c); + return unblockedDamage + temporaryHp; + } + return unblockedDamage; + } + } + + [HarmonyPatch(typeof(NHealthBar))] + [HarmonyPatch("IsPoisonLethal")] + public class TemporaryHpPoisonPatch + { + static bool Postfix(bool __result, int poisonDamage, Creature ____creature) + { + if (!__result) + { + return __result; + } + return ____creature.CurrentHp + VitalityField.GetVitality(____creature) <= poisonDamage; + } + + } + + [HarmonyPatch(typeof(NHealthBar), "RefreshBlockUi")] + public class TempHpOutline + { + + [HarmonyPostfix] + public static void SelfModulateOutline(Creature ____creature, Control ____blockOutline) + { + if (____creature.Block > 0 || VitalityField.GetVitality(____creature) <= 0) + { + ____blockOutline.SelfModulate = Colors.White; + return; + } + + ____blockOutline.Visible = true; + ____blockOutline.SelfModulate = VitalityOutlineColor; + } + } + + [HarmonyPatch(typeof(NHealthBar), "RefreshText")] + public class VitalityText + { + + [HarmonyPostfix] + public static void SelfModulateOutline(Creature ____creature, MegaLabel ____hpLabel) + { + if (____creature.Block > 0 || VitalityField.GetVitality(____creature) <= 0) + return; + + ____hpLabel.AddThemeColorOverride(ThemeConstants.Label.FontColor, NHealthBar._defaultFontColor); + ____hpLabel.AddThemeColorOverride(ThemeConstants.Label.FontOutlineColor, VitalityTextOutlineColor); + } + } + + // Code courtesy of CanYou with mild alterations. + [HarmonyPatch] + public static class VitalityHealthBarPatch + { + public static readonly Dictionary VitalityUi = new(); + public static readonly Dictionary CreatureHealthBar = new(); + + [HarmonyPatch(typeof(NHealthBar), nameof(NHealthBar.SetCreature))] + [HarmonyPostfix] + public static void CreateVitalityUi(NHealthBar __instance, Control ____blockContainer) + { + var vitalityContainer = (Control)____blockContainer.Duplicate(); + vitalityContainer.Visible = false; + vitalityContainer.Name = "VitalityContainer"; + + // Swap block icon for heart, tinted yellow + var icon = vitalityContainer.GetNode("BlockIcon"); + icon.Texture = GD.Load("res://images/atlases/ui_atlas.sprites/top_bar/top_bar_heart.tres"); + icon.SelfModulate = VitalityOutlineColor; + + var shaderCode = @" + shader_type canvas_item; + uniform vec4 tint_color : source_color = vec4(0.6, 0.6, 0, 1.0); + void fragment() { + vec4 tex = texture(TEXTURE, UV); + COLOR = vec4(tint_color.rgb, tex.a); + }"; + var shader = new Shader(); + shader.Code = shaderCode; + var material = new ShaderMaterial(); + material.Shader = shader; + material.SetShaderParameter("tint_color", VitalityOutlineColor); + icon.Material = material; + + var label = vitalityContainer.GetNode("BlockLabel"); + label.AddThemeColorOverride(ThemeConstants.Label.FontOutlineColor, VitalityTextOutlineColor); + + __instance.HpBarContainer.AddChild(vitalityContainer); + vitalityContainer.SetAnchorsPreset(Control.LayoutPreset.CenterLeft, true); + + // Mirror to the right side + vitalityContainer.Position = new Vector2( + __instance.HpBarContainer.Size.X - vitalityContainer.Size.X, + ____blockContainer.Position.Y); + + CreatureHealthBar[__instance._creature] = __instance; + VitalityUi[__instance] = (vitalityContainer, label); + } + + [HarmonyPatch(typeof(NHealthBar), "RefreshBlockUi")] + [HarmonyPostfix] + public static void RefreshVitalityUi(NHealthBar __instance, Creature ____creature) + { + if (!VitalityUi.TryGetValue(__instance, out var ui)) return; + + if (VitalityField.GetVitality(____creature) > 0) + { + ui.container.Visible = true; + ui.label.SetTextAutoSize(((int)VitalityField.GetVitality(____creature)).ToString()); + } + else + { + ui.container.Visible = false; + } + } + + [HarmonyPatch(typeof(NHealthBar), "SetHpBarContainerSizeWithOffsetsImmediately")] + [HarmonyPostfix] + public static void SetUpVitalityOffset(NHealthBar __instance) + { + if (!VitalityUi.TryGetValue(__instance, out var ui)) return; + + ui.container.Position = new Vector2( + __instance.HpBarContainer.Size.X - ui.container.Size.X + 9f, + __instance._blockContainer.Position.Y); + } + } + private static readonly Color[] HbColors = + [Colors.Gold, Colors.Green, Colors.MediumAquamarine, Colors.MediumVioletRed]; + + private static Color HealthBarColors(int i) => i > HbColors.Length - 1 ? HbColors[^1] : HbColors[i]; + public class VitalityForecast : IHealthBarForecastSource + { + public IEnumerable GetHealthBarForecastSegments(HealthBarForecastContext context) + { + var list = new List(); + for (var i = 0; i <= VitalityField.GetVitality(context.Creature) / context.Creature.CurrentHp; i++) + { + list.Add(new HealthBarForecastSegment( + (int)VitalityField.GetVitality(context.Creature) - context.Creature.CurrentHp * i, + HealthBarColors(i), HealthBarForecastDirection.FromLeft, -i)); + } + return list; + } + } + public static void AnimateInVitality(int oldVitality, int vitalityGain, Creature creature) + { + AnimateInVitality(oldVitality, vitalityGain, VitalityHealthBarPatch.CreatureHealthBar[creature]); + } + + public static void AnimateInVitality(int oldVitality, int vitalityGain, NHealthBar healthBar) + { + if (oldVitality != 0 || vitalityGain == 0) + return; + if (!VitalityHealthBarPatch.VitalityUi.TryGetValue(healthBar, out var ui)) return; + ui.container.Visible = true; + if (SaveManager.Instance.PrefsSave.FastMode == FastModeType.Instant) + return; + var originalPosition = ui.container.Position = new Vector2( + healthBar.HpBarContainer.Size.X - ui.container.Size.X + 9f, + healthBar._blockContainer.Position.Y); + ui.container.Modulate = StsColors.transparentWhite; + ui.container.Position = originalPosition - NHealthBar._blockAnimOffset; + VitalityField.VitalityTween.Get(healthBar._creature)?.Kill(); + VitalityField.VitalityTween.Set(healthBar._creature, healthBar.CreateTween().SetParallel()); + VitalityField.VitalityTween.Get(healthBar._creature)? + .TweenProperty(ui.container, (NodePath)"modulate:a", 1f, 0.5).SetEase(Tween.EaseType.Out) + .SetTrans(Tween.TransitionType.Sine); + VitalityField.VitalityTween.Get(healthBar._creature)? + .TweenProperty(ui.container, (NodePath)"position", originalPosition, 0.5) + .SetEase(Tween.EaseType.Out).SetTrans(Tween.TransitionType.Back); + if (healthBar._creature.IsPlayer) healthBar.RefreshValues(); + } + + [HarmonyPatch] + public class NMultiplayerPlayerStatePatch + { + [HarmonyPatch(typeof(NMultiplayerPlayerState))] + [HarmonyPatch("_Ready")] + public class _ReadyPatch + { + static void Postfix(NMultiplayerPlayerState __instance) + { + VitalityField.VitalityChanged.Set(__instance.Player.Creature, + VitalityField.VitalityChanged.Get(__instance.Player.Creature) + AnimateInVitality); + } + } + + [HarmonyPatch(typeof(NMultiplayerPlayerState))] + [HarmonyPatch("_ExitTree")] + public class _ExitTreePatch + { + static void Postfix(NMultiplayerPlayerState __instance) + { + VitalityField.VitalityChanged.Set(__instance.Player.Creature, + VitalityField.VitalityChanged.Get(__instance.Player.Creature) - AnimateInVitality); + } + } + } + + [HarmonyPatch] + public class NCreatureStateDisplayPatch + { + [HarmonyPatch(typeof(NCreatureStateDisplay))] + [HarmonyPatch("SubscribeToCreatureEvents")] + public class SubscribeToCreatureEventsPatch + { + static void Postfix(NCreatureStateDisplay __instance) + { + if (__instance._creature == null) return; + VitalityField.VitalityChanged.Set(__instance._creature, + VitalityField.VitalityChanged.Get(__instance._creature) + AnimateInVitality); + } + } + + [HarmonyPatch(typeof(NCreatureStateDisplay))] + [HarmonyPatch("_ExitTree")] + public class _ExitTreePatch + { + static void Postfix(NCreatureStateDisplay __instance) + { + if (__instance._creature == null) return; + VitalityField.VitalityChanged.Set(__instance._creature, + VitalityField.VitalityChanged.Get(__instance._creature) - AnimateInVitality); + } + } + } + + [HarmonyPatch] + public class CombatStateTrackerPatch + { + [HarmonyPatch(typeof(CombatStateTracker))] + [HarmonyPatch("Subscribe")] + [HarmonyPatch([typeof(Creature)])] + public class SubscribePatch + { + static void Postfix(Creature creature) + { + VitalityField.VitalityChanged.Set(creature, + VitalityField.VitalityChanged.Get(creature) + AnimateInVitality); + } + } + + [HarmonyPatch(typeof(CombatStateTracker))] + [HarmonyPatch("Unsubscribe")] + [HarmonyPatch([typeof(Creature)])] + public class UnsubscribePatch + { + static void Postfix(Creature creature) + { + VitalityField.VitalityChanged.Set(creature, + VitalityField.VitalityChanged.Get(creature) - AnimateInVitality); + } + } + } +} \ No newline at end of file diff --git a/Patches/VitalityPatch.cs.uid b/Patches/VitalityPatch.cs.uid new file mode 100644 index 00000000..7e4052d8 --- /dev/null +++ b/Patches/VitalityPatch.cs.uid @@ -0,0 +1 @@ +uid://c3yt8v41pfnul From 142b470310f477c3469a81d433d6d6e0209d7232 Mon Sep 17 00:00:00 2001 From: SoytheProton Date: Wed, 15 Apr 2026 17:56:39 -0700 Subject: [PATCH 2/4] fixes --- .../localization/eng/static_hover_tips.json | 4 ++- Cards/Variables/VitalityVar.cs | 14 ++++++++++ Commands/VitalityCmd.cs | 11 +++----- Hooks/IVitalityAmountModifier.cs | 2 +- Hooks/IVitalityHooks.cs | 2 +- Patches/{ => Features}/VitalityPatch.cs | 26 ++++++++----------- Patches/{ => Features}/VitalityPatch.cs.uid | 0 7 files changed, 34 insertions(+), 25 deletions(-) create mode 100644 Cards/Variables/VitalityVar.cs rename Patches/{ => Features}/VitalityPatch.cs (93%) rename Patches/{ => Features}/VitalityPatch.cs.uid (100%) diff --git a/BaseLib/localization/eng/static_hover_tips.json b/BaseLib/localization/eng/static_hover_tips.json index ed6c49da..da0cf952 100644 --- a/BaseLib/localization/eng/static_hover_tips.json +++ b/BaseLib/localization/eng/static_hover_tips.json @@ -4,5 +4,7 @@ "BASELIB-EXHAUSTIVE.title": "Exhaustive", "BASELIB-EXHAUSTIVE.description": "This card [gold]Exhausts[/gold] after {Exhaustive:cond:>1?[blue]{Exhaustive}[/blue] |}{Exhaustive:plural:use|uses}.", "BASELIB-REFUND.title": "Refund", - "BASELIB-REFUND.description": "When energy is spent on this card, up to [blue]{Refund}[/blue] of that energy is refunded." + "BASELIB-REFUND.description": "When energy is spent on this card, up to [blue]{Refund}[/blue] of that energy is refunded.", + "BASELIB-VITALITY.title": "Vitality", + "BASELIB-VITALITY.description": "Until the end of combat, prevents HP loss." } \ No newline at end of file diff --git a/Cards/Variables/VitalityVar.cs b/Cards/Variables/VitalityVar.cs new file mode 100644 index 00000000..06edd476 --- /dev/null +++ b/Cards/Variables/VitalityVar.cs @@ -0,0 +1,14 @@ +using BaseLib.Extensions; +using MegaCrit.Sts2.Core.Localization.DynamicVars; + +namespace BaseLib.Cards.Variables; + +public class VitalityVar : DynamicVar +{ + public const string Key = "Vitality"; + + public VitalityVar(decimal baseValue) : base(Key, baseValue) + { + this.WithTooltip(); + } +} \ No newline at end of file diff --git a/Commands/VitalityCmd.cs b/Commands/VitalityCmd.cs index ee62ed2f..a31a72e7 100644 --- a/Commands/VitalityCmd.cs +++ b/Commands/VitalityCmd.cs @@ -1,16 +1,13 @@ using MegaCrit.Sts2.Core.Combat; using MegaCrit.Sts2.Core.Combat.History; -using MegaCrit.Sts2.Core.Combat.History.Entries; using MegaCrit.Sts2.Core.Commands; using MegaCrit.Sts2.Core.Entities.Cards; using MegaCrit.Sts2.Core.Entities.Creatures; using MegaCrit.Sts2.Core.Models; -using MegaCrit.Sts2.Core.Runs; -using MegaCrit.Sts2.Core.ValueProps; -using TestMod.TestModCode.Hooks; -using TestMod.TestModCode.Patches; +using BaseLib.Hooks; +using BaseLib.Patches; -namespace TestMod.TestModCode.Commands; +namespace BaseLib.Commands; public class VitalityCmd { @@ -130,7 +127,7 @@ static async Task AfterVitalityGained( } } - public class VitalityGainedEntry : CombatHistoryEntry + private class VitalityGainedEntry : CombatHistoryEntry { public int Amount { get; } diff --git a/Hooks/IVitalityAmountModifier.cs b/Hooks/IVitalityAmountModifier.cs index 106f416e..6ff46e46 100644 --- a/Hooks/IVitalityAmountModifier.cs +++ b/Hooks/IVitalityAmountModifier.cs @@ -2,7 +2,7 @@ using MegaCrit.Sts2.Core.Entities.Creatures; using MegaCrit.Sts2.Core.Models; -namespace TestMod.TestModCode.Hooks; +namespace BaseLib.Hooks; public interface IVitalityAmountModifier { diff --git a/Hooks/IVitalityHooks.cs b/Hooks/IVitalityHooks.cs index 2f956876..69682bb2 100644 --- a/Hooks/IVitalityHooks.cs +++ b/Hooks/IVitalityHooks.cs @@ -2,7 +2,7 @@ using MegaCrit.Sts2.Core.Entities.Creatures; using MegaCrit.Sts2.Core.Models; -namespace TestMod.TestModCode.Hooks; +namespace BaseLib.Hooks; public interface IVitalityHooks { diff --git a/Patches/VitalityPatch.cs b/Patches/Features/VitalityPatch.cs similarity index 93% rename from Patches/VitalityPatch.cs rename to Patches/Features/VitalityPatch.cs index 8aae1931..cd01c146 100644 --- a/Patches/VitalityPatch.cs +++ b/Patches/Features/VitalityPatch.cs @@ -6,21 +6,14 @@ using HarmonyLib; using MegaCrit.Sts2.addons.mega_text; using MegaCrit.Sts2.Core.Combat; -using MegaCrit.Sts2.Core.Commands; -using MegaCrit.Sts2.Core.Entities.Cards; using MegaCrit.Sts2.Core.Entities.Creatures; -using MegaCrit.Sts2.Core.Entities.Players; using MegaCrit.Sts2.Core.Helpers; -using MegaCrit.Sts2.Core.Models; using MegaCrit.Sts2.Core.Nodes.Combat; using MegaCrit.Sts2.Core.Nodes.Multiplayer; -using MegaCrit.Sts2.Core.Runs; using MegaCrit.Sts2.Core.Saves; using MegaCrit.Sts2.Core.Settings; -using TestMod.TestModCode.Hooks; -using TestMod.TestModCode.Ui; -namespace TestMod.TestModCode.Patches; +namespace BaseLib.Patches.Features; public static class VitalityPatch { @@ -61,7 +54,7 @@ public static int GetVitality(Creature creature) [HarmonyPatch("LoseHpInternal")] public class HpInterceptPatch { - private static int temporaryHp; + // private static int temporaryHp; static IEnumerable Transpiler(IEnumerable instructions) { @@ -69,7 +62,7 @@ static IEnumerable Transpiler(IEnumerable inst MethodInfo getCurrentHpInfo = AccessTools.PropertyGetter(typeof(Creature), nameof(Creature.CurrentHp)); MethodInfo tempHp = AccessTools.Method(typeof(HpInterceptPatch), nameof(TemporaryHpHandler)); - MethodInfo unblockedOverride = AccessTools.Method(typeof(HpInterceptPatch), nameof(UnblockedDamageOverride)); + // MethodInfo unblockedOverride = AccessTools.Method(typeof(HpInterceptPatch), nameof(UnblockedDamageOverride)); codeMatcher.MatchStartForward( new CodeMatch(OpCodes.Ldarg_0), @@ -84,7 +77,7 @@ static IEnumerable Transpiler(IEnumerable inst new CodeInstruction(OpCodes.Stloc_2) ); - codeMatcher.MatchStartForward( + /*codeMatcher.MatchStartForward( new CodeMatch(OpCodes.Ldloc_1), new CodeMatch(OpCodes.Ldarg_0), new CodeMatch(OpCodes.Call, getCurrentHpInfo), @@ -94,14 +87,14 @@ static IEnumerable Transpiler(IEnumerable inst .InsertAfterAndAdvance( new CodeMatch(OpCodes.Ldarg_0), new CodeInstruction(OpCodes.Call, unblockedOverride) - ); + );*/ return codeMatcher.InstructionEnumeration(); } private static int TemporaryHpHandler(Creature c, int num) { - int tempHp = temporaryHp = (int) VitalityField.GetVitality(c); + int tempHp = (int) VitalityField.GetVitality(c); if (num >= tempHp) { num -= tempHp; @@ -115,6 +108,7 @@ private static int TemporaryHpHandler(Creature c, int num) return num; } + /* Code for making Vitality trigger HP Loss effects. private static int UnblockedDamageOverride(int unblockedDamage, Creature c) { if (TestModConfig.TriggerHpLoss) @@ -123,7 +117,7 @@ private static int UnblockedDamageOverride(int unblockedDamage, Creature c) return unblockedDamage + temporaryHp; } return unblockedDamage; - } + }*/ } [HarmonyPatch(typeof(NHealthBar))] @@ -174,7 +168,7 @@ public static void SelfModulateOutline(Creature ____creature, MegaLabel ____hpLa } } - // Code courtesy of CanYou with mild alterations. + // Code courtesy of CanYou with alterations. [HarmonyPatch] public static class VitalityHealthBarPatch { @@ -251,6 +245,8 @@ public static void SetUpVitalityOffset(NHealthBar __instance) __instance._blockContainer.Position.Y); } } + + // Enables a Vitality "Overflow" on the bar where if it loops over it changes colors. Subject to change. private static readonly Color[] HbColors = [Colors.Gold, Colors.Green, Colors.MediumAquamarine, Colors.MediumVioletRed]; diff --git a/Patches/VitalityPatch.cs.uid b/Patches/Features/VitalityPatch.cs.uid similarity index 100% rename from Patches/VitalityPatch.cs.uid rename to Patches/Features/VitalityPatch.cs.uid From bfd4b55812f9b702d5497d3baeba71e85c38e461 Mon Sep 17 00:00:00 2001 From: SoytheProton Date: Wed, 15 Apr 2026 18:04:37 -0700 Subject: [PATCH 3/4] Delete Commands/VitalityCmd.cs.uid --- Commands/VitalityCmd.cs.uid | 1 - 1 file changed, 1 deletion(-) delete mode 100644 Commands/VitalityCmd.cs.uid diff --git a/Commands/VitalityCmd.cs.uid b/Commands/VitalityCmd.cs.uid deleted file mode 100644 index ceb1b8c7..00000000 --- a/Commands/VitalityCmd.cs.uid +++ /dev/null @@ -1 +0,0 @@ -uid://dq3c3rm1cl23x From 0bf68e3f8b257202a368d18007ec181342566e6a Mon Sep 17 00:00:00 2001 From: SoytheProton Date: Wed, 15 Apr 2026 18:05:11 -0700 Subject: [PATCH 4/4] Delete Patches/Features/VitalityPatch.cs.uid --- Patches/Features/VitalityPatch.cs.uid | 1 - 1 file changed, 1 deletion(-) delete mode 100644 Patches/Features/VitalityPatch.cs.uid diff --git a/Patches/Features/VitalityPatch.cs.uid b/Patches/Features/VitalityPatch.cs.uid deleted file mode 100644 index 7e4052d8..00000000 --- a/Patches/Features/VitalityPatch.cs.uid +++ /dev/null @@ -1 +0,0 @@ -uid://c3yt8v41pfnul