using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using TMPro;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("StructureDamageTweaks")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("StructureDamageTweaks")]
[assembly: AssemblyCopyright("Copyright © 2022")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("b6fe5d82-5883-4677-b77c-3a0d9cfbb356")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.6.1", FrameworkDisplayName = ".NET Framework 4.6.1")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
internal sealed class IgnoresAccessChecksToAttribute : Attribute
{
public IgnoresAccessChecksToAttribute(string assemblyName)
{
}
}
}
namespace StructureDamageTweaks
{
[BepInPlugin("StructureDamageTweaks", "Structure Damage Tweaks", "1.1.0")]
public class StructureDamageTweaks : BaseUnityPlugin
{
public class Category
{
public string id;
public string name;
public float baseMultiplier;
public bool onlyPlayerStructures = true;
public List<string> globalKeys;
public List<float> globalKeysMultipliers;
public List<string> items;
public List<float> itemsMultipliers;
public List<float> itemsRange;
public List<float> itemsAutoRepairPercentage;
private float maxRange;
private bool differentRanges;
private int numRepairItems;
private int numProtectionItems;
public string defaultGlobalKeys = "";
public string defaultGlobalKeysMultipliers = "";
public string defaultItems = "";
public string defaultItemModifiers = "";
public string defaultIdentifiers = "";
public static readonly CultureInfo cultureInfo = new CultureInfo("en-US");
public virtual bool IsInCategory(WearNTear __instance)
{
if (onlyPlayerStructures && ((Object)(object)((Component)__instance).GetComponent<Piece>() == (Object)null || ((Component)__instance).GetComponent<Piece>().GetCreator() == 0L))
{
return false;
}
return true;
}
public float ComputeMultiplier(ref WearNTear __instance)
{
//IL_008a: Unknown result type (might be due to invalid IL or missing references)
//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
float num = 1f;
for (int i = 0; i < globalKeys.Count; i++)
{
if (ZoneSystem.instance.GetGlobalKey(globalKeys[i]))
{
num = globalKeysMultipliers[i];
Log("Modify damage by " + globalKeysMultipliers[i] + " due to global key " + globalKeys[i]);
break;
}
}
if (numProtectionItems > 0)
{
List<Player> playersInRadius = GetPlayersInRadius(((Component)__instance).transform.position, maxRange);
float num2 = 1f;
foreach (Player item in playersInRadius)
{
for (int j = 0; j < items.Count; j++)
{
if (!(itemsMultipliers[j] >= num2) && (!differentRanges || !(Vector3.Distance(((Component)item).transform.position, ((Component)__instance).transform.position) >= itemsRange[j])) && CheckItem(item, items[j]))
{
num2 = itemsMultipliers[j];
Log("Modify damage by " + itemsMultipliers[j] + " due to item " + items[j].ToString());
break;
}
}
}
num *= num2;
if (playersInRadius.Count > 1)
{
Log("Applying best item multiplier found: " + num2);
}
}
if (num != 1f)
{
Log("Applied total damage factor of '" + num + "' from category name '" + name + "' which has the id '" + id + "'.");
}
return num;
}
public float ComputeAutoRepair(WearNTear __instance)
{
//IL_0018: Unknown result type (might be due to invalid IL or missing references)
//IL_0066: Unknown result type (might be due to invalid IL or missing references)
//IL_0071: Unknown result type (might be due to invalid IL or missing references)
float num = 0f;
if (numRepairItems > 0)
{
List<Player> playersInRadius = GetPlayersInRadius(((Component)__instance).transform.position, maxRange);
foreach (Player item in playersInRadius)
{
for (int i = 0; i < items.Count; i++)
{
if (!(itemsAutoRepairPercentage[i] <= num) && (!differentRanges || !(Vector3.Distance(((Component)item).transform.position, ((Component)__instance).transform.position) >= itemsRange[i])) && CheckItem(item, items[i]))
{
num = itemsAutoRepairPercentage[i];
Log("Autorepair by " + itemsAutoRepairPercentage[i] + "% due to item " + items[i].ToString());
break;
}
}
}
if (playersInRadius.Count > 1)
{
Log("Applying best item auptorepair found: " + num + "%");
}
}
return num;
}
public virtual void Init(StructureDamageTweaks parent)
{
name = ((BaseUnityPlugin)parent).Config.Bind<string>(id, "name", name, "Only used in game with logging enabled. Though I advice naming your categories for clarity").Value;
onlyPlayerStructures = ((BaseUnityPlugin)parent).Config.Bind<bool>(id, "OnlyPlayerStructures", onlyPlayerStructures, "Determines whether this group only applies to structures build by any player. Be aware, that if this is false, this may include beehives or other structures that you actually want to destroy.").Value;
string value = ((BaseUnityPlugin)parent).Config.Bind<string>(id, "GlobalKeysNames", defaultGlobalKeys, "Global keys that apply modifiers. First applicable key will be used. Example values: defeated_eikthyr,defeated_gdking,defeated_bonemass,defeated_dragon,defeated_goblinking. EVA: Killed_HelDemon,Killed_Jotunn,Killed_SvartalfarQueen").Value;
globalKeys = value.Split(new char[1] { ',' }).ToList().ConvertAll((string x) => x.Trim(new char[1] { ' ' }));
globalKeysMultipliers = StringToFloatList(1f, ((BaseUnityPlugin)parent).Config.Bind<string>(id, "GlobalKeysMultipliers", defaultGlobalKeysMultipliers, "Damage multipliers per name. First applicable multiplier will be used. This list should have the same length as 'GlobalKeysNames'. Values that cannot be converted to a float will be ignored. Values below zero would repair a structure and are not adviced. Below one reduces damage to structures. Above one will increase damage.").Value);
StripList(ref globalKeys, globalKeysMultipliers.Count);
value = ((BaseUnityPlugin)parent).Config.Bind<string>(id, "ItemInInventoryRequirements", "Yagluth thing", "'Token name' of items that apply modifiers if in player inventory. See https://github.com/Valheim-Modding/Wiki/wiki/ObjectDB-Table for vanilla items. You can use 'structuredamagetweaks inventory' ingame to print a full list of 'Token names' for what is currently in your inventory. First applicable item with an actuall effect (see below) in this list will be used.").Value;
items = value.Split(new char[1] { ',' }).ToList().ConvertAll((string x) => x.Trim(new char[1] { ' ' }));
itemsMultipliers = StringToFloatList(1f, ((BaseUnityPlugin)parent).Config.Bind<string>(id, "ItemInInventoryMultipliers", "0.5", "Damage multipliers per item. First applicable multiplier that is unequal to '1' will be used. This list should have the same length as 'ItemInInventoryRequirements'. Missing or non-float-convertable ones will be set to '1', i.e. no effect. Values below zero would repair a structure and are not adviced. Below one reduces damage to structures. Above one will increase damage.").Value);
numProtectionItems = itemsMultipliers.Count((float x) => (double)x != 1.0);
FillList(ref itemsMultipliers, items.Count, 1f);
itemsAutoRepairPercentage = StringToFloatList(0f, ((BaseUnityPlugin)parent).Config.Bind<string>(id, "ItemInInventoryAutoRepairPercentage", "5", "Amount of auto-repair per item. All structures in range will be repaired by this percentage every '[AutoRepair]Timer' seconds. First applicable non-zero value will be used. This list should have the same length as 'ItemInInventoryRequirements', missing entries will be set to zero, i.e. no effect.").Value);
numRepairItems = itemsAutoRepairPercentage.Count((float x) => x != 0f);
FillList(ref itemsAutoRepairPercentage, items.Count, 0f);
itemsRange = StringToFloatList(10f, ((BaseUnityPlugin)parent).Config.Bind<string>(id, "ItemInInventoryRange", "100", "Range per item.Checked per structure.This is a restriction as to when an item is 'applicable', see above.This list should have the same length as 'ItemInInventoryRequirements', missing entries will be set to the maximum given range, or '10' should the whole list be empty.Different items with different ranges can work together if ordered correctly: Put strong short range items first.").Value);
if (itemsRange.Count > 0)
{
maxRange = itemsRange.Max();
}
else
{
maxRange = 100f;
}
FillList(ref itemsRange, items.Count, maxRange);
differentRanges = maxRange != itemsRange.Min();
}
}
public class NameCategory : Category
{
public List<string> structureNames;
public override bool IsInCategory(WearNTear __instance)
{
if (!base.IsInCategory(__instance))
{
return false;
}
return structureNames.IndexOf(((Object)((Component)__instance).gameObject).name.ToLower().Replace("(clone)", "")) != -1;
}
public override void Init(StructureDamageTweaks parent)
{
string value = ((BaseUnityPlugin)parent).Config.Bind<string>(id, "StructureName", defaultIdentifiers, "Names of the structure(s) in this category.").Value;
structureNames = value.Split(new char[1] { ',' }).ToList().ConvertAll((string x) => x.ToLower().Replace("(clone)", "").Trim(new char[1] { ' ' }));
base.Init(parent);
}
}
public class MaterialCategory : Category
{
public List<MaterialType> types = new List<MaterialType>();
public override bool IsInCategory(WearNTear __instance)
{
//IL_0012: Unknown result type (might be due to invalid IL or missing references)
if (!base.IsInCategory(__instance))
{
return false;
}
return types.IndexOf(__instance.m_materialType) != -1;
}
public override void Init(StructureDamageTweaks parent)
{
using (List<string>.Enumerator enumerator = ((BaseUnityPlugin)parent).Config.Bind<string>(id, "MaterialTypes", defaultIdentifiers, "Names of the material(s) in this category. Possibly values: Wood, Stone, Iron, HardWood").Value.Split(new char[1] { ',' }).ToList().ConvertAll((string x) => x.ToLower())
.GetEnumerator())
{
while (enumerator.MoveNext())
{
switch (enumerator.Current)
{
case "wood":
types.Add((MaterialType)0);
break;
case "stone":
types.Add((MaterialType)1);
break;
case "iron":
types.Add((MaterialType)2);
break;
case "hardwood":
types.Add((MaterialType)3);
break;
}
}
}
base.Init(parent);
}
}
[HarmonyPatch(typeof(WearNTear), "ApplyDamage")]
public static class StructureDamage
{
private static bool Prefix(ref WearNTear __instance, ref float damage)
{
Log("Pre change structure damage: " + damage);
foreach (Category category in categories)
{
if (category.IsInCategory(__instance))
{
damage *= category.ComputeMultiplier(ref __instance);
break;
}
}
Log("Post change structure damage: " + damage);
return true;
}
}
[HarmonyPatch(typeof(WearNTear), "RPC_Damage")]
private static class TamedStructureDamage
{
private static void Prefix(ref HitData hit, Piece ___m_piece)
{
if (preventTamedDamage.Value && hit != null)
{
Character attacker = hit.GetAttacker();
if ((Object)(object)attacker != (Object)null && attacker.IsTamed() && (preventTamedDamageNonPlayer.Value || ((Object)(object)___m_piece != (Object)null && ___m_piece.GetCreator() != 0L)))
{
((DamageTypes)(ref hit.m_damage)).Modify(0f);
}
}
}
}
[HarmonyPatch(typeof(WearNTear), "UpdateWear")]
private static class RainDamageRemoval
{
private static void Prefix(float time, WearNTear __instance)
{
if (Object.op_Implicit((Object)(object)__instance) && preventRainDamage.Value)
{
__instance.m_noRoofWear = false;
}
}
}
[HarmonyPatch(typeof(WearNTear), "HaveRoof")]
private static class RainDamageRemovalB
{
public static void Postfix(ref bool __result)
{
if (preventRainDamage.Value)
{
__result = true;
}
}
}
[HarmonyPatch(typeof(Terminal), "InputText")]
private static class InputText_Patch
{
private static bool Prefix(Terminal __instance)
{
string text = ((TMP_InputField)__instance.m_input).text.ToLower();
if (text.StartsWith("structuredamagetweaks"))
{
if (text.ToLower().Equals("structuredamagetweaks reset"))
{
_instance.Reload();
Traverse.Create((object)__instance).Method("AddString", new object[1] { text }).GetValue();
Traverse.Create((object)__instance).Method("AddString", new object[1] { "Structure Damage Tweaks config reloaded" }).GetValue();
}
else if (text.ToLower().Equals("structuredamagetweaks inventory"))
{
Inventory inventory = ((Humanoid)Player.m_localPlayer).GetInventory();
foreach (ItemData item in ((inventory != null) ? inventory.GetAllItems() : null) ?? new List<ItemData>(0))
{
string text2 = "Item Token Name: '" + item.m_shared.m_name + "'";
((BaseUnityPlugin)_instance).Logger.LogInfo((object)text2);
}
Traverse.Create((object)__instance).Method("AddString", new object[1] { "Finished printing inventory" }).GetValue();
}
else
{
Traverse.Create((object)__instance).Method("AddString", new object[1] { "Incorrect argument. Possible: with keyword 'reset': Reload config of Stucture DamageTweaks. With keyword 'inventory': Display item names currently in your inventory (for use in config file)" }).GetValue();
}
return false;
}
return true;
}
}
[HarmonyPatch(typeof(Terminal), "InitTerminal")]
public static class TerminalInitConsole_Patch
{
private static void Postfix()
{
//IL_0014: Unknown result type (might be due to invalid IL or missing references)
new ConsoleCommand("structuredamagetweaks", "with keyword 'reset': Reload config of Stucture DamageTweaks. With keyword 'inventory': Display item names currently in your inventory (for use in config file)", (ConsoleEventFailable)null, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
}
}
public static ConfigEntry<bool> preventRainDamage;
public static ConfigEntry<bool> preventTamedDamage;
public static ConfigEntry<bool> preventTamedDamageNonPlayer;
public const string PluginId = "StructureDamageTweaks";
private static StructureDamageTweaks _instance;
private static ConfigEntry<bool> _loggingEnabled;
public static List<Category> categories;
public uint autoRepairTimer;
private Coroutine autoRepairRoutine;
private Harmony _harmony;
public static void Log(string message)
{
if (_loggingEnabled.Value)
{
((BaseUnityPlugin)_instance).Logger.LogInfo((object)message);
}
}
public static void LogWarning(string message)
{
if (_loggingEnabled.Value)
{
((BaseUnityPlugin)_instance).Logger.LogWarning((object)message);
}
}
public static void LogError(string message)
{
((BaseUnityPlugin)_instance).Logger.LogError((object)message);
}
private void Awake()
{
_instance = this;
_harmony = Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), "StructureDamageTweaks");
categories = new List<Category>();
Init();
}
public void Init()
{
_loggingEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Logging", "Logging Enabled", false, "Enable logging. Please be aware, that enabling this together with auto repair will slow your game each time auto repair is performed if you are in a region with many instances that are in need of repairs.");
preventTamedDamage = ((BaseUnityPlugin)this).Config.Bind<bool>("TamedCreatures", "PreventTamedDamage", true, "If enabled, tamed creatures do no damage to player created structures.");
preventTamedDamageNonPlayer = ((BaseUnityPlugin)this).Config.Bind<bool>("TamedCreatures", "NonPlayerStructures", false, "If enabled, extends PreventTamedDamage to non-player-structures.");
preventRainDamage = ((BaseUnityPlugin)this).Config.Bind<bool>("Rain", "PreventRainDamage", false, "If enabled, disables rain damaging any parts, including ones that normally take damaged unless roofed.");
uint value = ((BaseUnityPlugin)this).Config.Bind<uint>("Category", "NumberOfCategories", 1u, "Number of additional categories for which damage reductions may be defined. Remark: You can use a category to exclude structures from another category. Each structure can always be in only one category and they are tested zero first, ..., default last").Value;
autoRepairTimer = ((BaseUnityPlugin)this).Config.Bind<uint>("AutoRepair", "Timer", 120u, "Timer in seconds on how often auto repair is applied if the player has an according item (see in categories). Disabled if value is zero. Low values not adviced.").Value;
for (uint num = 0u; num < value; num++)
{
string text = "Category" + num;
string value2 = ((BaseUnityPlugin)this).Config.Bind<string>(text, "type", "structureName", "What is used to determine if a structure in inside this category. Options: 'structureName', 'material', 'none'. In '[CategoryDefault]' this is always 'none'.").Value;
if (value2.ToLower() == "structurename")
{
categories.Add(new NameCategory
{
id = text
});
}
else if (value2.ToLower() == "material")
{
categories.Add(new MaterialCategory
{
id = text
});
}
else if (value2.ToLower() == "none")
{
categories.Add(new Category
{
id = text
});
}
else
{
LogError("Invalid type");
}
}
if (categories.Count > 0)
{
categories[0].name = "Ships";
categories[0].defaultIdentifiers = "raft,karve,vikingship,littleboat,cargoship,bigcargoship";
categories[0].defaultGlobalKeys = "Killed_HelDemon,Killed_Jotunn,Killed_SvartalfarQueen,defeated_goblinking";
categories[0].defaultGlobalKeysMultipliers = "0.01,0.2,0.3,0.5";
}
categories.Add(new Category
{
id = "CategoryDefault",
name = "Default",
defaultGlobalKeys = "Killed_HelDemon,Killed_Jotunn,Killed_SvartalfarQueen,defeated_goblinking",
defaultGlobalKeysMultipliers = "0.1,0.2,0.3,0.5"
});
foreach (Category category in categories)
{
category.Init(this);
}
if (autoRepairTimer != 0)
{
autoRepairRoutine = ((MonoBehaviour)this).StartCoroutine(DelayedRepair());
}
}
private void OnDestroy()
{
categories.Clear();
categories = null;
_instance = null;
Harmony harmony = _harmony;
if (harmony != null)
{
harmony.UnpatchSelf();
}
}
public void Reload()
{
((BaseUnityPlugin)this).Config.Reload();
categories.Clear();
((MonoBehaviour)this).StopCoroutine(autoRepairRoutine);
Init();
}
public static List<Player> GetPlayersInRadius(Vector3 point, float range)
{
//IL_0021: Unknown result type (might be due to invalid IL or missing references)
//IL_0026: Unknown result type (might be due to invalid IL or missing references)
List<Player> list = new List<Player>();
foreach (Player allPlayer in Player.GetAllPlayers())
{
if (Vector3.Distance(((Component)allPlayer).transform.position, point) < range)
{
list.Add(allPlayer);
}
}
return list;
}
public static bool CheckItem(Player player, string itemString)
{
Inventory inventory = ((Humanoid)player).GetInventory();
foreach (ItemData item in ((inventory != null) ? inventory.GetAllItems() : null) ?? new List<ItemData>(0))
{
if (item.m_shared.m_name == itemString)
{
return true;
}
}
return false;
}
public static List<float> StringToFloatList(float defaultVal, string inString)
{
List<float> list = new List<float>();
foreach (string item in inString.Split(new char[1] { ',' }).ToList())
{
if (float.TryParse(item, NumberStyles.Any, Category.cultureInfo, out var result))
{
list.Add(result);
}
else
{
list.Add(defaultVal);
}
}
return list;
}
public static void StripList<T>(ref List<T> l, int len)
{
if (l.Count > len)
{
l.RemoveRange(len, l.Count - len);
}
}
public static void FillList<T>(ref List<T> l, int len, T filler)
{
if (l.Count < len)
{
l.InsertRange(l.Count, Enumerable.Repeat(filler, len - l.Count));
}
}
private static object GetInstanceField<T>(T instance, string fieldName)
{
FieldInfo field = typeof(T).GetField(fieldName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
if (field != null)
{
return field.GetValue(instance);
}
return null;
}
private IEnumerator DelayedRepair()
{
while (true)
{
if (Object.op_Implicit((Object)(object)ZNetScene.instance))
{
List<WearNTear> allInstances = WearNTear.GetAllInstances();
if (allInstances.Count > 0)
{
foreach (WearNTear item in allInstances)
{
if (!Object.op_Implicit((Object)(object)item) || (Object)(object)((Component)item).GetComponent<Piece>() == (Object)null)
{
continue;
}
ZNetView val = (ZNetView)GetInstanceField<WearNTear>(item, "m_nview");
if ((Object)(object)val == (Object)null)
{
continue;
}
float @float = val.GetZDO().GetFloat("health", 0f);
if (@float == 0f || @float == item.m_health)
{
continue;
}
foreach (Category category in categories)
{
if (!category.IsInCategory(item))
{
continue;
}
float num = category.ComputeAutoRepair(item);
if (@float != 0f)
{
@float += item.m_health / 100f * num;
if (@float > item.m_health)
{
@float = item.m_health;
}
val.GetZDO().Set("health", @float);
}
break;
}
}
}
}
yield return (object)new WaitForSecondsRealtime((float)autoRepairTimer);
}
}
}
}