using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using BetterStamina;
using HarmonyLib;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("BetterStamina")]
[assembly: AssemblyDescription("Rebalances stamina system to make it more forgiving and improve the combat flow.")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("BetterStamina")]
[assembly: AssemblyCopyright("Copyright © bakaspaceman 2021-2023")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("c78b697c-0617-40b5-b559-88d31b5ed1a8")]
[assembly: AssemblyFileVersion("2.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")]
[assembly: AssemblyVersion("2.0.0.0")]
internal static class StatusEffectPatches
{
private static void UpdateStatusEffect(StatusEffect se, bool onPlayer = false)
{
string text = (onPlayer ? " on local player " : " in ObjectDB ");
SE_Stats val = (SE_Stats)(object)((se is SE_Stats) ? se : null);
if (val != null)
{
switch (se.m_name)
{
case "$se_cold_name":
BepInPluginTemplate.DebugLog($"[{Localization.instance.Localize(se.m_name)}] Updating m_staminaRegenMultiplier{text}from {val.m_staminaRegenMultiplier} to {BetterStaminaPlugin.coldStaminaRegenMultiplier.Value}");
val.m_staminaRegenMultiplier = BetterStaminaPlugin.coldStaminaRegenMultiplier.Value;
break;
case "$se_wet_name":
BepInPluginTemplate.DebugLog($"[{Localization.instance.Localize(se.m_name)}] Updating m_staminaRegenMultiplier{text}from {val.m_staminaRegenMultiplier} to {BetterStaminaPlugin.wetStaminaRegenMultiplier.Value}");
val.m_staminaRegenMultiplier = BetterStaminaPlugin.wetStaminaRegenMultiplier.Value;
break;
case "$se_rested_name":
{
BepInPluginTemplate.DebugLog($"[{Localization.instance.Localize(se.m_name)}] Updating m_staminaRegenMultiplier{text}from {val.m_staminaRegenMultiplier} to {BetterStaminaPlugin.restedStaminaRegenMultiplier.Value}");
val.m_staminaRegenMultiplier = BetterStaminaPlugin.restedStaminaRegenMultiplier.Value;
SE_Rested val2 = (SE_Rested)(object)((se is SE_Rested) ? se : null);
BepInPluginTemplate.DebugLog($"[{Localization.instance.Localize(se.m_name)}] Updating m_TTLPerComfortLevel{text}from {val2.m_TTLPerComfortLevel} to {BetterStaminaPlugin.restedDurationPerComfortLvl.Value}");
val2.m_TTLPerComfortLevel = BetterStaminaPlugin.restedDurationPerComfortLvl.Value;
break;
}
}
}
}
public static void UpdateEffects(ObjectDB __instance)
{
foreach (StatusEffect statusEffect in __instance.m_StatusEffects)
{
if (statusEffect is SE_Stats)
{
UpdateStatusEffect(statusEffect);
}
}
}
[HarmonyPatch(typeof(ObjectDB), "Awake")]
[HarmonyPostfix]
public static void ObjectDBAwake_PostFix(ObjectDB __instance)
{
UpdateEffects(__instance);
}
[HarmonyPatch(typeof(Player), "SetLocalPlayer")]
[HarmonyPostfix]
public static void SetLocalPlayerPostFix(Player __instance, SEMan ___m_seman)
{
if (!((Object)(object)__instance != (Object)null) || ___m_seman == null)
{
return;
}
foreach (StatusEffect item in (List<StatusEffect>)BetterStaminaPlugin.statusEffectsField.GetValue(___m_seman))
{
UpdateStatusEffect(item, onPlayer: true);
}
}
}
internal static class SkillPatches
{
private static float defaultBlockStaminaDrain;
private static float defaultSneakStaminaDrain;
private static float defaultStaminaUsage;
private static float defaultJumpStaminaUsage;
[HarmonyPatch(typeof(Player), "OnSneaking")]
[HarmonyPrefix]
private static void OnSneaking_Prefix(Player __instance, Skills ___m_skills)
{
defaultSneakStaminaDrain = __instance.m_sneakStaminaDrain;
if (!((Object)(object)Player.m_localPlayer == (Object)null) && !((Object)(object)Player.m_localPlayer != (Object)(object)__instance))
{
float num = EasingFunctions.GetEasingFunction(EasingFunctions.Ease.EaseOutSine)(1f, BetterStaminaPlugin.sneakMaxSkillStaminaCost.Value, ___m_skills.GetSkillFactor((SkillType)100));
__instance.m_sneakStaminaDrain *= num;
if (BetterStaminaPlugin.enableSkillStaminaLogging != null && BetterStaminaPlugin.enableSkillStaminaLogging.Value)
{
BepInPluginTemplate.DebugLog($"OnSneaking: Usage change: {defaultSneakStaminaDrain} - {__instance.m_sneakStaminaDrain}; Mathf.Lerp: {Mathf.Lerp(1f, BetterStaminaPlugin.sneakMaxSkillStaminaCost.Value, ___m_skills.GetSkillFactor((SkillType)100))}; Custom: {num}; skill: {___m_skills.GetSkillFactor((SkillType)100)};");
}
}
}
[HarmonyPatch(typeof(Player), "OnSneaking")]
[HarmonyPostfix]
private static void OnSneaking_PostFix(Player __instance)
{
if (defaultSneakStaminaDrain != 0f)
{
__instance.m_sneakStaminaDrain = defaultSneakStaminaDrain;
}
}
[HarmonyPatch(typeof(Player), "OnJump")]
[HarmonyPrefix]
private static void OnJump_Prefix(Player __instance, Skills ___m_skills)
{
defaultJumpStaminaUsage = ((Character)__instance).m_jumpStaminaUsage;
if (!((Object)(object)Player.m_localPlayer == (Object)null) && !((Object)(object)Player.m_localPlayer != (Object)(object)__instance))
{
float num = EasingFunctions.GetEasingFunction(EasingFunctions.Ease.EaseOutSine)(1f, BetterStaminaPlugin.jumpMaxSkillStaminaCost.Value, ___m_skills.GetSkillFactor((SkillType)100));
((Character)__instance).m_jumpStaminaUsage = ((Character)__instance).m_jumpStaminaUsage * num;
if (BetterStaminaPlugin.enableSkillStaminaLogging != null && BetterStaminaPlugin.enableSkillStaminaLogging.Value)
{
BepInPluginTemplate.DebugLog($"OnJump: Usage change: {defaultJumpStaminaUsage} - {((Character)__instance).m_jumpStaminaUsage}; Mathf.Lerp: {Mathf.Lerp(1f, BetterStaminaPlugin.jumpMaxSkillStaminaCost.Value, ___m_skills.GetSkillFactor((SkillType)100))}; Custom: {num}; skill: {___m_skills.GetSkillFactor((SkillType)100)};");
}
}
}
[HarmonyPatch(typeof(Player), "OnJump")]
[HarmonyPostfix]
private static void OnJump_PostFix(Player __instance)
{
if (defaultJumpStaminaUsage != 0f)
{
((Character)__instance).m_jumpStaminaUsage = defaultJumpStaminaUsage;
}
}
[HarmonyPatch(typeof(Player), "UpdateDodge")]
[HarmonyPrefix]
private static void UpdateDodge_Prefix(Player __instance, Skills ___m_skills, float ___m_queuedDodgeTimer)
{
defaultStaminaUsage = __instance.m_dodgeStaminaUsage;
if (!((Object)(object)Player.m_localPlayer == (Object)null) && !((Object)(object)Player.m_localPlayer != (Object)(object)__instance))
{
float num = EasingFunctions.GetEasingFunction(EasingFunctions.Ease.EaseOutSine)(1f, BetterStaminaPlugin.dodgeMaxSkillStaminaCost.Value, ___m_skills.GetSkillFactor((SkillType)100));
__instance.m_dodgeStaminaUsage *= num;
if (BetterStaminaPlugin.enableSkillStaminaLogging != null && BetterStaminaPlugin.enableSkillStaminaLogging.Value && (double)___m_queuedDodgeTimer > 0.0 && ((Character)__instance).IsOnGround() && !((Character)__instance).IsDead() && !((Character)__instance).InAttack() && !((Character)__instance).IsEncumbered() && !((Character)__instance).InDodge())
{
BepInPluginTemplate.DebugLog($"UpdateDoge: Usage change: {defaultStaminaUsage} - {__instance.m_dodgeStaminaUsage}; Mathf.Lerp: {Mathf.Lerp(1f, BetterStaminaPlugin.dodgeMaxSkillStaminaCost.Value, ___m_skills.GetSkillFactor((SkillType)100))}; Custom: {num}; skill: {___m_skills.GetSkillFactor((SkillType)100)};");
}
}
}
[HarmonyPatch(typeof(Player), "UpdateDodge")]
[HarmonyPostfix]
private static void UpdateDodge_PostFix(Player __instance)
{
if (defaultStaminaUsage != 0f)
{
__instance.m_dodgeStaminaUsage = defaultStaminaUsage;
}
}
[HarmonyPatch(typeof(Humanoid), "BlockAttack")]
[HarmonyPrefix]
private static void BlockAttack_Prefix(Humanoid __instance)
{
//IL_003a: Unknown result type (might be due to invalid IL or missing references)
//IL_0040: Expected O, but got Unknown
if (!((Object)(object)Player.m_localPlayer != (Object)null) || !((Object)(object)Player.m_localPlayer == (Object)(object)__instance))
{
return;
}
defaultBlockStaminaDrain = __instance.m_blockStaminaDrain;
Skills val = (Skills)BetterStaminaPlugin.playerSkillsField.GetValue(Player.m_localPlayer);
if (!((Object)(object)val == (Object)null))
{
float num = EasingFunctions.GetEasingFunction(EasingFunctions.Ease.EaseOutSine)(1f, BetterStaminaPlugin.blockMaxSkillStaminaCost.Value, val.GetSkillFactor((SkillType)6));
__instance.m_blockStaminaDrain *= num;
if (BetterStaminaPlugin.enableSkillStaminaLogging != null && BetterStaminaPlugin.enableSkillStaminaLogging.Value)
{
BepInPluginTemplate.DebugLog($"BlockAttack: Usage change: {defaultBlockStaminaDrain} - {__instance.m_blockStaminaDrain}; Mathf.Lerp: {Mathf.Lerp(1f, BetterStaminaPlugin.blockMaxSkillStaminaCost.Value, val.GetSkillFactor((SkillType)6))}; Custom: {num}; skill: {val.GetSkillFactor((SkillType)6)};");
}
}
}
[HarmonyPatch(typeof(Humanoid), "BlockAttack")]
[HarmonyPostfix]
private static void BlockAttack_PostFix(Player __instance)
{
if (defaultBlockStaminaDrain != 0f)
{
((Humanoid)__instance).m_blockStaminaDrain = defaultBlockStaminaDrain;
}
}
[HarmonyPatch(typeof(Attack), "GetAttackStamina")]
[HarmonyPrefix]
private static bool GetStaminaUsage_Prefix(Attack __instance, Humanoid ___m_character, float ___m_attackStamina, ItemData ___m_weapon, ref float __result)
{
//IL_0053: Unknown result type (might be due to invalid IL or missing references)
//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
//IL_0104: Unknown result type (might be due to invalid IL or missing references)
//IL_011c: Unknown result type (might be due to invalid IL or missing references)
if ((Object)(object)Player.m_localPlayer == (Object)null || (Object)(object)Player.m_localPlayer != (Object)(object)___m_character)
{
return true;
}
if ((double)___m_attackStamina <= 0.0)
{
__result = 0f;
return false;
}
double num = ___m_attackStamina;
float num2 = EasingFunctions.GetEasingFunction(EasingFunctions.Ease.EaseOutSine)(1f, BetterStaminaPlugin.weaponMaxSkillAttackStaminaCost.Value, ((Character)___m_character).GetSkillFactor(___m_weapon.m_shared.m_skillType));
__result = (float)(num * (double)num2);
if (BetterStaminaPlugin.enableSkillStaminaLogging != null && BetterStaminaPlugin.enableSkillStaminaLogging.Value && new StackFrame(2).GetMethod().Name.Contains("Update"))
{
float num3 = (float)(num * (double)(1f - BetterStaminaPlugin.weaponMaxSkillAttackStaminaCost.Value) * (double)((Character)___m_character).GetSkillFactor(___m_weapon.m_shared.m_skillType));
float num4 = (float)(num - (double)num3);
BepInPluginTemplate.DebugLog($"Attack.GetStaminaUsage(): Cost - {__result}; Original: {num4}; Custom: {__result}; skill: {((Character)___m_character).GetSkillFactor(___m_weapon.m_shared.m_skillType)}({___m_weapon.m_shared.m_skillType});");
}
return false;
}
[HarmonyPatch(typeof(ItemData), "GetDrawStaminaDrain")]
[HarmonyPostfix]
private static void GetDrawStaminaDrain_Postfix(SharedData ___m_shared, ref float __result)
{
//IL_0028: Unknown result type (might be due to invalid IL or missing references)
//IL_0089: Unknown result type (might be due to invalid IL or missing references)
//IL_00af: Unknown result type (might be due to invalid IL or missing references)
float drawStaminaDrain = ___m_shared.m_attack.m_drawStaminaDrain;
float num = EasingFunctions.GetEasingFunction(EasingFunctions.Ease.EaseOutSine)(1f, BetterStaminaPlugin.bowMaxSkillHoldStaminaCost.Value, ((Character)Player.m_localPlayer).GetSkillFactor(___m_shared.m_skillType));
float num2 = (__result = drawStaminaDrain * num);
if (BetterStaminaPlugin.enableSkillStaminaLogging != null && BetterStaminaPlugin.enableSkillStaminaLogging.Value)
{
BepInPluginTemplate.DebugLog($"BowHoldStamina: Usage change: {drawStaminaDrain} - {num2}; Mathf.Lerp: {Mathf.Lerp(1f, BetterStaminaPlugin.bowMaxSkillHoldStaminaCost.Value, ((Character)Player.m_localPlayer).GetSkillFactor(___m_shared.m_skillType))}; Custom: {num}; skill: {((Character)Player.m_localPlayer).GetSkillFactor(___m_shared.m_skillType)};");
}
}
public static float GetRunStaminaSkillFactor(float drainMax, float drainMin, float skillFactor, Player playerInst)
{
drainMin = BetterStaminaPlugin.runMaxSkillStaminaCost.Value;
if ((Object)(object)Player.m_localPlayer != (Object)null && (Object)(object)Player.m_localPlayer == (Object)(object)playerInst)
{
if (((Humanoid)playerInst).GetCurrentWeapon() != null && !((object)((Humanoid)playerInst).GetCurrentWeapon()).Equals((object?)((Humanoid)playerInst).m_unarmedWeapon.m_itemData))
{
drainMin = BetterStaminaPlugin.runWithWeapMaxSkillStaminaCost.Value;
}
float num = EasingFunctions.GetEasingFunction(EasingFunctions.Ease.EaseOutSine)(drainMax, drainMin, skillFactor);
if (BetterStaminaPlugin.enableSkillStaminaLogging != null && BetterStaminaPlugin.enableSkillStaminaLogging.Value)
{
BepInPluginTemplate.DebugLog($"RunStamina: Skill factor change: {Mathf.Lerp(drainMax, drainMin, skillFactor)} - {num}");
}
return num;
}
return Mathf.Lerp(drainMax, drainMin, skillFactor);
}
[HarmonyPatch(typeof(Player), "CheckRun")]
[HarmonyTranspiler]
private static IEnumerable<CodeInstruction> CheckRun_Transpiler(IEnumerable<CodeInstruction> instructions)
{
//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
//IL_00e6: Expected O, but got Unknown
//IL_013c: Unknown result type (might be due to invalid IL or missing references)
//IL_0146: Expected O, but got Unknown
BepInPluginTemplate.DebugTranspilerLog("######## CheckRun_Transpiler START ########");
List<CodeInstruction> list = new List<CodeInstruction>(instructions);
for (int i = 0; i < list.Count; i++)
{
CodeInstruction val = list[i];
BepInPluginTemplate.DebugTranspilerLog($"{i} {val}");
if (val.opcode == OpCodes.Call && ((object)val).ToString().Contains("Mathf::Lerp"))
{
int num = i;
BepInPluginTemplate.DebugTranspilerLog($">>> Deleting instruction {num} {((object)list[num]).ToString()}:");
list.RemoveAt(i);
BepInPluginTemplate.DebugTranspilerLog($">>> Inserting instruction at {num}:");
BepInPluginTemplate.DebugTranspilerLog("Old: " + ((object)list[num]).ToString());
list.Insert(num, new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(typeof(SkillPatches), "GetRunStaminaSkillFactor", (Type[])null, (Type[])null)));
BepInPluginTemplate.DebugTranspilerLog("New: " + ((object)list[num]).ToString());
BepInPluginTemplate.DebugTranspilerLog($">>> Inserting instruction at {num}:");
BepInPluginTemplate.DebugTranspilerLog("Old: " + ((object)list[num]).ToString());
list.Insert(num, new CodeInstruction(OpCodes.Ldarg_0, (object)null));
BepInPluginTemplate.DebugTranspilerLog("New: " + ((object)list[num]).ToString());
break;
}
}
BepInPluginTemplate.DebugTranspilerLog("");
BepInPluginTemplate.DebugTranspilerLog("#############################################################");
BepInPluginTemplate.DebugTranspilerLog($"######## MODIFIED INSTRUCTIONS - {list.Count} ########");
BepInPluginTemplate.DebugTranspilerLog("#############################################################");
BepInPluginTemplate.DebugTranspilerLog("");
for (int j = 0; j < list.Count; j++)
{
CodeInstruction arg = list[j];
BepInPluginTemplate.DebugTranspilerLog($"{j} {arg}");
}
BepInPluginTemplate.DebugTranspilerLog("######## CheckRun_Transpiler END ########");
BepInPluginTemplate.DebugTranspilerLog("");
return list;
}
public static float GetSwimmingStaminaDrain(float drainMax, float drainMin, float skillFactor, Player playerInst)
{
if ((Object)(object)Player.m_localPlayer != (Object)null && (Object)(object)Player.m_localPlayer == (Object)(object)playerInst)
{
float num = EasingFunctions.GetEasingFunction(EasingFunctions.Ease.EaseOutSine)(BetterStaminaPlugin.swimMaxStaminaCost.Value, BetterStaminaPlugin.swimMinStaminaCost.Value, skillFactor);
if (BetterStaminaPlugin.enableSkillStaminaLogging != null && BetterStaminaPlugin.enableSkillStaminaLogging.Value)
{
BepInPluginTemplate.DebugLog($"SwimStamina: Usage change: {Mathf.Lerp(drainMax, drainMin, skillFactor)} - {num}; skill: {((Character)playerInst).GetSkillFactor((SkillType)103)};");
}
return num;
}
return Mathf.Lerp(drainMax, drainMin, skillFactor);
}
[HarmonyPatch(typeof(Player), "OnSwimming")]
[HarmonyTranspiler]
private static IEnumerable<CodeInstruction> OnSwimming_Transpiler(IEnumerable<CodeInstruction> instructions)
{
//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
//IL_00e6: Expected O, but got Unknown
//IL_013c: Unknown result type (might be due to invalid IL or missing references)
//IL_0146: Expected O, but got Unknown
BepInPluginTemplate.DebugTranspilerLog("######## OnSwimming_Transpiler START ########");
List<CodeInstruction> list = new List<CodeInstruction>(instructions);
for (int i = 0; i < list.Count; i++)
{
CodeInstruction val = list[i];
BepInPluginTemplate.DebugTranspilerLog($"{i} {val}");
if (val.opcode == OpCodes.Call && ((object)val).ToString().Contains("Mathf::Lerp"))
{
int num = i;
BepInPluginTemplate.DebugTranspilerLog($">>> Deleting instruction {num} {((object)list[num]).ToString()}:");
list.RemoveAt(i);
BepInPluginTemplate.DebugTranspilerLog($">>> Inserting instruction at {num}:");
BepInPluginTemplate.DebugTranspilerLog("Old: " + ((object)list[num]).ToString());
list.Insert(num, new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(typeof(SkillPatches), "GetSwimmingStaminaDrain", (Type[])null, (Type[])null)));
BepInPluginTemplate.DebugTranspilerLog("New: " + ((object)list[num]).ToString());
BepInPluginTemplate.DebugTranspilerLog($">>> Inserting instruction at {num}:");
BepInPluginTemplate.DebugTranspilerLog("Old: " + ((object)list[num]).ToString());
list.Insert(num, new CodeInstruction(OpCodes.Ldarg_0, (object)null));
BepInPluginTemplate.DebugTranspilerLog("New: " + ((object)list[num]).ToString());
break;
}
}
BepInPluginTemplate.DebugTranspilerLog("");
BepInPluginTemplate.DebugTranspilerLog("#############################################################");
BepInPluginTemplate.DebugTranspilerLog($"######## MODIFIED INSTRUCTIONS - {list.Count} ########");
BepInPluginTemplate.DebugTranspilerLog("#############################################################");
BepInPluginTemplate.DebugTranspilerLog("");
for (int j = 0; j < list.Count; j++)
{
CodeInstruction arg = list[j];
BepInPluginTemplate.DebugTranspilerLog($"{j} {arg}");
}
BepInPluginTemplate.DebugTranspilerLog("######## OnSwimming_Transpiler END ########");
BepInPluginTemplate.DebugTranspilerLog("");
return list;
}
}
internal static class GeneralStaminaPatches
{
private static float defaultEncumberedStaminaDrain;
private static void UpdateEncumberedStaminaDrain(Player __instance)
{
if (BetterStaminaPlugin.removeEncumberedStaminaDrain.Value)
{
if (defaultEncumberedStaminaDrain == 0f)
{
defaultEncumberedStaminaDrain = __instance.m_encumberedStaminaDrain;
}
__instance.m_encumberedStaminaDrain = 0f;
}
else if (defaultEncumberedStaminaDrain != 0f)
{
__instance.m_encumberedStaminaDrain = defaultEncumberedStaminaDrain;
defaultEncumberedStaminaDrain = 0f;
}
}
private static float CalculateNewStamina(Player __instance, bool ___m_wallRunning, float ___m_staminaRegen, float ___m_stamina, SEMan ___m_seman, ref float ___m_staminaRegenTimer, float dt)
{
float num = 1f;
if (((Character)__instance).IsBlocking())
{
num *= 0.8f;
}
if (((((Character)__instance).IsSwimming() && !((Character)__instance).IsOnGround()) || ((Character)__instance).InAttack() || ((Character)__instance).InDodge() || ___m_wallRunning) | ((Character)__instance).IsEncumbered())
{
num = 0f;
}
float maxStamina = ((Character)__instance).GetMaxStamina();
float num2 = (float)(1.0 - (double)___m_stamina / (double)maxStamina);
float num3 = (___m_staminaRegen + num2 * ___m_staminaRegen * __instance.m_staminaRegenTimeMultiplier) * num;
float num4 = 1f;
___m_seman.ModifyStaminaRegen(ref num4);
float num5 = num3 * num4;
___m_staminaRegenTimer -= dt;
float num6 = ___m_stamina;
if ((double)___m_stamina < (double)maxStamina && (double)___m_staminaRegenTimer <= 0.0)
{
num6 = Mathf.Min(maxStamina, ___m_stamina + num5 * dt);
}
float num7 = num6 - ___m_stamina;
if (Mathf.Abs(num7) > 0f)
{
BepInPluginTemplate.DebugLog($"StaminaChangeThisFrame: {num5}(dt-{num7}), base regen - {___m_staminaRegen}; activity mult - {num}; base mult - {__instance.m_staminaRegenTimeMultiplier}; missing mult - {num2}; SE mult - {num4}");
}
return num6;
}
[HarmonyPatch(typeof(Player), "UpdateStats", new Type[] { typeof(float) })]
[HarmonyPrefix]
private static void UpdateStats_Prefix(Player __instance, float dt, SEMan ___m_seman, bool ___m_wallRunning, float ___m_staminaRegen, float ___m_stamina, float ___m_staminaRegenTimer)
{
UpdateEncumberedStaminaDrain(__instance);
__instance.m_staminaRegenTimeMultiplier = ((((Humanoid)__instance).GetCurrentWeapon() != null && !((object)((Humanoid)__instance).GetCurrentWeapon()).Equals((object?)((Humanoid)__instance).m_unarmedWeapon.m_itemData)) ? BetterStaminaPlugin.staminaRegenRateMultiplierWithWeapons.Value : BetterStaminaPlugin.staminaRegenRateMultiplier.Value);
__instance.m_staminaRegenDelay = BetterStaminaPlugin.staminaRegenDelay.Value;
if (BetterStaminaPlugin.enableStaminaRegenLogging != null && BetterStaminaPlugin.enableStaminaRegenLogging.Value)
{
CalculateNewStamina(__instance, ___m_wallRunning, ___m_staminaRegen, ___m_stamina, ___m_seman, ref ___m_staminaRegenTimer, dt);
}
}
}
internal static class DebugStaminaPatches
{
[HarmonyPatch(typeof(Player), "UseStamina")]
[HarmonyPrefix]
private static void UseStamina_Prefix(Player __instance, ref float __state, ref float ___m_stamina)
{
__state = ___m_stamina;
}
[HarmonyPatch(typeof(Player), "UseStamina")]
[HarmonyPostfix]
private static void UseStamina_Postfix(Player __instance, float __state, ref float ___m_stamina)
{
if (BetterStaminaPlugin.enableStaminaLogging != null && BetterStaminaPlugin.enableStaminaLogging.Value && ___m_stamina - __state != 0f)
{
BepInPluginTemplate.DebugLog($"UseStamina(): source - {new StackFrame(2).GetMethod().Name}; change - {___m_stamina - __state}");
}
}
}
internal static class ToolsPatches
{
[HarmonyPatch(typeof(Player), "UseStamina")]
[HarmonyPrefix]
private static bool UseStamina_Prefix(Player __instance, ref float v)
{
if (((Humanoid)__instance).GetCurrentWeapon() != null)
{
string name = new StackFrame(2).GetMethod().Name;
if ((name.Contains("Repair") || name.Contains("UpdatePlacement")) && BetterStaminaPlugin.removeToolStaminaCost.Value)
{
return false;
}
}
return true;
}
}
public class BepInPluginTemplate : BaseUnityPlugin
{
public enum LogMessageType
{
LogInfo,
LogMessage,
LogDebug,
LogWarning,
LogError,
LogFatal
}
public static ConfigEntry<bool> enableLogging;
public static ConfigEntry<bool> enableTranspilerLogging;
protected BepInPlugin BepInAttr { get; set; }
protected static Harmony harmonyInst { get; set; }
protected static BepInPluginTemplate modInst { get; set; }
protected static ManualLogSource Logger { get; set; }
public static void DebugTranspilerLog(object message, LogMessageType msgType = LogMessageType.LogInfo)
{
DebugLog(message, msgType, transpilerlogs: true);
}
public static void DebugLog(object message, LogMessageType msgType = LogMessageType.LogInfo, bool transpilerlogs = false)
{
if (enableLogging != null && enableLogging.Value && (!transpilerlogs || enableTranspilerLogging == null || enableTranspilerLogging.Value))
{
switch (msgType)
{
case LogMessageType.LogMessage:
Logger.LogMessage(message);
break;
case LogMessageType.LogDebug:
Logger.LogDebug(message);
break;
case LogMessageType.LogWarning:
Logger.LogWarning(message);
break;
case LogMessageType.LogError:
Logger.LogError(message);
break;
case LogMessageType.LogFatal:
Logger.LogFatal(message);
break;
default:
Logger.LogInfo(message);
break;
}
}
}
public static void PrintOutInstructions(List<CodeInstruction> instructions)
{
DebugTranspilerLog("");
DebugTranspilerLog("#############################################################");
DebugTranspilerLog($"######## MODIFIED INSTRUCTIONS - {instructions.Count} ########");
DebugTranspilerLog("#############################################################");
DebugTranspilerLog("");
for (int i = 0; i < instructions.Count; i++)
{
CodeInstruction arg = instructions[i];
DebugTranspilerLog($"{i} {arg}");
}
}
protected virtual void Awake()
{
//IL_001c: Unknown result type (might be due to invalid IL or missing references)
//IL_0026: Expected O, but got Unknown
//IL_0046: Unknown result type (might be due to invalid IL or missing references)
//IL_0050: Expected O, but got Unknown
modInst = this;
BepInAttr = (BepInPlugin)Attribute.GetCustomAttribute(((object)this).GetType(), typeof(BepInPlugin));
Logger = Logger.CreateLogSource(BepInAttr.Name);
harmonyInst = new Harmony(BepInAttr.GUID);
DebugLog("Loading..");
}
protected virtual void OnDestroy()
{
if (harmonyInst != null)
{
harmonyInst.UnpatchSelf();
}
DebugLog("Unloading..");
Logger.Sources.Remove((ILogSource)(object)Logger);
}
}
internal static class BepInExHelpers
{
public static ObjectDB FindObjectDB()
{
GameObject val = GameObject.Find("_GameMain");
if ((Object)(object)val != (Object)null)
{
ObjectDB component = val.GetComponent<ObjectDB>();
if ((Object)(object)component != (Object)null)
{
return component;
}
}
return null;
}
}
public static class MathUtils
{
public static string ColorRGBToHexString(int r, int g, int b)
{
return $"#{r:X2}{g:X2}{b:X2}";
}
public static float ConvertRange(float originalStart, float originalEnd, float newStart, float newEnd, float value)
{
double num = (double)(newEnd - newStart) / (double)(originalEnd - originalStart);
return (float)((double)newStart + (double)(value - originalStart) * num);
}
public static float Clamp(float value, float min, float max)
{
if (!(value < min))
{
if (!(value > max))
{
return value;
}
return max;
}
return min;
}
}
public static class EasingFunctions
{
public enum Ease
{
EaseInQuad,
EaseOutQuad,
EaseInOutQuad,
EaseInCubic,
EaseOutCubic,
EaseInOutCubic,
EaseInQuart,
EaseOutQuart,
EaseInOutQuart,
EaseInQuint,
EaseOutQuint,
EaseInOutQuint,
EaseInSine,
EaseOutSine,
EaseInOutSine,
EaseInExpo,
EaseOutExpo,
EaseInOutExpo,
EaseInCirc,
EaseOutCirc,
EaseInOutCirc,
Linear,
Spring,
EaseInBounce,
EaseOutBounce,
EaseInOutBounce,
EaseInBack,
EaseOutBack,
EaseInOutBack,
EaseInElastic,
EaseOutElastic,
EaseInOutElastic
}
public delegate float Function(float s, float e, float v);
private const float NATURAL_LOG_OF_2 = 0.6931472f;
public static float Linear(float start, float end, float value)
{
return Mathf.Lerp(start, end, value);
}
public static float Spring(float start, float end, float value)
{
value = Mathf.Clamp01(value);
value = (Mathf.Sin(value * (float)Math.PI * (0.2f + 2.5f * value * value * value)) * Mathf.Pow(1f - value, 2.2f) + value) * (1f + 1.2f * (1f - value));
return start + (end - start) * value;
}
public static float EaseInQuad(float start, float end, float value)
{
end -= start;
return end * value * value + start;
}
public static float EaseOutQuad(float start, float end, float value)
{
end -= start;
return (0f - end) * value * (value - 2f) + start;
}
public static float EaseInOutQuad(float start, float end, float value)
{
value /= 0.5f;
end -= start;
if (value < 1f)
{
return end * 0.5f * value * value + start;
}
value -= 1f;
return (0f - end) * 0.5f * (value * (value - 2f) - 1f) + start;
}
public static float EaseInCubic(float start, float end, float value)
{
end -= start;
return end * value * value * value + start;
}
public static float EaseOutCubic(float start, float end, float value)
{
value -= 1f;
end -= start;
return end * (value * value * value + 1f) + start;
}
public static float EaseInOutCubic(float start, float end, float value)
{
value /= 0.5f;
end -= start;
if (value < 1f)
{
return end * 0.5f * value * value * value + start;
}
value -= 2f;
return end * 0.5f * (value * value * value + 2f) + start;
}
public static float EaseInQuart(float start, float end, float value)
{
end -= start;
return end * value * value * value * value + start;
}
public static float EaseOutQuart(float start, float end, float value)
{
value -= 1f;
end -= start;
return (0f - end) * (value * value * value * value - 1f) + start;
}
public static float EaseInOutQuart(float start, float end, float value)
{
value /= 0.5f;
end -= start;
if (value < 1f)
{
return end * 0.5f * value * value * value * value + start;
}
value -= 2f;
return (0f - end) * 0.5f * (value * value * value * value - 2f) + start;
}
public static float EaseInQuint(float start, float end, float value)
{
end -= start;
return end * value * value * value * value * value + start;
}
public static float EaseOutQuint(float start, float end, float value)
{
value -= 1f;
end -= start;
return end * (value * value * value * value * value + 1f) + start;
}
public static float EaseInOutQuint(float start, float end, float value)
{
value /= 0.5f;
end -= start;
if (value < 1f)
{
return end * 0.5f * value * value * value * value * value + start;
}
value -= 2f;
return end * 0.5f * (value * value * value * value * value + 2f) + start;
}
public static float EaseInSine(float start, float end, float value)
{
end -= start;
return (0f - end) * Mathf.Cos(value * ((float)Math.PI / 2f)) + end + start;
}
public static float EaseOutSine(float start, float end, float value)
{
end -= start;
return end * Mathf.Sin(value * ((float)Math.PI / 2f)) + start;
}
public static float EaseInOutSine(float start, float end, float value)
{
end -= start;
return (0f - end) * 0.5f * (Mathf.Cos((float)Math.PI * value) - 1f) + start;
}
public static float EaseInExpo(float start, float end, float value)
{
end -= start;
return end * Mathf.Pow(2f, 10f * (value - 1f)) + start;
}
public static float EaseOutExpo(float start, float end, float value)
{
end -= start;
return end * (0f - Mathf.Pow(2f, -10f * value) + 1f) + start;
}
public static float EaseInOutExpo(float start, float end, float value)
{
value /= 0.5f;
end -= start;
if (value < 1f)
{
return end * 0.5f * Mathf.Pow(2f, 10f * (value - 1f)) + start;
}
value -= 1f;
return end * 0.5f * (0f - Mathf.Pow(2f, -10f * value) + 2f) + start;
}
public static float EaseInCirc(float start, float end, float value)
{
end -= start;
return (0f - end) * (Mathf.Sqrt(1f - value * value) - 1f) + start;
}
public static float EaseOutCirc(float start, float end, float value)
{
value -= 1f;
end -= start;
return end * Mathf.Sqrt(1f - value * value) + start;
}
public static float EaseInOutCirc(float start, float end, float value)
{
value /= 0.5f;
end -= start;
if (value < 1f)
{
return (0f - end) * 0.5f * (Mathf.Sqrt(1f - value * value) - 1f) + start;
}
value -= 2f;
return end * 0.5f * (Mathf.Sqrt(1f - value * value) + 1f) + start;
}
public static float EaseInBounce(float start, float end, float value)
{
end -= start;
float num = 1f;
return end - EaseOutBounce(0f, end, num - value) + start;
}
public static float EaseOutBounce(float start, float end, float value)
{
value /= 1f;
end -= start;
if (value < 0.36363637f)
{
return end * (7.5625f * value * value) + start;
}
if (value < 0.72727275f)
{
value -= 0.54545456f;
return end * (7.5625f * value * value + 0.75f) + start;
}
if ((double)value < 0.9090909090909091)
{
value -= 0.8181818f;
return end * (7.5625f * value * value + 0.9375f) + start;
}
value -= 21f / 22f;
return end * (7.5625f * value * value + 63f / 64f) + start;
}
public static float EaseInOutBounce(float start, float end, float value)
{
end -= start;
float num = 1f;
if (value < num * 0.5f)
{
return EaseInBounce(0f, end, value * 2f) * 0.5f + start;
}
return EaseOutBounce(0f, end, value * 2f - num) * 0.5f + end * 0.5f + start;
}
public static float EaseInBack(float start, float end, float value)
{
end -= start;
value /= 1f;
float num = 1.70158f;
return end * value * value * ((num + 1f) * value - num) + start;
}
public static float EaseOutBack(float start, float end, float value)
{
float num = 1.70158f;
end -= start;
value -= 1f;
return end * (value * value * ((num + 1f) * value + num) + 1f) + start;
}
public static float EaseInOutBack(float start, float end, float value)
{
float num = 1.70158f;
end -= start;
value /= 0.5f;
if (value < 1f)
{
num *= 1.525f;
return end * 0.5f * (value * value * ((num + 1f) * value - num)) + start;
}
value -= 2f;
num *= 1.525f;
return end * 0.5f * (value * value * ((num + 1f) * value + num) + 2f) + start;
}
public static float EaseInElastic(float start, float end, float value)
{
end -= start;
float num = 1f;
float num2 = num * 0.3f;
float num3 = 0f;
if (value == 0f)
{
return start;
}
if ((value /= num) == 1f)
{
return start + end;
}
float num4;
if (num3 == 0f || num3 < Mathf.Abs(end))
{
num3 = end;
num4 = num2 / 4f;
}
else
{
num4 = num2 / ((float)Math.PI * 2f) * Mathf.Asin(end / num3);
}
return 0f - num3 * Mathf.Pow(2f, 10f * (value -= 1f)) * Mathf.Sin((value * num - num4) * ((float)Math.PI * 2f) / num2) + start;
}
public static float EaseOutElastic(float start, float end, float value)
{
end -= start;
float num = 1f;
float num2 = num * 0.3f;
float num3 = 0f;
if (value == 0f)
{
return start;
}
if ((value /= num) == 1f)
{
return start + end;
}
float num4;
if (num3 == 0f || num3 < Mathf.Abs(end))
{
num3 = end;
num4 = num2 * 0.25f;
}
else
{
num4 = num2 / ((float)Math.PI * 2f) * Mathf.Asin(end / num3);
}
return num3 * Mathf.Pow(2f, -10f * value) * Mathf.Sin((value * num - num4) * ((float)Math.PI * 2f) / num2) + end + start;
}
public static float EaseInOutElastic(float start, float end, float value)
{
end -= start;
float num = 1f;
float num2 = num * 0.3f;
float num3 = 0f;
if (value == 0f)
{
return start;
}
if ((value /= num * 0.5f) == 2f)
{
return start + end;
}
float num4;
if (num3 == 0f || num3 < Mathf.Abs(end))
{
num3 = end;
num4 = num2 / 4f;
}
else
{
num4 = num2 / ((float)Math.PI * 2f) * Mathf.Asin(end / num3);
}
if (value < 1f)
{
return -0.5f * (num3 * Mathf.Pow(2f, 10f * (value -= 1f)) * Mathf.Sin((value * num - num4) * ((float)Math.PI * 2f) / num2)) + start;
}
return num3 * Mathf.Pow(2f, -10f * (value -= 1f)) * Mathf.Sin((value * num - num4) * ((float)Math.PI * 2f) / num2) * 0.5f + end + start;
}
public static Function GetEasingFunction(Ease easingFunction)
{
return easingFunction switch
{
Ease.EaseInQuad => EaseInQuad,
Ease.EaseOutQuad => EaseOutQuad,
Ease.EaseInOutQuad => EaseInOutQuad,
Ease.EaseInCubic => EaseInCubic,
Ease.EaseOutCubic => EaseOutCubic,
Ease.EaseInOutCubic => EaseInOutCubic,
Ease.EaseInQuart => EaseInQuart,
Ease.EaseOutQuart => EaseOutQuart,
Ease.EaseInOutQuart => EaseInOutQuart,
Ease.EaseInQuint => EaseInQuint,
Ease.EaseOutQuint => EaseOutQuint,
Ease.EaseInOutQuint => EaseInOutQuint,
Ease.EaseInSine => EaseInSine,
Ease.EaseOutSine => EaseOutSine,
Ease.EaseInOutSine => EaseInOutSine,
Ease.EaseInExpo => EaseInExpo,
Ease.EaseOutExpo => EaseOutExpo,
Ease.EaseInOutExpo => EaseInOutExpo,
Ease.EaseInCirc => EaseInCirc,
Ease.EaseOutCirc => EaseOutCirc,
Ease.EaseInOutCirc => EaseInOutCirc,
Ease.Linear => Linear,
Ease.Spring => Spring,
Ease.EaseInBounce => EaseInBounce,
Ease.EaseOutBounce => EaseOutBounce,
Ease.EaseInOutBounce => EaseInOutBounce,
Ease.EaseInBack => EaseInBack,
Ease.EaseOutBack => EaseOutBack,
Ease.EaseInOutBack => EaseInOutBack,
Ease.EaseInElastic => EaseInElastic,
Ease.EaseOutElastic => EaseOutElastic,
Ease.EaseInOutElastic => EaseInOutElastic,
_ => null,
};
}
}
namespace BetterStamina;
[BepInPlugin("bakaSpaceman.BetterStamina", "Better Stamina - Hildirs Request Update", "2.1.0")]
public class BetterStaminaPlugin : BepInPluginTemplate
{
public static ConfigEntry<bool> enableStaminaLogging;
public static ConfigEntry<bool> enableStaminaRegenLogging;
public static ConfigEntry<bool> enableSkillStaminaLogging;
public static ConfigEntry<int> nexusID;
public static ConfigEntry<bool> removeEncumberedStaminaDrain;
public static ConfigEntry<float> staminaRegenRateMultiplier;
public static ConfigEntry<float> staminaRegenRateMultiplierWithWeapons;
public static ConfigEntry<float> staminaRegenDelay;
public static ConfigEntry<bool> removeToolStaminaCost;
public static ConfigEntry<float> runMaxSkillStaminaCost;
public static ConfigEntry<float> runWithWeapMaxSkillStaminaCost;
public static ConfigEntry<float> dodgeMaxSkillStaminaCost;
public static ConfigEntry<float> jumpMaxSkillStaminaCost;
public static ConfigEntry<float> blockMaxSkillStaminaCost;
public static ConfigEntry<float> sneakMaxSkillStaminaCost;
public static ConfigEntry<float> weaponMaxSkillAttackStaminaCost;
public static ConfigEntry<float> bowMaxSkillHoldStaminaCost;
public static ConfigEntry<float> swimMaxStaminaCost;
public static ConfigEntry<float> swimMinStaminaCost;
public static ConfigEntry<float> restedStaminaRegenMultiplier;
public static ConfigEntry<float> restedDurationPerComfortLvl;
public static ConfigEntry<float> wetStaminaRegenMultiplier;
public static ConfigEntry<float> coldStaminaRegenMultiplier;
public static FieldInfo playerSkillsField = typeof(Player).GetField("m_skills", BindingFlags.Instance | BindingFlags.NonPublic);
public static FieldInfo statusEffectsField = typeof(SEMan).GetField("m_statusEffects", BindingFlags.Instance | BindingFlags.NonPublic);
private void SetupConfig()
{
nexusID = ((BaseUnityPlugin)this).Config.Bind<int>("General", "NexusID", 153, "Nexus mod ID for updates");
staminaRegenRateMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("General", "StaminaRegenRateMod", 1.5f, "1 - Default rate, 1.5 - 50% faster rate, 0.5 - 50% slower, etc.");
staminaRegenRateMultiplierWithWeapons = ((BaseUnityPlugin)this).Config.Bind<float>("General", "StaminaRegenRateModWithWeapons", 1.4f, "This will be used instead of StaminaRegenRateMod if player has weapons equipped. 1 - Default rate, 1.5 - 50% faster rate, 0.5 - 50% slower, etc.");
staminaRegenDelay = ((BaseUnityPlugin)this).Config.Bind<float>("General", "StaminaRegenDelay", 1f, "Time in seconds before stamina starts regenerating. Default value - 1.");
removeEncumberedStaminaDrain = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "RemoveEncumberedStaminaDrain", true, "Prevents stamina drain while encumbered.");
removeToolStaminaCost = ((BaseUnityPlugin)this).Config.Bind<bool>("Tools", "RemoveToolStaminaCost", true, "Using tools to repair/build will not consume stamina.");
runMaxSkillStaminaCost = ((BaseUnityPlugin)this).Config.Bind<float>("Skills", "RunCostAtMaxSkill", 0.4f, "The value is a percentage modifier of the default cost. 1 - default cost, < 1 - reduced cost, > 1 - increased");
runWithWeapMaxSkillStaminaCost = ((BaseUnityPlugin)this).Config.Bind<float>("Skills", "RunWithWeaponsCostAtMaxSkill", 0.5f, "This will be used instead of RunCostModifierAtMaxSkill if player has weapons equipped. The value is a percentage modifier of the default cost. 1 - default cost, < 1 - reduced cost, > 1 - increased");
dodgeMaxSkillStaminaCost = ((BaseUnityPlugin)this).Config.Bind<float>("Skills", "DodgeCostAtMaxSkill", 0.67f, "The value is a percentage modifier of the default cost. 1 - default cost, < 1 - reduced cost, > 1 - increased");
jumpMaxSkillStaminaCost = ((BaseUnityPlugin)this).Config.Bind<float>("Skills", "JumpCostAtMaxSkill", 0.67f, "The value is a percentage modifier of the default cost. 1 - default cost, < 1 - reduced cost, > 1 - increased");
blockMaxSkillStaminaCost = ((BaseUnityPlugin)this).Config.Bind<float>("Skills", "BlockCostAtMaxSkill", 0.67f, "The value is a percentage modifier of the default cost. 1 - default cost, < 1 - reduced cost, > 1 - increased");
sneakMaxSkillStaminaCost = ((BaseUnityPlugin)this).Config.Bind<float>("Skills", "SneakCostAtMaxSkill", 0.67f, "The value is a percentage modifier of the default cost. 1 - default cost, < 1 - reduced cost, > 1 - increased");
weaponMaxSkillAttackStaminaCost = ((BaseUnityPlugin)this).Config.Bind<float>("Skills", "WeaponAttackCostAtMaxSkill", 0.67f, "The value is a percentage modifier of the default cost. 1 - default cost, < 1 - reduced cost, > 1 - increased");
bowMaxSkillHoldStaminaCost = ((BaseUnityPlugin)this).Config.Bind<float>("Skills", "HoldBowCostAtMaxSkill", 0.67f, "The value is a percentage modifier of the default cost. 1 - default cost, < 1 - reduced cost, > 1 - increased");
swimMaxStaminaCost = ((BaseUnityPlugin)this).Config.Bind<float>("Skills", "SwimCostAtMinSkill", 6f, "Swimming stamina cost defined in units at minimum Swim skill.");
swimMinStaminaCost = ((BaseUnityPlugin)this).Config.Bind<float>("Skills", "SwimCostAtMaxSkill", 3f, "Swimming stamina cost defined in units at maximum Swim skill.");
coldStaminaRegenMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("Status Effects", "ColdStaminaRegenModifier", 0.75f, "Vanilla value - 0.75 (25% penalty)");
restedStaminaRegenMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("Status Effects", "RestedStaminaRegenModifier", 1.5f, "Vanilla value - 2 (100% bonus)");
restedDurationPerComfortLvl = ((BaseUnityPlugin)this).Config.Bind<float>("Status Effects", "RestedDurationIncreasePerConfortLevel", 60f, "This amount of seconds will be added to the effects duration per comfort level. Vanilla value - 60 seconds");
wetStaminaRegenMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("Status Effects", "WetStaminaRegenModifier", 0.85f, "Vanilla value - 0.85 (15% penalty)");
}
protected override void Awake()
{
base.Awake();
SetupConfig();
if (BepInPluginTemplate.enableLogging != null && BepInPluginTemplate.enableLogging.Value)
{
BepInPluginTemplate.harmonyInst.PatchAll(typeof(DebugStaminaPatches));
}
BepInPluginTemplate.harmonyInst.PatchAll(typeof(GeneralStaminaPatches));
BepInPluginTemplate.harmonyInst.PatchAll(typeof(ToolsPatches));
BepInPluginTemplate.harmonyInst.PatchAll(typeof(SkillPatches));
BepInPluginTemplate.harmonyInst.PatchAll(typeof(StatusEffectPatches));
}
}