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 HarmonyLib;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("Magical")]
[assembly: AssemblyDescription("Magical")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Crystal")]
[assembly: AssemblyProduct("Magical")]
[assembly: AssemblyCopyright("Copyright © 2023 Crystal Ferrai")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("1414a5c1-1a42-448f-8f27-000b965fcb46")]
[assembly: AssemblyFileVersion("1.0.2.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyVersion("1.0.2.0")]
namespace Magical;
[BepInPlugin("dev.crystal.magical", "Magical", "1.0.2.0")]
[BepInProcess("valheim.exe")]
[BepInProcess("valheim_server.exe")]
public class MagicalPlugin : BaseUnityPlugin
{
[HarmonyPatch(typeof(Player))]
private static class Player_Tracking_Patches
{
[HarmonyPatch("Awake")]
[HarmonyPostfix]
private static void Awake_Prefix(Player __instance)
{
SetPlayerValues(__instance);
sPlayers.Add(__instance);
}
[HarmonyPatch("OnDestroy")]
[HarmonyPrefix]
private static void OnDestroy_Prefix(Player __instance)
{
sPlayers.Remove(__instance);
}
}
[HarmonyPatch(typeof(Player))]
private static class Player_Patches
{
private enum TranspilerState
{
Searching,
Checking,
Replacing,
Searching2,
Checking2,
Replacing2,
Finishing
}
[HarmonyPatch("UpdateFood")]
[HarmonyTranspiler]
private static IEnumerable<CodeInstruction> UpdateFood_Transpiler(IEnumerable<CodeInstruction> instructions)
{
TranspilerState state = TranspilerState.Searching;
CodeInstruction previousInstruction = null;
foreach (CodeInstruction instruction in instructions)
{
switch (state)
{
case TranspilerState.Searching:
if (instruction.opcode == OpCodes.Ldfld && ((FieldInfo)instruction.operand).Name.Equals("m_foodRegenTimer"))
{
state = TranspilerState.Checking;
}
yield return instruction;
break;
case TranspilerState.Checking:
if (instruction.opcode == OpCodes.Ldc_R4 && (float)instruction.operand == 10f)
{
state = TranspilerState.Replacing;
break;
}
yield return instruction;
state = TranspilerState.Searching;
break;
case TranspilerState.Replacing:
yield return new CodeInstruction(OpCodes.Ldc_R4, (object)HealthRegenTickRate.Value);
yield return instruction;
state = TranspilerState.Searching2;
break;
case TranspilerState.Searching2:
if (instruction.opcode == OpCodes.Ldc_R4 && (float)instruction.operand == 0f)
{
previousInstruction = instruction;
state = TranspilerState.Checking2;
}
else
{
yield return instruction;
}
break;
case TranspilerState.Checking2:
if (instruction.opcode == OpCodes.Stloc_S)
{
previousInstruction = instruction;
state = TranspilerState.Replacing2;
}
else
{
yield return previousInstruction;
yield return instruction;
state = TranspilerState.Searching2;
}
break;
case TranspilerState.Replacing2:
yield return new CodeInstruction(OpCodes.Ldc_R4, (object)BaseHealthRegen.Value);
yield return previousInstruction;
yield return instruction;
state = TranspilerState.Finishing;
break;
case TranspilerState.Finishing:
yield return instruction;
break;
}
}
}
[HarmonyPatch("GetTotalFoodValue")]
[HarmonyTranspiler]
private static IEnumerable<CodeInstruction> GetTotalFoodValue_Transpiler(IEnumerable<CodeInstruction> instructions)
{
TranspilerState state = TranspilerState.Searching;
foreach (CodeInstruction instruction in instructions)
{
switch (state)
{
case TranspilerState.Searching:
if (instruction.opcode == OpCodes.Ldc_R4 && (float)instruction.operand == 0f)
{
state = TranspilerState.Replacing;
}
else
{
yield return instruction;
}
break;
case TranspilerState.Replacing:
yield return new CodeInstruction(OpCodes.Ldc_R4, (object)BaseEitr.Value);
yield return instruction;
state = TranspilerState.Finishing;
break;
case TranspilerState.Finishing:
yield return instruction;
break;
}
}
}
}
[HarmonyPatch(typeof(Attack))]
private static class Attack_Patches
{
private enum TranspilerState
{
Searching,
Replacing,
Finishing
}
[HarmonyPatch("GetAttackStamina")]
[HarmonyTranspiler]
private static IEnumerable<CodeInstruction> GetAttackStamina_Transpiler(IEnumerable<CodeInstruction> instructions)
{
return ReplaceSkillModifier(instructions, SkillStaminaReduction.Value);
}
[HarmonyPatch("GetAttackEitr")]
[HarmonyTranspiler]
private static IEnumerable<CodeInstruction> GetAttackEitr_Transpiler(IEnumerable<CodeInstruction> instructions)
{
return ReplaceSkillModifier(instructions, SkillEitrReduction.Value);
}
[HarmonyPatch("GetAttackHealth")]
[HarmonyTranspiler]
private static IEnumerable<CodeInstruction> GetAttackHealth_Transpiler(IEnumerable<CodeInstruction> instructions)
{
return ReplaceSkillModifier(instructions, SkillHealthReduction.Value);
}
private static IEnumerable<CodeInstruction> ReplaceSkillModifier(IEnumerable<CodeInstruction> instructions, float newModifier)
{
TranspilerState state = TranspilerState.Searching;
foreach (CodeInstruction instruction in instructions)
{
switch (state)
{
case TranspilerState.Searching:
if (instruction.opcode == OpCodes.Ldc_R4 && (float)instruction.operand == 0.33f)
{
state = TranspilerState.Replacing;
}
else
{
yield return instruction;
}
break;
case TranspilerState.Replacing:
yield return new CodeInstruction(OpCodes.Ldc_R4, (object)newModifier);
yield return instruction;
state = TranspilerState.Finishing;
break;
case TranspilerState.Finishing:
yield return instruction;
break;
}
}
}
}
public const string ModId = "dev.crystal.magical";
public static ConfigEntry<float> BaseStamina;
public static ConfigEntry<float> BaseEitr;
public static ConfigEntry<float> BaseHealth;
public static ConfigEntry<float> BaseStaminaRegen;
public static ConfigEntry<float> BaseEitrRegen;
public static ConfigEntry<float> BaseHealthRegen;
public static ConfigEntry<float> StaminaRegenDelay;
public static ConfigEntry<float> EitrRegenDelay;
public static ConfigEntry<float> HealthRegenTickRate;
public static ConfigEntry<float> SkillStaminaReduction;
public static ConfigEntry<float> SkillEitrReduction;
public static ConfigEntry<float> SkillHealthReduction;
private static Harmony sPlayerTrackingHarmony;
private static Harmony sPlayerHarmony;
private static Harmony sAttackHarmony;
private static List<Player> sPlayers;
static MagicalPlugin()
{
sPlayers = new List<Player>();
}
private void Awake()
{
//IL_02bd: Unknown result type (might be due to invalid IL or missing references)
//IL_02c7: Expected O, but got Unknown
//IL_02cc: Unknown result type (might be due to invalid IL or missing references)
//IL_02d6: Expected O, but got Unknown
//IL_02db: Unknown result type (might be due to invalid IL or missing references)
//IL_02e5: Expected O, but got Unknown
BaseStamina = ((BaseUnityPlugin)this).Config.Bind<float>("Base", "BaseStamina", 50f, "Maximum stamina before any food modifiers are applied. Game default 50.");
BaseStamina.SettingChanged += PlayerVariable_SettingChanged;
BaseEitr = ((BaseUnityPlugin)this).Config.Bind<float>("Base", "BaseEitr", 0f, "Maximum eitr before any food modifiers are applied. Game default 0.");
BaseEitr.SettingChanged += PlayerConstant_SettingChanged;
BaseHealth = ((BaseUnityPlugin)this).Config.Bind<float>("Base", "BaseHealth", 25f, "Maximum health before any food modifiers are applied. Game default 25.");
BaseHealth.SettingChanged += PlayerVariable_SettingChanged;
BaseStaminaRegen = ((BaseUnityPlugin)this).Config.Bind<float>("Regen", "BaseStaminaRegen", 6f, "The base rate of stamina regen per second, before any modifiers are applied. Game default 6.");
BaseStaminaRegen.SettingChanged += PlayerVariable_SettingChanged;
BaseEitrRegen = ((BaseUnityPlugin)this).Config.Bind<float>("Regen", "BaseEitrRegen", 2f, "The base rate of eitr regen per second, before any modifiers are applied. Game default 2.");
BaseEitrRegen.SettingChanged += PlayerVariable_SettingChanged;
BaseHealthRegen = ((BaseUnityPlugin)this).Config.Bind<float>("Regen", "BaseHealthRegen", 0f, "The base rate of health regen per health regen tick, before any modifiers are applied. Game default 0.");
BaseHealthRegen.SettingChanged += PlayerConstant_SettingChanged;
StaminaRegenDelay = ((BaseUnityPlugin)this).Config.Bind<float>("Regen", "StaminaRegenDelay", 1f, "The number of seconds after using stamina before it starts to regenerate. Game default 1.");
StaminaRegenDelay.SettingChanged += PlayerVariable_SettingChanged;
EitrRegenDelay = ((BaseUnityPlugin)this).Config.Bind<float>("Regen", "EitrRegenDelay", 1f, "The number of seconds after using eitr before it starts to regenerate. Game default 1.");
EitrRegenDelay.SettingChanged += PlayerVariable_SettingChanged;
HealthRegenTickRate = ((BaseUnityPlugin)this).Config.Bind<float>("Regen", "HealthRegenTickRate", 10f, "The number of seconds between ticks of health regeneration. Game default 10.");
HealthRegenTickRate.SettingChanged += PlayerConstant_SettingChanged;
SkillStaminaReduction = ((BaseUnityPlugin)this).Config.Bind<float>("Skill", "SkillStaminaReduction", 0.33f, "Stamina cost reduction multiplier for actions based on player skill. Value represents reduction with 100 skill and will scale down at lower skill levels. Game default 0.33.");
SkillStaminaReduction.SettingChanged += Attack_SettingChanged;
SkillEitrReduction = ((BaseUnityPlugin)this).Config.Bind<float>("Skill", "SkillEitrReduction", 0.33f, "Eitr cost reduction multiplier for actions based on player skill. Value represents reduction with 100 skill and will scale down at lower skill levels. Game default 0.33.");
SkillEitrReduction.SettingChanged += Attack_SettingChanged;
SkillHealthReduction = ((BaseUnityPlugin)this).Config.Bind<float>("Skill", "SkillHealthReduction", 0.33f, "Health cost reduction multiplier for actions based on player skill. Value represents reduction with 100 skill and will scale down at lower skill levels. Game default 0.33.");
SkillHealthReduction.SettingChanged += Attack_SettingChanged;
sPlayerTrackingHarmony = new Harmony("dev.crystal.magical_Player_Tracking");
sPlayerHarmony = new Harmony("dev.crystal.magical_Player");
sAttackHarmony = new Harmony("dev.crystal.magical_Attack");
ClampConfig();
sPlayerTrackingHarmony.PatchAll(typeof(Player_Tracking_Patches));
sPlayerHarmony.PatchAll(typeof(Player_Patches));
sAttackHarmony.PatchAll(typeof(Attack_Patches));
}
private void OnDestroy()
{
sPlayerTrackingHarmony.UnpatchSelf();
sPlayerHarmony.UnpatchSelf();
sAttackHarmony.UnpatchSelf();
sPlayers.Clear();
}
private static void ClampConfig()
{
if (BaseStamina.Value < 0f)
{
BaseStamina.Value = 0f;
}
if (BaseStamina.Value > 1000f)
{
BaseStamina.Value = 1000f;
}
if (BaseEitr.Value < 0f)
{
BaseEitr.Value = 0f;
}
if (BaseEitr.Value > 1000f)
{
BaseEitr.Value = 1000f;
}
if (BaseHealth.Value < 0f)
{
BaseHealth.Value = 0f;
}
if (BaseHealth.Value > 1000f)
{
BaseHealth.Value = 1000f;
}
if (BaseStaminaRegen.Value < 0.1f)
{
BaseStaminaRegen.Value = 0.1f;
}
if (BaseStaminaRegen.Value > 1000f)
{
BaseStaminaRegen.Value = 1000f;
}
if (BaseEitrRegen.Value < 0.1f)
{
BaseEitrRegen.Value = 0.1f;
}
if (BaseEitrRegen.Value > 1000f)
{
BaseEitrRegen.Value = 1000f;
}
if (BaseHealthRegen.Value < 0f)
{
BaseHealthRegen.Value = 0f;
}
if (BaseHealthRegen.Value > 1000f)
{
BaseHealthRegen.Value = 1000f;
}
if (StaminaRegenDelay.Value < 0.1f)
{
StaminaRegenDelay.Value = 0.1f;
}
if (StaminaRegenDelay.Value > 3600f)
{
StaminaRegenDelay.Value = 3600f;
}
if (EitrRegenDelay.Value < 0.1f)
{
EitrRegenDelay.Value = 0.1f;
}
if (EitrRegenDelay.Value > 3600f)
{
EitrRegenDelay.Value = 3600f;
}
if (HealthRegenTickRate.Value < 0.1f)
{
HealthRegenTickRate.Value = 0.1f;
}
if (HealthRegenTickRate.Value > 3600f)
{
HealthRegenTickRate.Value = 3600f;
}
if (SkillStaminaReduction.Value < 0f)
{
SkillStaminaReduction.Value = 0f;
}
if (SkillStaminaReduction.Value > 1f)
{
SkillStaminaReduction.Value = 1f;
}
if (SkillEitrReduction.Value < 0f)
{
SkillEitrReduction.Value = 0f;
}
if (SkillEitrReduction.Value > 1f)
{
SkillEitrReduction.Value = 1f;
}
if (SkillHealthReduction.Value < 0f)
{
SkillHealthReduction.Value = 0f;
}
if (SkillHealthReduction.Value > 1f)
{
SkillHealthReduction.Value = 1f;
}
}
private void PlayerVariable_SettingChanged(object sender, EventArgs e)
{
ClampConfig();
foreach (Player sPlayer in sPlayers)
{
SetPlayerValues(sPlayer);
}
}
private void Attack_SettingChanged(object sender, EventArgs e)
{
ClampConfig();
sAttackHarmony.UnpatchSelf();
sAttackHarmony.PatchAll(typeof(Attack_Patches));
}
private void PlayerConstant_SettingChanged(object sender, EventArgs e)
{
ClampConfig();
sPlayerHarmony.UnpatchSelf();
sPlayerHarmony.PatchAll(typeof(Player_Patches));
}
private static void SetPlayerValues(Player player)
{
player.m_baseStamina = BaseStamina.Value;
player.m_baseHP = BaseHealth.Value;
player.m_staminaRegen = BaseStaminaRegen.Value;
player.m_eiterRegen = BaseEitrRegen.Value;
player.m_staminaRegenDelay = StaminaRegenDelay.Value;
player.m_eitrRegenDelay = EitrRegenDelay.Value;
}
}