1. Welcome to the official Starbound Mod repository, Guest! Not sure how to install your mods? Check out the installation guide or check out the modding help thread for more guides.
    Outdated Mods have been moved to their own category! If you update your mod please let a moderator know so we can move it back to the active section.
    Dismiss Notice

Regeneration 1.1.3

Provides configurable health and stamina regeneration.

  1. v1.1.0

    Hammurabi
    I've added the ability to have Exhaustion affect your stamina regen rate and stamina idle time before regen, and more importantly, I've added HealthRange and StaminaRange arrays that allow you to custom-define modifiers to the regen rates, running regen rates, and idle times based on whether the current health or stamina is within a defined range. The effects of all applicable range structures will be applied to the regen rates and idle times.

    Additionally, I've tinkered around with the standard settings. Using the HealthRanges and StaminaRanges, I've made it so that health will only recover up to 50%, and stamina will only recover up to 75%, and both of them will recover more slowly (especially when running) and have longer idle times when they are very low. Together with the new Exhaustion modifiers, this means that completely draining your stamina would require slightly more than 9 in-game hours without exertion or running to recover your stamina as far as possible using only regeneration; given that this no longer feels very cheaty, I also made exhaustion recovery enabled by default, with a requirement just shy of the 75% maximum stamina recovery.

    The ranges have the following data:
    * Min(Health/Stamina)Absolute: The minimum amount of health or stamina for the range modifiers to be applied, stated in points of health or stamina (e.g., 50).
    * Min(Health/Stamina)Percentage: The minimum amount of health or stamina for the range modifiers to be applied, stated in percentage of maximum in 0 to 1 format (e.g., 0.5).
    * Max(Health/Stamina)Absolute: The maximum amount of health or stamina for the range modifiers to be applied, stated in points of health or stamina (e.g., 250). If left at zero, no maximum is applied.
    * Max(Health/Stamina)Percentage: The maximum amount of health or stamina for the range modifiers to be applied, stated in percentage of maximum in 0 to 1 format (e.g., 0.9). If left at zero, no maximum is applied.
    * RegenRateMultiplier: The value that the regen rate should be multiplied by if the current health/stamina is within this range.
    * RunRateMulitiplier: The value that the running regen rate percentage should be multiplied by if the current health/stamina is within this range.
    * IdleTimeMultiplier: The value that the idle time should be multiplied by if the current health/stamina is within this range.

    If you are updating from the previous version, you can delete your config.json file to have one with the new defaults created at game start.

    New source code:
    Code:
    using System;
    using StardewModdingAPI;
    using StardewModdingAPI.Events;
    using StardewValley;
    
    namespace Regeneration {
    
       public class ModConfig {
         public float healthRegenAbsolutePerSecond { get; set; }
         public float healthRegenPercentagePerSecond { get; set; }
         public double healthIdleSeconds { get; set; }
         public float regenHealthWhileRunningRate { get; set; }
         public bool loseFractionalHealthWhenInjured { get; set; }
    
         public HealthRange[] HealthRanges { get; set; }
    
         public float staminaRegenAbsolutePerSecond { get; set; }
         public float staminaRegenPercentagePerSecond { get; set; }
         public double staminaIdleSeconds { get; set; }
         public float regenStaminaWhileRunningRate { get; set; }
    
         public StaminaRange[] StaminaRanges { get; set; }
    
         public float criticalStaminaAbsolute { get; set; }
         public float criticalStaminaPercentage { get; set; }
         public float criticalStaminaRegenRate { get; set; }
         public double criticalStaminaIdleMultiplier { get; set; }
    
         public float highStaminaAbsolute { get; set; }
         public float highStaminaPercentage { get; set; }
         public float highStaminaRegenRate { get; set; }
         public double highStaminaIdleMultiplier { get; set; }
    
         public float exhaustionStaminaRegenRage { get; set; }
         public double exhaustionStaminaIdleMultiplier { get; set; }
    
         public bool removeExhaustion { get; set; }
         public float removeExhaustionStaminaPercentage { get; set; }
    
    
         public ModConfig() {
           healthRegenAbsolutePerSecond = 0.1f;
           healthRegenPercentagePerSecond = 0.0f;
           healthIdleSeconds = 20.0;
           regenHealthWhileRunningRate = 0.4f;
           loseFractionalHealthWhenInjured = false;
    
           HealthRanges = new HealthRange[] {
             new HealthRange() { MaxHealthPercentage = (0.25f), RegenRateMultiplier = 0.5f, IdleTimeMultiplier = (3f/2f), RunRateMulitiplier = 0.5f },
             new HealthRange() { MinHealthPercentage = (0.5f), RegenRateMultiplier = 0 }
           };
    
           staminaRegenAbsolutePerSecond = 0.0f;
           staminaRegenPercentagePerSecond = (1.0f / 270.0f); // Recover 1 stamina per second at game start, but increase as max stamina increases.
           staminaIdleSeconds = 10.0;
           regenStaminaWhileRunningRate = 0.25f;
    
           StaminaRanges = new StaminaRange[] {
             new StaminaRange() { MaxStaminaPercentage = 0.5f, RegenRateMultiplier = 0.8f, IdleTimeMultiplier = 1.25, RunRateMulitiplier = 0.75f },
             new StaminaRange() { MaxStaminaPercentage = 0.25f, RegenRateMultiplier = 0.75f, IdleTimeMultiplier = 1.4, RunRateMulitiplier = (2f/3f) },
             new StaminaRange() { MinStaminaPercentage = 0.75f, RegenRateMultiplier = 0 }
           };
    
           exhaustionStaminaRegenRage = 0.75f;
           exhaustionStaminaIdleMultiplier = 2;
    
           removeExhaustion = true;
           removeExhaustionStaminaPercentage = 0.745f;
         }
       }
    
       public struct HealthRange {
         public int MinHealthAbsolute { get; set; }
         public float MinHealthPercentage { get; set; }
         public float MaxHealthAbsolute { get; set; }
         public float MaxHealthPercentage { get; set; }
         public float RegenRateMultiplier { get; set; }
         public float RunRateMulitiplier { get; set; }
         public double IdleTimeMultiplier { get; set; }
    
         public bool WithinRange(int Health, int MaxHealth) {
           if ((MinHealthAbsolute == 0 || Health >= MinHealthAbsolute) && (MinHealthPercentage == 0 || Health >= (MinHealthPercentage * MaxHealth))) {
             if ((MaxHealthAbsolute == 0 || Health < MaxHealthAbsolute) && (MaxHealthPercentage == 0 || Health < (MaxHealthPercentage * MaxHealth))) {
               return true;
             }
           }
           return false;
         }
    
         public void Validate() {
           if (MinHealthAbsolute < 0) { MinHealthAbsolute = 0; }
           if (MinHealthPercentage < 0) { MinHealthPercentage = 0; }
           if (MinHealthPercentage > 1) { MinHealthPercentage = 1; }
           if (MaxHealthAbsolute < 0) { MaxHealthAbsolute = 0; }
           if (MaxHealthPercentage < 0) { MaxHealthPercentage = 0; }
           if (MaxHealthPercentage > 1) { MaxHealthPercentage = 1; }
           if (RunRateMulitiplier < 0) { RunRateMulitiplier = 0; }
           if (IdleTimeMultiplier < 0) { IdleTimeMultiplier = 0; }
         }
       }
    
       public struct StaminaRange {
         public float MinStaminaAbsolute { get; set; }
         public float MinStaminaPercentage { get; set; }
         public float MaxStaminaAbsolute { get; set; }
         public float MaxStaminaPercentage { get; set; }
         public float RegenRateMultiplier { get; set; }
         public float RunRateMulitiplier { get; set; }
         public double IdleTimeMultiplier { get; set; }
    
         public bool WithinRange(float Stamina, float MaxStamina) {
           if ((MinStaminaAbsolute == 0 || Stamina >= MinStaminaAbsolute) && (MinStaminaPercentage == 0 || Stamina >= (MinStaminaPercentage * MaxStamina))) {
             if ((MaxStaminaAbsolute == 0 || Stamina < MaxStaminaAbsolute) && (MaxStaminaPercentage == 0 || Stamina < (MaxStaminaPercentage * MaxStamina))) {
               return true;
             }
           }
           return false;
         }
    
         public void Validate() {
           if (MinStaminaAbsolute < 0) { MinStaminaAbsolute = 0; }
           if (MinStaminaPercentage < 0) { MinStaminaPercentage = 0; }
           if (MinStaminaPercentage > 1) { MinStaminaPercentage = 1; }
           if (MaxStaminaAbsolute < 0) { MaxStaminaAbsolute = 0; }
           if (MaxStaminaPercentage < 0) { MaxStaminaPercentage = 0; }
           if (MaxStaminaPercentage > 1) { MaxStaminaPercentage = 1; }
           if (RunRateMulitiplier < 0) { RunRateMulitiplier = 0; }
           if (IdleTimeMultiplier < 0) { IdleTimeMultiplier = 0; }
         }
       }
    
       public class Regeneration : Mod {
         // Health values
         int lastHealth;
         float healthAccum;  // Accumulated health regenerated while running.
         double healthRegenStartTime;  // The time when health regeneration should next begin. Updated after taking damage.
    
         // Stamina values
         float lastStamina;  // Last recorded player stamina value.
         double staminaRegenStartTime;       // The time when stamina regeneration should next begin. Updated after losing stamina.
    
         // Control values
         double lastTickTime;  // The time at the last tick processed.
         bool playerIsRunning;  // Whether the player has run since the preceeding update tick.
    
         Farmer Player;  // Our player.
         ModConfig myConfig;  // Config data.
    
         public override void Entry(IModHelper helper) {
           myConfig = helper.ReadConfig<ModConfig>();
           ValidateConfig();
           helper.WriteConfig<ModConfig>(myConfig);
    
           healthAccum = 0.0f;
           lastHealth = 0;
           lastStamina = 0;
           lastTickTime = 0.0;
           playerIsRunning = false;
    
           GameEvents.UpdateTick += OnUpdateTick;
         }
    
         public void ValidateConfig() {
           // Percentage values can't be greater than 100%.
           if (myConfig.healthRegenPercentagePerSecond > 1.0) { myConfig.healthRegenPercentagePerSecond = 1.0f; }
           if (myConfig.staminaRegenPercentagePerSecond > 1.0) { myConfig.staminaRegenPercentagePerSecond = 1.0f; }
           if (myConfig.removeExhaustionStaminaPercentage > 1.0) { myConfig.removeExhaustionStaminaPercentage = 1.0f; }
    
           // Remove Exhaustion percentage shouldn't be less than 0%.
           if (myConfig.removeExhaustionStaminaPercentage < 0.0) { myConfig.removeExhaustionStaminaPercentage = 0.0f; }
    
           // Max running rate can't be higher than base.
           if (myConfig.regenHealthWhileRunningRate > 1.0) { myConfig.regenHealthWhileRunningRate = 1.0f; }
           if (myConfig.regenStaminaWhileRunningRate > 1.0) { myConfig.regenStaminaWhileRunningRate = 1.0f; }
    
           // Idle times can't be negative.
           if (myConfig.healthIdleSeconds < 0) { myConfig.healthIdleSeconds = 0; }
           if (myConfig.staminaIdleSeconds < 0) { myConfig.staminaIdleSeconds = 0; }
    
           foreach (HealthRange h in myConfig.HealthRanges) { h.Validate(); }
           foreach (StaminaRange s in myConfig.StaminaRanges) { s.Validate(); }
         }
    
         private void OnUpdateTick(object sender, EventArgs e) {
           Player = Game1.player;
    
           // If game is running, and time can pass (i.e., are not in an event/cutscene/menu/festival)
           if (Game1.hasLoadedGame && Game1.shouldTimePass()) {
             // Make sure we know exactly how much time has elapsed
             double currentTime = Game1.currentGameTime.TotalGameTime.TotalSeconds;
             float timeElapsed = (float) (currentTime - lastTickTime);
             lastTickTime = currentTime;
    
             // Check for player injury. If player has been injured since last tick, recalculate the time when health regeneration should start.
             if (Player.health < lastHealth)
             {
               double IdleTime = myConfig.healthIdleSeconds;
               foreach (HealthRange h in myConfig.HealthRanges) {
                 if (h.WithinRange(Player.health, Player.maxHealth)) {
                   IdleTime *= h.IdleTimeMultiplier;
                 }
               }
               healthRegenStartTime = currentTime + IdleTime;
               if (myConfig.loseFractionalHealthWhenInjured) { healthAccum = 0; }
             }
    
             // Check for player exertion. If player has used stamina since last tick, recalculate the time when stamina regeneration should start.
             if (Player.stamina < lastStamina) {
               double IdleTime = myConfig.staminaIdleSeconds;
               foreach (StaminaRange s in myConfig.StaminaRanges) {
                 if (s.WithinRange(Player.stamina, Player.maxStamina)) {
                   IdleTime *= s.IdleTimeMultiplier;
                 }
               }
               staminaRegenStartTime = currentTime + IdleTime;
             }
    
             /* Determine whether movement status will block normal regeneration: If player is...
      * 1. running, and
      * 2. has moved recently, and
      * 3. is not on horseback, then
      * movement blocks normal regen and the running rate prevails. (If the running rate is 0, there is no regeneration.)
      */
             if (Player.running && Player.movedDuringLastTick() && !Player.isRidingHorse()) { playerIsRunning = true; }
             else { playerIsRunning = false; }
    
             // Process health regeneration.
             if (healthRegenStartTime <= currentTime && Player.health < Player.maxHealth) {
               float absRegen = myConfig.healthRegenAbsolutePerSecond * timeElapsed;
               float percRegen = myConfig.healthRegenPercentagePerSecond * Player.maxHealth * timeElapsed;
               float runningModifier = playerIsRunning ? myConfig.regenHealthWhileRunningRate : 1;
               float regenRangeModifier = 1;
    
               foreach (HealthRange h in myConfig.HealthRanges) {
                 if (h.WithinRange(Player.health, Player.maxHealth)) {
                   runningModifier *= playerIsRunning ? h.RunRateMulitiplier : 1;
                   regenRangeModifier *= h.RegenRateMultiplier;
                 }
               }
    
               healthAccum += (absRegen + percRegen) * runningModifier * regenRangeModifier;
               if (healthAccum >= 1) {
                 Player.health += 1;
                 healthAccum -= 1;
               }
             }
    
             // Process stamina regeneration.
             if (staminaRegenStartTime <= currentTime && Player.stamina < Player.maxStamina) {
               float absRegen = myConfig.staminaRegenAbsolutePerSecond * timeElapsed;
               float percRegen = myConfig.staminaRegenPercentagePerSecond * Player.maxStamina * timeElapsed;
               float runningModifier = playerIsRunning ? myConfig.regenStaminaWhileRunningRate : 1;
               float regenRangeModifier = 1;
    
               foreach (StaminaRange s in myConfig.StaminaRanges) {
                 if (s.WithinRange(Player.stamina, Player.maxStamina)) {
                   runningModifier *= playerIsRunning ? s.RunRateMulitiplier : 1;
                   regenRangeModifier *= s.RegenRateMultiplier;
                 }
               }
    
               Player.stamina += (absRegen + percRegen) * runningModifier * regenRangeModifier;
    
               // Final sanity check
               if (Player.stamina > Player.maxStamina) { Player.stamina = Player.maxStamina; }
             }
    
             // Updated stored health/stamina values.
             lastHealth = Player.health;
             lastStamina = Player.stamina;
    
             // Remove exhaustion if the player is sufficiently recovered.
             if (myConfig.removeExhaustion && (Player.stamina / Player.maxStamina) > myConfig.removeExhaustionStaminaPercentage) {
               Player.exhausted = false;
             }
           }
         }
       }
    }
Return to update list...