Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of TarCoating v1.0.0
TarCoating.dll
Decompiled a day ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using JetBrains.Annotations; using Microsoft.CodeAnalysis; using ServerSync; using TMPro; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: IgnoresAccessChecksTo("assembly_valheim")] [assembly: AssemblyCompany("TarCoating")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("TarCoating")] [assembly: AssemblyTitle("TarCoating")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace TarCoating { internal static class ApplySound { private static readonly MethodInfo CreateMethod = AccessTools.Method(typeof(EffectList), "Create", new Type[6] { typeof(Vector3), typeof(Quaternion), typeof(Transform), typeof(float), typeof(int), typeof(ZDOID) }, (Type[])null) ?? AccessTools.Method(typeof(EffectList), "Create", new Type[5] { typeof(Vector3), typeof(Quaternion), typeof(Transform), typeof(float), typeof(int) }, (Type[])null) ?? AccessTools.Method(typeof(EffectList), "Create", new Type[2] { typeof(Vector3), typeof(Quaternion) }, (Type[])null); private static EffectList _effect; internal static void PlayAt(Vector3 position) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) Play(GetEffect(), position); } internal static void Play(EffectList effect, Vector3 position) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) if (effect == null || CreateMethod == null) { return; } try { ParameterInfo[] parameters = CreateMethod.GetParameters(); object[] parameters2 = ((parameters.Length >= 6) ? new object[6] { position, Quaternion.identity, null, 1f, -1, ZDOID.None } : ((parameters.Length < 5) ? new object[2] { position, Quaternion.identity } : new object[5] { position, Quaternion.identity, null, 1f, -1 })); CreateMethod.Invoke(effect, parameters2); } catch (Exception ex) { TarCoatingPlugin.Log.LogWarning((object)("Could not play effect: " + ex.Message)); } } private static EffectList GetEffect() { //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Expected O, but got Unknown //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Expected O, but got Unknown if (_effect != null) { return _effect; } if ((Object)(object)ObjectDB.instance != (Object)null) { GameObject itemPrefab = ObjectDB.instance.GetItemPrefab("Hoe"); EffectList val = (((Object)(object)itemPrefab != (Object)null) ? itemPrefab.GetComponent<ItemDrop>() : null)?.m_itemData?.m_shared?.m_buildEffect; if (val != null && val.m_effectPrefabs.Length != 0) { _effect = val; return _effect; } } if ((Object)(object)ZNetScene.instance == (Object)null) { return null; } GameObject prefab = ZNetScene.instance.GetPrefab("sfx_build_hoe"); if ((Object)(object)prefab == (Object)null) { TarCoatingPlugin.Log.LogWarning((object)"Could not find tar apply sound (sfx_build_hoe)."); return null; } EffectList val2 = new EffectList(); val2.m_effectPrefabs = (EffectData[])(object)new EffectData[1] { new EffectData { m_prefab = prefab } }; _effect = val2; return _effect; } } internal static class Coating { [HarmonyPatch(typeof(Humanoid), "UseItem")] private static class UseItemPatch { private static void Prefix(ItemData item) { TarItem.EnsureItemConfigured(item); } } [HarmonyPatch(typeof(Player), "UseHotbarItem")] private static class UseHotbarItemPatch { private static void Prefix(Player __instance, int index) { TarItem.EnsureItemConfigured(((Humanoid)__instance).GetInventory().GetItemAt(index - 1, 0)); } } [HarmonyPatch(typeof(Player), "OnSpawned")] private static class PlayerSpawnedPatch { private static void Postfix(Player __instance) { if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer)) { RepairInventoryTar(__instance); } } } [HarmonyPatch(typeof(Humanoid), "StartAttack")] private static class StartAttackPatch { private static bool Prefix(Humanoid __instance, Character target, bool secondaryAttack) { if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer) { return true; } if (IsHoldingTar(__instance)) { if (!secondaryAttack) { ItemData rightItem = __instance.GetRightItem(); TryApplyOnce(__instance, __instance.GetInventory(), rightItem); } return false; } if (!secondaryAttack && TarConfig.AllowScrape.Value && IsHoldingKnife(__instance) && ((Character)__instance).IsBlocking() && TryScrapeOnce(__instance, __instance.GetInventory())) { _suppressStructureDamageUntil = Time.time + 0.75f; } return true; } } [HarmonyPatch(typeof(WearNTear), "Damage")] private static class WearNTearDamagePatch { private static bool Prefix(HitData hit) { if (Time.time > _suppressStructureDamageUntil) { return true; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || !IsHoldingKnife((Humanoid)(object)localPlayer)) { return true; } Character val = ((hit != null) ? hit.GetAttacker() : null); if ((Object)(object)val != (Object)null && (Object)(object)val != (Object)(object)localPlayer) { return true; } return false; } } [HarmonyPatch(typeof(Player), "Update")] private static class PlayerUpdatePatch { private static void Postfix(Player __instance) { if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer)) { RecoverLocalPlayerState(__instance); CleanupDepletedTar(__instance); } } } [HarmonyPatch(typeof(WearNTear), "IsWet")] private static class IsWetPatch { private static void Postfix(WearNTear __instance, ref bool __result) { if (__result && IsCoated(__instance)) { __result = false; } } } [HarmonyPatch(typeof(Hud), "UpdateCrosshair")] private static class HudCrosshairPatch { private static readonly FieldInfo HoverNameField = AccessTools.Field(typeof(Hud), "m_hoverName"); private static readonly PropertyInfo HoverTextProperty = HoverNameField?.FieldType.GetProperty("text", BindingFlags.Instance | BindingFlags.Public); private static readonly FieldInfo CrosshairField = AccessTools.Field(typeof(Hud), "m_crosshair"); private static readonly PropertyInfo CrosshairColorProperty = CrosshairField?.FieldType.GetProperty("color", BindingFlags.Instance | BindingFlags.Public); private static void Postfix(Hud __instance, Player player) { //IL_010e: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player != (Object)(object)Player.m_localPlayer || HoverNameField == null || HoverTextProperty == null) { return; } GameObject hoverObject = ((Humanoid)player).GetHoverObject(); if ((Object)(object)hoverObject == (Object)null) { return; } string text = BuildStructureHoverLine(hoverObject.GetComponentInParent<WearNTear>(), (Humanoid)(object)player); if (string.IsNullOrEmpty(text)) { return; } object value = HoverNameField.GetValue(__instance); if (value == null) { return; } string text2 = (HoverTextProperty.GetValue(value) as string) ?? string.Empty; string value2 = Localization.instance.Localize("$tarcoating_coated"); string value3 = Localization.instance.Localize("$tarcoating_already_coated"); if (!text2.Contains(value2) && !text2.Contains(value3)) { string text3 = (string.IsNullOrEmpty(text2) ? text : (text2 + "\n" + text)); HoverTextProperty.SetValue(value, text3); if (text3.Length > 0 && CrosshairField != null && CrosshairColorProperty != null) { object value4 = CrosshairField.GetValue(__instance); CrosshairColorProperty.SetValue(value4, Color.yellow); } } } } internal const string CoatedKey = "tar_coated"; private const string BeeswaxSealedKey = "beeswax_sealed"; private static readonly MethodInfo CharacterMessageMethod = FindLongestNamedMethod(typeof(Character), "Message"); private static readonly MethodInfo HudShowMessageMethod = FindLongestNamedMethod(typeof(MessageHud), "ShowMessage"); private static int _lastApplyFrame = -1; private static bool _recoveredLocalPlayerState; private static float _suppressStructureDamageUntil; internal static string BuildStructureHoverLine(WearNTear wear, Humanoid player) { if ((Object)(object)wear == (Object)null || !TarConfig.IsHoverTextAllowed()) { return null; } bool flag = (Object)(object)player != (Object)null && IsHoldingTar(player); if (IsCoated(wear)) { if (flag) { if (!TarConfig.ShowAlreadyTarredLabel.Value) { return null; } return "[<color=#8B5A2B>★</color>] " + Localization.instance.Localize("$tarcoating_already_coated"); } if (!TarConfig.ShowTarredLabel.Value) { return null; } return "[<color=#8B5A2B>★</color>] " + Localization.instance.Localize("$tarcoating_coated"); } if (!CanCoat(wear) || IsWaxed(wear) || !flag || !TarConfig.ShowApplyLabel.Value) { return null; } return Localization.instance.Localize("[<color=yellow><b>$KEY_Attack</b></color>] $tarcoating_apply"); } private static void RepairInventoryTar(Player player) { foreach (ItemData allItem in ((Humanoid)player).GetInventory().GetAllItems()) { TarItem.EnsureItemConfigured(allItem); } } private static void RecoverLocalPlayerState(Player player) { if (!_recoveredLocalPlayerState) { _recoveredLocalPlayerState = true; RepairInventoryTar(player); ItemData rightItem = ((Humanoid)player).GetRightItem(); if (TarItem.IsTarItem(rightItem)) { TarItem.EnsureItemConfigured(rightItem); ((Humanoid)player).UnequipItem(rightItem, false); TarItem.EnsureItemConfigured(rightItem); ((Humanoid)player).EquipItem(rightItem, false); } } } private static bool TryApplyOnce(Humanoid user, Inventory inventory, ItemData item) { if (Time.frameCount == _lastApplyFrame) { return false; } if (!TryApply(user, inventory, item)) { return false; } _lastApplyFrame = Time.frameCount; return true; } internal static bool TryApply(Humanoid user, Inventory inventory, ItemData item) { //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)user == (Object)null || inventory == null || item == null) { return false; } GameObject hoverObject = user.GetHoverObject(); if ((Object)(object)hoverObject == (Object)null) { return false; } WearNTear componentInParent = hoverObject.GetComponentInParent<WearNTear>(); if (!CanCoat(componentInParent) || IsCoated(componentInParent)) { return false; } if (IsWaxed(componentInParent)) { ShowMessage((Character)(object)user, (MessageType)2, Localization.instance.Localize("$tarcoating_already_waxed")); return true; } ZNetView component = ((Component)componentInParent).GetComponent<ZNetView>(); if ((Object)(object)component == (Object)null || !component.IsValid()) { return false; } if (!PrivateArea.CheckAccess(((Component)componentInParent).transform.position, 0f, true, false)) { ShowMessage((Character)(object)user, (MessageType)2, "$msg_privatearea"); return true; } int tarPerPiece = TarConfig.GetTarPerPiece(); string name = item.m_shared.m_name; if (inventory.CountItems(name, -1, false) < tarPerPiece) { ShowMessage((Character)(object)user, (MessageType)2, Localization.instance.Localize("$tarcoating_not_enough")); return true; } if (tarPerPiece == 1) { inventory.RemoveItem(item, 1); } else { inventory.RemoveItem(name, tarPerPiece, -1, false); } component.ClaimOwnership(); component.GetZDO().Set("tar_coated", 1); StructureVisuals.Apply(componentInParent); ApplySound.PlayAt(((Component)componentInParent).transform.position); ShowMessage((Character)(object)user, (MessageType)2, Localization.instance.Localize("$tarcoating_coated")); ((Character)user).m_zanim.SetTrigger("interact"); CleanupAfterApply(user, inventory, item); return true; } private static bool TryScrapeOnce(Humanoid user, Inventory inventory) { if (Time.frameCount == _lastApplyFrame) { return false; } if (!TryScrape(user, inventory)) { return false; } _lastApplyFrame = Time.frameCount; return true; } private static bool TryScrape(Humanoid user, Inventory inventory) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)user == (Object)null || inventory == null) { return false; } GameObject hoverObject = user.GetHoverObject(); if ((Object)(object)hoverObject == (Object)null) { return false; } WearNTear componentInParent = hoverObject.GetComponentInParent<WearNTear>(); if (!IsCoated(componentInParent)) { return false; } ZNetView component = ((Component)componentInParent).GetComponent<ZNetView>(); if ((Object)(object)component == (Object)null || !component.IsValid()) { return false; } if (!PrivateArea.CheckAccess(((Component)componentInParent).transform.position, 0f, true, false)) { ShowMessage((Character)(object)user, (MessageType)2, "$msg_privatearea"); return true; } if (!RefundTar(user, inventory)) { return false; } component.ClaimOwnership(); component.GetZDO().Set("tar_coated", 0); StructureVisuals.Revert(componentInParent); ApplySound.PlayAt(((Component)componentInParent).transform.position); ShowMessage((Character)(object)user, (MessageType)2, Localization.instance.Localize("$tarcoating_scraped")); return true; } private static bool RefundTar(Humanoid user, Inventory inventory) { //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ObjectDB.instance != (Object)null) { TarItem.Register(ObjectDB.instance); } if ((Object)(object)TarItem.ItemDrop == (Object)null || (Object)(object)TarItem.Prefab == (Object)null) { TarCoatingPlugin.Log.LogWarning((object)"Tar refund failed: vanilla Tar prefab was not found."); return false; } ItemData val = TarItem.ItemDrop.m_itemData.Clone(); val.m_stack = 1; val.m_dropPrefab = TarItem.Prefab; val.m_worldLevel = (byte)Game.m_worldLevel; TarItem.EnsureItemConfigured(val); if (inventory.AddItem(val)) { return true; } Vector3 val2 = ((Component)user).transform.position + Vector3.up; GameObject val3 = Object.Instantiate<GameObject>(TarItem.Prefab, val2, ((Component)user).transform.rotation); ItemDrop obj = (((Object)(object)val3 != (Object)null) ? val3.GetComponent<ItemDrop>() : null); if (obj != null) { obj.SetStack(1); } return true; } private static MethodInfo FindLongestNamedMethod(Type type, string name) { MethodInfo methodInfo = null; foreach (MethodInfo declaredMethod in AccessTools.GetDeclaredMethods(type)) { if (!(declaredMethod.Name != name) && (methodInfo == null || declaredMethod.GetParameters().Length > methodInfo.GetParameters().Length)) { methodInfo = declaredMethod; } } return methodInfo; } private static object[] BuildMessageArgs(MethodInfo method, MessageType type, string msg) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) ParameterInfo[] parameters = method.GetParameters(); object[] array = new object[parameters.Length]; if (parameters.Length != 0) { array[0] = type; } if (parameters.Length > 1) { array[1] = msg; } for (int i = 2; i < parameters.Length; i++) { Type parameterType = parameters[i].ParameterType; if (parameterType == typeof(int)) { array[i] = 0; } else if (parameterType == typeof(bool)) { array[i] = false; } else { array[i] = null; } } return array; } private static void ShowMessage(Character user, MessageType type, string msg) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(msg)) { return; } try { if (CharacterMessageMethod != null && (Object)(object)user != (Object)null) { CharacterMessageMethod.Invoke(user, BuildMessageArgs(CharacterMessageMethod, type, msg)); } else if (HudShowMessageMethod != null && (Object)(object)MessageHud.instance != (Object)null) { HudShowMessageMethod.Invoke(MessageHud.instance, BuildMessageArgs(HudShowMessageMethod, type, msg)); } } catch (Exception ex) { TarCoatingPlugin.Log.LogWarning((object)("Could not show message: " + ex.Message)); } } private static void CleanupAfterApply(Humanoid user, Inventory inventory, ItemData item) { if (item.m_stack <= 0 || !inventory.ContainsItem(item)) { user.UnequipItem(item, true); } } private static void CleanupDepletedTar(Player player) { ItemData rightItem = ((Humanoid)player).GetRightItem(); if (TarItem.IsTarItem(rightItem) && (rightItem.m_stack <= 0 || !((Humanoid)player).GetInventory().ContainsItem(rightItem))) { ((Humanoid)player).UnequipItem(rightItem, true); } } internal static bool IsCoated(WearNTear wear) { if ((Object)(object)wear == (Object)null || IsCraftingStationOrExtension(wear)) { return false; } ZNetView component = ((Component)wear).GetComponent<ZNetView>(); if ((Object)(object)component != (Object)null && component.IsValid()) { return component.GetZDO().GetInt("tar_coated", 0) == 1; } return false; } private static bool IsWaxed(WearNTear wear) { ZNetView val = (((Object)(object)wear != (Object)null) ? ((Component)wear).GetComponent<ZNetView>() : null); if ((Object)(object)val != (Object)null && val.IsValid()) { return val.GetZDO().GetInt("beeswax_sealed", 0) == 1; } return false; } internal static void ClearDisallowedCoating(WearNTear wear) { if ((Object)(object)wear == (Object)null || !IsCraftingStationOrExtension(wear)) { return; } ZNetView component = ((Component)wear).GetComponent<ZNetView>(); if ((Object)(object)component == (Object)null || !component.IsValid()) { return; } ZDO zDO = component.GetZDO(); if (zDO.GetInt("tar_coated", 0) == 1) { StructureVisuals.Revert(wear); if (component.IsOwner()) { zDO.Set("tar_coated", 0); } } } private static bool IsCraftingStationOrExtension(WearNTear wear) { if (!((Object)(object)((Component)wear).GetComponentInParent<CraftingStation>() != (Object)null)) { return (Object)(object)((Component)wear).GetComponentInParent<StationExtension>() != (Object)null; } return true; } private static bool CanCoat(WearNTear wear) { //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Invalid comparison between Unknown and I4 if ((Object)(object)wear == (Object)null) { return false; } if (IsCraftingStationOrExtension(wear)) { return false; } if ((Object)(object)((Component)wear).GetComponent<Beehive>() != (Object)null || (Object)(object)((Component)wear).GetComponentInParent<Beehive>() != (Object)null) { return false; } string prefabName = Utils.GetPrefabName(((Object)((Component)wear).gameObject).name); if (prefabName.IndexOf("bird", StringComparison.OrdinalIgnoreCase) >= 0 || prefabName.IndexOf("feather", StringComparison.OrdinalIgnoreCase) >= 0 || prefabName.Equals("Beehive", StringComparison.OrdinalIgnoreCase)) { return false; } switch (((object)Unsafe.As<MaterialType, MaterialType>(ref wear.m_materialType)/*cast due to .constrained prefix*/).ToString()) { case "Wood": case "HardWood": case "Timberwood": return true; default: { if ((int)wear.m_materialType != 2) { return false; } Piece piece = wear.m_piece; if ((Object)(object)piece == (Object)null) { return false; } string prefabName2 = Utils.GetPrefabName(((Object)((Component)piece).gameObject).name); if (!prefabName2.StartsWith("woodiron", StringComparison.OrdinalIgnoreCase) && !prefabName2.StartsWith("wood_iron", StringComparison.OrdinalIgnoreCase)) { return prefabName2.StartsWith("ironwood", StringComparison.OrdinalIgnoreCase); } return true; } } } private static bool IsHoldingTar(Humanoid humanoid) { ItemData rightItem = humanoid.GetRightItem(); if (!TarItem.IsTarItem(rightItem)) { return false; } TarItem.EnsureItemConfigured(rightItem); if (rightItem.m_stack > 0) { return humanoid.GetInventory().ContainsItem(rightItem); } return false; } internal static bool IsHoldingKnife(Humanoid humanoid) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Invalid comparison between Unknown and I4 ItemData rightItem = humanoid.GetRightItem(); if (rightItem?.m_shared != null) { return (int)rightItem.m_shared.m_skillType == 2; } return false; } } internal sealed class ConfigurationManagerAttributes { public string DispName; public int? Order; public bool? HideSettingName; public bool? HideDefaultButton; public bool? ReadOnly; public Action<ConfigEntryBase> CustomDrawer; } internal static class LocalizationSetup { internal const string CoatedLabel = "$tarcoating_coated"; internal const string AlreadyCoatedLabel = "$tarcoating_already_coated"; internal const string ApplyLabel = "$tarcoating_apply"; internal const string ScrapeLabel = "$tarcoating_scrape"; internal const string ScrapedLabel = "$tarcoating_scraped"; internal const string AlreadyWaxedLabel = "$tarcoating_already_waxed"; internal const string NotEnoughLabel = "$tarcoating_not_enough"; private static readonly MethodInfo AddWordMethod = AccessTools.Method(typeof(Localization), "AddWord", (Type[])null, (Type[])null); private static readonly Dictionary<string, Dictionary<string, string>> Translations = new Dictionary<string, Dictionary<string, string>> { ["English"] = new Dictionary<string, string> { ["tarcoating_coated"] = "Tarred", ["tarcoating_already_coated"] = "Already tarred", ["tarcoating_apply"] = "Apply tar", ["tarcoating_scrape"] = "Scrape Tar", ["tarcoating_scraped"] = "Scraped Tar", ["tarcoating_already_waxed"] = "Already waxed", ["tarcoating_not_enough"] = "Not enough tar" }, ["German"] = new Dictionary<string, string> { ["tarcoating_coated"] = "Geteert", ["tarcoating_already_coated"] = "Bereits geteert", ["tarcoating_apply"] = "Teer auftragen", ["tarcoating_scrape"] = "Teer abkratzen", ["tarcoating_scraped"] = "Teer abgekratzt", ["tarcoating_already_waxed"] = "Bereits gewachst", ["tarcoating_not_enough"] = "Nicht genug Teer" }, ["French"] = new Dictionary<string, string> { ["tarcoating_coated"] = "Goudronné", ["tarcoating_already_coated"] = "Déjà goudronné", ["tarcoating_apply"] = "Appliquer le goudron", ["tarcoating_scrape"] = "Gratter le goudron", ["tarcoating_scraped"] = "Goudron gratté", ["tarcoating_already_waxed"] = "Déjà ciré", ["tarcoating_not_enough"] = "Pas assez de goudron" }, ["Italian"] = new Dictionary<string, string> { ["tarcoating_coated"] = "Catramato", ["tarcoating_already_coated"] = "Già catramato", ["tarcoating_apply"] = "Applica catrame", ["tarcoating_scrape"] = "Raschia catrame", ["tarcoating_scraped"] = "Catrame raschiato", ["tarcoating_already_waxed"] = "Già cerato", ["tarcoating_not_enough"] = "Catrame insufficiente" }, ["Spanish"] = new Dictionary<string, string> { ["tarcoating_coated"] = "Alquitranado", ["tarcoating_already_coated"] = "Ya alquitranado", ["tarcoating_apply"] = "Aplicar alquitrán", ["tarcoating_scrape"] = "Raspar alquitrán", ["tarcoating_scraped"] = "Alquitrán raspado", ["tarcoating_already_waxed"] = "Ya encerado", ["tarcoating_not_enough"] = "No hay suficiente alquitrán" }, ["Russian"] = new Dictionary<string, string> { ["tarcoating_coated"] = "Просмолено", ["tarcoating_already_coated"] = "Уже просмолено", ["tarcoating_apply"] = "Нанести смолу", ["tarcoating_scrape"] = "Соскоблить смолу", ["tarcoating_scraped"] = "Смола соскоблена", ["tarcoating_already_waxed"] = "Уже пропитано воском", ["tarcoating_not_enough"] = "Недостаточно смолы" }, ["Polish"] = new Dictionary<string, string> { ["tarcoating_coated"] = "Smołowane", ["tarcoating_already_coated"] = "Już smołowane", ["tarcoating_apply"] = "Nałóż smołę", ["tarcoating_scrape"] = "Zeskrob smołę", ["tarcoating_scraped"] = "Smoła zeskrobana", ["tarcoating_already_waxed"] = "Już pokryte woskiem", ["tarcoating_not_enough"] = "Za mało smoły" }, ["Portuguese_Brazilian"] = new Dictionary<string, string> { ["tarcoating_coated"] = "Alcatroado", ["tarcoating_already_coated"] = "Já alcatroado", ["tarcoating_apply"] = "Aplicar alcatrão", ["tarcoating_scrape"] = "Raspar alcatrão", ["tarcoating_scraped"] = "Alcatrão raspado", ["tarcoating_already_waxed"] = "Já encerado", ["tarcoating_not_enough"] = "Alcatrão insuficiente" }, ["Turkish"] = new Dictionary<string, string> { ["tarcoating_coated"] = "Katranlanmış", ["tarcoating_already_coated"] = "Zaten katranlanmış", ["tarcoating_apply"] = "Katran sür", ["tarcoating_scrape"] = "Katranı kazı", ["tarcoating_scraped"] = "Katran kazındı", ["tarcoating_already_waxed"] = "Zaten balmumlanmış", ["tarcoating_not_enough"] = "Yeterli katran yok" }, ["Chinese"] = new Dictionary<string, string> { ["tarcoating_coated"] = "已涂焦油", ["tarcoating_already_coated"] = "已经涂过焦油", ["tarcoating_apply"] = "涂抹焦油", ["tarcoating_scrape"] = "刮除焦油", ["tarcoating_scraped"] = "已刮除焦油", ["tarcoating_already_waxed"] = "已经上过蜡", ["tarcoating_not_enough"] = "焦油不足" }, ["Chinese_Trad"] = new Dictionary<string, string> { ["tarcoating_coated"] = "已塗焦油", ["tarcoating_already_coated"] = "已經塗過焦油", ["tarcoating_apply"] = "塗抹焦油", ["tarcoating_scrape"] = "刮除焦油", ["tarcoating_scraped"] = "已刮除焦油", ["tarcoating_already_waxed"] = "已經上過蠟", ["tarcoating_not_enough"] = "焦油不足" }, ["Japanese"] = new Dictionary<string, string> { ["tarcoating_coated"] = "タール塗布済み", ["tarcoating_already_coated"] = "すでにタール塗布済み", ["tarcoating_apply"] = "タールを塗る", ["tarcoating_scrape"] = "タールを削り取る", ["tarcoating_scraped"] = "タールを削り取った", ["tarcoating_already_waxed"] = "すでに蝋引き済み", ["tarcoating_not_enough"] = "タールが足りない" }, ["Korean"] = new Dictionary<string, string> { ["tarcoating_coated"] = "타르 처리됨", ["tarcoating_already_coated"] = "이미 타르 처리됨", ["tarcoating_apply"] = "타르 바르기", ["tarcoating_scrape"] = "타르 긁어내기", ["tarcoating_scraped"] = "타르를 긁어냄", ["tarcoating_already_waxed"] = "이미 왁스 처리됨", ["tarcoating_not_enough"] = "타르가 부족합니다" }, ["Ukrainian"] = new Dictionary<string, string> { ["tarcoating_coated"] = "Просмолено", ["tarcoating_already_coated"] = "Уже просмолено", ["tarcoating_apply"] = "Нанести смолу", ["tarcoating_scrape"] = "Зіскребти смолу", ["tarcoating_scraped"] = "Смолу зіскребено", ["tarcoating_already_waxed"] = "Уже пропитано воском", ["tarcoating_not_enough"] = "Недостатньо смоли" } }; internal static void Apply() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown new Harmony("ModdedWolf.TarCoating.Localization").Patch((MethodBase)AccessTools.Method(typeof(Localization), "SetupLanguage", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(LocalizationSetup), "SetupLanguagePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); if (Localization.instance != null) { RegisterWords(Localization.instance, Localization.instance.GetSelectedLanguage()); } } private static void SetupLanguagePostfix(Localization __instance, string language) { RegisterWords(__instance, language); } private static void RegisterWords(Localization localization, string language) { if (localization == null || AddWordMethod == null) { return; } if (!Translations.TryGetValue(language, out var value)) { value = Translations["English"]; } foreach (KeyValuePair<string, string> item in value) { AddWord(localization, item.Key, item.Value); } } private static void AddWord(Localization localization, string key, string value) { AddWordMethod.Invoke(localization, new object[2] { key, value }); } } internal static class ScrapeKeyHints { [HarmonyPatch(typeof(KeyHints), "UpdateHints")] private static class UpdateHintsPatch { private static void Postfix(KeyHints __instance) { CleanupLegacyTips(__instance); EnsureTips(__instance); if (!((Object)(object)_tipKb == (Object)null) || !((Object)(object)_tipGp == (Object)null)) { bool num = ShouldShow(__instance); bool flag = ZInput.IsGamepadActive(); bool active = num && !flag && (Object)(object)_tipKb != (Object)null; bool active2 = num && flag && (Object)(object)_tipGp != (Object)null; SetTipActive(_tipKb, active); SetTipActive(_tipGp, active2); } } } private const string TipObjectNameKb = "TarCoatingScrapeHintKB"; private const string TipObjectNameGp = "TarCoatingScrapeHintGP"; private const string LegacyTipObjectName = "TarCoatingScrapeHint"; private static GameObject _tipKb; private static GameObject _tipGp; private static int _failedKbId; private static int _failedGpId; private static bool _loggedKbFailure; private static bool _loggedGpFailure; private static bool ShouldShow(KeyHints keyHints) { if ((Object)(object)keyHints == (Object)null || (Object)(object)keyHints.m_combatHints == (Object)null || !keyHints.m_combatHints.activeInHierarchy) { return false; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || !TarConfig.AllowScrape.Value || !Coating.IsHoldingKnife((Humanoid)(object)localPlayer)) { return false; } GameObject hoverObject = ((Humanoid)localPlayer).GetHoverObject(); if ((Object)(object)hoverObject == (Object)null) { return false; } return Coating.IsCoated(hoverObject.GetComponentInParent<WearNTear>()); } private static void EnsureTips(KeyHints keyHints) { if ((Object)(object)keyHints == (Object)null) { return; } if (!IsAlive(_tipKb)) { _tipKb = null; } if (!IsAlive(_tipGp)) { _tipGp = null; } if ((Object)(object)_tipKb == (Object)null && (Object)(object)keyHints.m_primaryAttackKB != (Object)null) { int instanceID = ((Object)keyHints.m_primaryAttackKB).GetInstanceID(); if (instanceID != _failedKbId) { _tipKb = CreateComposedTip(keyHints.m_primaryAttackKB, "TarCoatingScrapeHintKB", gamepad: false); if ((Object)(object)_tipKb == (Object)null) { _failedKbId = instanceID; } else { _failedKbId = 0; } } } if (!((Object)(object)_tipGp == (Object)null) || !((Object)(object)keyHints.m_primaryAttackGP != (Object)null)) { return; } int instanceID2 = ((Object)keyHints.m_primaryAttackGP).GetInstanceID(); if (instanceID2 != _failedGpId) { _tipGp = CreateComposedTip(keyHints.m_primaryAttackGP, "TarCoatingScrapeHintGP", gamepad: true); if ((Object)(object)_tipGp == (Object)null) { _failedGpId = instanceID2; } else { _failedGpId = 0; } } } private static GameObject CreateComposedTip(GameObject attackTip, string name, bool gamepad) { if ((Object)(object)attackTip == (Object)null || (Object)(object)attackTip.transform.parent == (Object)null) { return null; } Transform parent = attackTip.transform.parent; Transform val = parent.Find(name); if ((Object)(object)val != (Object)null && (Object)(object)((Component)val).gameObject != (Object)(object)_tipKb && (Object)(object)((Component)val).gameObject != (Object)(object)_tipGp) { Object.Destroy((Object)(object)((Component)val).gameObject); } GameObject val2 = FindSiblingTip(parent, "Block", name); if ((Object)(object)val2 == (Object)null) { val2 = FindBlockTipByLabel(parent, name); } GameObject val3 = FindKeyBox(val2); GameObject val4 = FindKeyBox(attackTip); if ((Object)(object)val3 == (Object)null || (Object)(object)val4 == (Object)null) { LogLookupFailureOnce(gamepad, (Object)(object)val2 != (Object)null, (Object)(object)val3 != (Object)null, (Object)(object)val4 != (Object)null); return null; } GameObject val5 = Object.Instantiate<GameObject>(attackTip, parent, false); ((Object)val5).name = name; val5.transform.SetAsLastSibling(); TextMeshProUGUI val6 = FindTitleLabel(val5); if ((Object)(object)val6 != (Object)null && Localization.instance != null) { Localization.instance.RemoveTextFromCache((TMP_Text)(object)val6); ((TMP_Text)val6).text = Localization.instance.Localize("$tarcoating_scrape"); } GameObject val7 = FindKeyBox(val5); if ((Object)(object)val7 == (Object)null) { Object.Destroy((Object)(object)val5); LogLookupFailureOnce(gamepad, (Object)(object)val2 != (Object)null, (Object)(object)val3 != (Object)null, attackBox: false); return null; } int siblingIndex = val7.transform.GetSiblingIndex(); GameObject obj = Object.Instantiate<GameObject>(val3, val5.transform, false); ((Object)obj).name = "BlockKey"; obj.SetActive(true); obj.transform.SetSiblingIndex(siblingIndex); GameObject val8 = CreatePlusLabel(val6, val5.transform); if ((Object)(object)val8 != (Object)null) { val8.transform.SetSiblingIndex(siblingIndex + 1); } val5.SetActive(false); return val5; } private static void LogLookupFailureOnce(bool gamepad, bool blockTip, bool blockBox, bool attackBox) { if (gamepad) { if (_loggedGpFailure) { return; } _loggedGpFailure = true; } else { if (_loggedKbFailure) { return; } _loggedKbFailure = true; } TarCoatingPlugin.Log.LogWarning((object)$"Could not find Block/Attack key boxes for scrape tip (blockTip={blockTip}, block={blockBox}, attack={attackBox}, gamepad={gamepad})."); } private static GameObject CreatePlusLabel(TextMeshProUGUI styleSource, Transform parent) { if ((Object)(object)styleSource == (Object)null) { return null; } GameObject val = Object.Instantiate<GameObject>(((Component)styleSource).gameObject, parent, false); ((Object)val).name = "Plus"; TextMeshProUGUI componentInChildren = val.GetComponentInChildren<TextMeshProUGUI>(true); if ((Object)(object)componentInChildren == (Object)null) { Object.Destroy((Object)(object)val); return null; } if (Localization.instance != null) { Localization.instance.RemoveTextFromCache((TMP_Text)(object)componentInChildren); } ((TMP_Text)componentInChildren).text = "+"; return val; } private static GameObject FindSiblingTip(Transform parent, string nameContains, string excludeName) { if ((Object)(object)parent == (Object)null) { return null; } for (int i = 0; i < parent.childCount; i++) { Transform child = parent.GetChild(i); if (!((Object)(object)child == (Object)null) && !(((Object)child).name == excludeName) && ((Object)child).name.IndexOf(nameContains, StringComparison.OrdinalIgnoreCase) >= 0 && ((Object)child).name.IndexOf("TarCoating", StringComparison.OrdinalIgnoreCase) < 0) { return ((Component)child).gameObject; } } return null; } private static GameObject FindBlockTipByLabel(Transform parent, string excludeName) { if ((Object)(object)parent == (Object)null || Localization.instance == null) { return null; } string text = Localization.instance.Localize("$settings_block"); if (string.IsNullOrEmpty(text) || text.StartsWith("$")) { text = "Block"; } for (int i = 0; i < parent.childCount; i++) { Transform child = parent.GetChild(i); if ((Object)(object)child == (Object)null || ((Object)child).name == excludeName || ((Object)child).name.IndexOf("Attack", StringComparison.OrdinalIgnoreCase) >= 0 || ((Object)child).name.IndexOf("Dodge", StringComparison.OrdinalIgnoreCase) >= 0 || ((Object)child).name.IndexOf("Bow", StringComparison.OrdinalIgnoreCase) >= 0 || ((Object)child).name.IndexOf("TarCoating", StringComparison.OrdinalIgnoreCase) >= 0) { continue; } TextMeshProUGUI[] componentsInChildren = ((Component)child).GetComponentsInChildren<TextMeshProUGUI>(true); foreach (TextMeshProUGUI val in componentsInChildren) { if ((Object)(object)val != (Object)null && !string.IsNullOrEmpty(((TMP_Text)val).text) && ((TMP_Text)val).text.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0) { return ((Component)child).gameObject; } } } return null; } private static TextMeshProUGUI FindTitleLabel(GameObject tipRow) { if ((Object)(object)tipRow == (Object)null) { return null; } TextMeshProUGUI component = tipRow.GetComponent<TextMeshProUGUI>(); if ((Object)(object)component != (Object)null) { return component; } TextMeshProUGUI[] componentsInChildren = tipRow.GetComponentsInChildren<TextMeshProUGUI>(true); if (componentsInChildren.Length == 0) { return null; } return componentsInChildren[0]; } private static GameObject FindKeyBox(GameObject tipRow) { if ((Object)(object)tipRow == (Object)null) { return null; } TextMeshProUGUI val = FindTitleLabel(tipRow); Transform directChildBranch = GetDirectChildBranch(tipRow.transform, ((Object)(object)val != (Object)null) ? ((TMP_Text)val).transform : null); for (int i = 0; i < tipRow.transform.childCount; i++) { Transform child = tipRow.transform.GetChild(i); if (!((Object)(object)directChildBranch != (Object)null) || !((Object)(object)child == (Object)(object)directChildBranch)) { return ((Component)child).gameObject; } } if ((Object)(object)directChildBranch != (Object)null) { for (int j = 0; j < directChildBranch.childCount; j++) { Transform child2 = directChildBranch.GetChild(j); if (!((Object)(object)val != (Object)null) || (!((Object)(object)child2 == (Object)(object)((TMP_Text)val).transform) && !((TMP_Text)val).transform.IsChildOf(child2))) { return ((Component)child2).gameObject; } } } Transform[] componentsInChildren = tipRow.GetComponentsInChildren<Transform>(true); foreach (Transform val2 in componentsInChildren) { if ((Object)(object)val2 == (Object)(object)tipRow.transform || (Object)(object)val2 == (Object)(object)directChildBranch || ((Object)(object)val != (Object)null && ((Object)(object)val2 == (Object)(object)((TMP_Text)val).transform || val2.IsChildOf(((TMP_Text)val).transform)))) { continue; } string name = ((Object)val2).name; if (name.IndexOf("key", StringComparison.OrdinalIgnoreCase) >= 0 || name.IndexOf("input", StringComparison.OrdinalIgnoreCase) >= 0 || name.IndexOf("glyph", StringComparison.OrdinalIgnoreCase) >= 0 || name.IndexOf("bind", StringComparison.OrdinalIgnoreCase) >= 0 || name.IndexOf("button", StringComparison.OrdinalIgnoreCase) >= 0 || name.IndexOf("icon", StringComparison.OrdinalIgnoreCase) >= 0) { Transform directChildBranch2 = GetDirectChildBranch(tipRow.transform, val2); if ((Object)(object)directChildBranch2 != (Object)null) { return ((Component)directChildBranch2).gameObject; } } } return null; } private static Transform GetDirectChildBranch(Transform root, Transform descendant) { if ((Object)(object)root == (Object)null || (Object)(object)descendant == (Object)null) { return null; } Transform val = descendant; while ((Object)(object)val.parent != (Object)null && (Object)(object)val.parent != (Object)(object)root) { val = val.parent; } if (!((Object)(object)val.parent == (Object)(object)root)) { return null; } return val; } private static void SetTipActive(GameObject tip, bool active) { if ((Object)(object)tip != (Object)null && tip.activeSelf != active) { tip.SetActive(active); } } private static void CleanupLegacyTips(KeyHints keyHints) { if (!((Object)(object)keyHints == (Object)null)) { if ((Object)(object)keyHints.m_dodgeKey != (Object)null) { DestroyNamedChild(((TMP_Text)keyHints.m_dodgeKey).transform.parent, "TarCoatingScrapeHint"); } if ((Object)(object)keyHints.m_primaryAttackKB != (Object)null) { DestroyNamedChild(keyHints.m_primaryAttackKB.transform.parent, "TarCoatingScrapeHint"); } if ((Object)(object)keyHints.m_primaryAttackGP != (Object)null) { DestroyNamedChild(keyHints.m_primaryAttackGP.transform.parent, "TarCoatingScrapeHint"); } } } private static void DestroyNamedChild(Transform parent, string name) { if (!((Object)(object)parent == (Object)null)) { Transform val = parent.Find(name); if (!((Object)(object)val == (Object)null) && !((Object)(object)((Component)val).gameObject == (Object)(object)_tipKb) && !((Object)(object)((Component)val).gameObject == (Object)(object)_tipGp)) { Object.Destroy((Object)(object)((Component)val).gameObject); } } } private static bool IsAlive(GameObject obj) { return (Object)(object)obj != (Object)null; } } internal static class ServerConfigSync { internal static readonly ConfigSync Instance = new ConfigSync("ModdedWolf.TarCoating") { DisplayName = "Tar Coating", CurrentVersion = "1.0.0", MinimumRequiredVersion = "1.0.0", ModRequired = true }; internal static void Synced<T>(ConfigEntry<T> entry) { Register<T>(entry, synchronized: true); } internal static void Local<T>(ConfigEntry<T> entry) { Register<T>(entry, synchronized: false); } private static void Register<T>(ConfigEntry<T> entry, bool synchronized) { if (entry != null) { Instance.AddConfigEntry<T>(entry).SynchronizedConfig = synchronized; } } } internal static class StructureBonuses { [HarmonyPatch(typeof(WearNTear), "GetMaterialProperties")] private static class GetMaterialPropertiesPatch { private static void Postfix(WearNTear __instance, ref float horizontalLoss, ref float verticalLoss) { float supportLossMultiplier = TarConfig.GetSupportLossMultiplier(); if (!(supportLossMultiplier >= 1f) && HasCoatingFlag(__instance)) { horizontalLoss *= supportLossMultiplier; verticalLoss *= supportLossMultiplier; } } } [HarmonyPatch(typeof(WearNTear), "RPC_Damage")] private static class RpcDamagePatch { private static void Prefix(WearNTear __instance, HitData hit) { if (hit != null) { float damageMultiplier = TarConfig.GetDamageMultiplier(); if (!(damageMultiplier >= 1f) && HasCoatingFlag(__instance)) { hit.ApplyModifier(damageMultiplier); } } } } private static bool HasCoatingFlag(WearNTear wear) { ZNetView nview = wear.m_nview; if ((Object)(object)nview != (Object)null && nview.IsValid()) { return nview.GetZDO().GetInt("tar_coated", 0) == 1; } return false; } } internal static class StructureVisuals { private struct MaterialSnapshot { public Material Material; public bool HasColor; public Color Color; public bool HasGlossiness; public float Glossiness; public bool HasSmoothness; public float Smoothness; } [HarmonyPatch(typeof(WearNTear), "Start")] private static class StartPatch { private static void Postfix(WearNTear __instance) { Coating.ClearDisallowedCoating(__instance); EnsureApplied(__instance); } } [HarmonyPatch(typeof(WearNTear), "UpdateWear")] private static class UpdateWearPatch { private static void Postfix(WearNTear __instance) { Coating.ClearDisallowedCoating(__instance); EnsureApplied(__instance); } } [HarmonyPatch(typeof(WearNTear), "OnDestroy")] private static class OnDestroyPatch { private static void Prefix(WearNTear __instance) { int instanceID = ((Object)__instance).GetInstanceID(); Snapshots.Remove(instanceID); AppliedIds.Remove(instanceID); } } private const float TarRed = 0.52f; private const float TarGreen = 0.4f; private const float TarBlue = 0.28f; private const float GlossBoost = 0.12f; private const string PieceShaderName = "Custom/Piece"; private const string SnowShaderName = "Valheim/Snow Mesh"; private const string RaftPrefabName = "Raft"; private const string RaftMastMaterialName = "Firetree_log"; private static readonly Color KarveTarredColor = new Color(0.424f, 0.326f, 0.228f); private static readonly HashSet<int> AppliedIds = new HashSet<int>(); private static readonly Dictionary<int, List<MaterialSnapshot>> Snapshots = new Dictionary<int, List<MaterialSnapshot>>(); internal static void Apply(WearNTear wear) { //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Unknown result type (might be due to invalid IL or missing references) if (!TarConfig.ShowTarredVisuals.Value || (Object)(object)wear == (Object)null || IsApplied(wear)) { return; } int instanceID = ((Object)wear).GetInstanceID(); bool flag = IsRaft(wear); bool isBoat = flag || (Object)(object)((Component)wear).GetComponentInParent<Ship>() != (Object)null; List<MaterialSnapshot> list = new List<MaterialSnapshot>(); Renderer[] componentsInChildren = ((Component)wear).GetComponentsInChildren<Renderer>(true); foreach (Renderer val in componentsInChildren) { if ((Object)(object)val == (Object)null || ((object)val).GetType().Name == "ParticleSystemRenderer" || val is TrailRenderer || val is LineRenderer || !HasCoatableMaterial(val, flag, isBoat)) { continue; } Material[] materials = val.materials; foreach (Material val2 in materials) { if (IsCoatableMaterial(val2, flag, isBoat)) { MaterialSnapshot item = CaptureSnapshot(val2); list.Add(item); if (item.HasColor && flag) { val2.color = new Color(KarveTarredColor.r, KarveTarredColor.g, KarveTarredColor.b, item.Color.a); } else if (item.HasColor) { Color color = item.Color; val2.color = new Color(color.r * 0.52f, color.g * 0.4f, color.b * 0.28f, color.a); } if (item.HasGlossiness) { val2.SetFloat("_Glossiness", Mathf.Min(item.Glossiness + 0.12f, 1f)); } else if (item.HasSmoothness) { val2.SetFloat("_Smoothness", Mathf.Min(item.Smoothness + 0.12f, 1f)); } } } } Snapshots[instanceID] = list; AppliedIds.Add(instanceID); } internal static void Revert(WearNTear wear) { //IL_005b: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)wear == (Object)null) { return; } int instanceID = ((Object)wear).GetInstanceID(); if (!Snapshots.TryGetValue(instanceID, out var value)) { AppliedIds.Remove(instanceID); return; } foreach (MaterialSnapshot item in value) { Material material = item.Material; if (!((Object)(object)material == (Object)null)) { if (item.HasColor) { material.color = item.Color; } if (item.HasGlossiness) { material.SetFloat("_Glossiness", item.Glossiness); } if (item.HasSmoothness) { material.SetFloat("_Smoothness", item.Smoothness); } } } Snapshots.Remove(instanceID); AppliedIds.Remove(instanceID); } internal static void RefreshAll() { WearNTear[] array = Object.FindObjectsByType<WearNTear>((FindObjectsSortMode)0); for (int i = 0; i < array.Length; i++) { EnsureApplied(array[i]); } } internal static void EnsureApplied(WearNTear wear) { if ((Object)(object)wear == (Object)null) { return; } if (!TarConfig.ShowTarredVisuals.Value) { if (IsApplied(wear)) { Revert(wear); } } else if (Coating.IsCoated(wear)) { Apply(wear); } else if (IsApplied(wear) && HasValidZdo(wear)) { Revert(wear); } } private static bool HasValidZdo(WearNTear wear) { ZNetView component = ((Component)wear).GetComponent<ZNetView>(); if ((Object)(object)component != (Object)null) { return component.IsValid(); } return false; } private static MaterialSnapshot CaptureSnapshot(Material material) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) MaterialSnapshot result = new MaterialSnapshot { Material = material }; if (material.HasProperty("_Color")) { result.HasColor = true; result.Color = material.color; } if (material.HasProperty("_Glossiness")) { result.HasGlossiness = true; result.Glossiness = material.GetFloat("_Glossiness"); } else if (material.HasProperty("_Smoothness")) { result.HasSmoothness = true; result.Smoothness = material.GetFloat("_Smoothness"); } return result; } private static bool HasCoatableMaterial(Renderer renderer, bool isRaft, bool isBoat) { Material[] sharedMaterials = renderer.sharedMaterials; for (int i = 0; i < sharedMaterials.Length; i++) { if (IsCoatableMaterial(sharedMaterials[i], isRaft, isBoat)) { return true; } } return false; } private static bool IsCoatableMaterial(Material material, bool isRaft, bool isBoat) { if ((Object)(object)material == (Object)null || (Object)(object)material.shader == (Object)null) { return false; } string name = ((Object)material).name; string name2 = ((Object)material.shader).name; if (name.StartsWith("sail", StringComparison.OrdinalIgnoreCase) || name.IndexOf("snow", StringComparison.OrdinalIgnoreCase) >= 0 || name2 == "Valheim/Snow Mesh") { return false; } if (name2 == "Custom/Piece") { return true; } if (isBoat) { if (isRaft) { return name.StartsWith("Firetree_log", StringComparison.OrdinalIgnoreCase); } return false; } return true; } private static bool IsRaft(WearNTear wear) { return Utils.GetPrefabName(((Object)((Component)wear).gameObject).name) == "Raft"; } private static bool IsApplied(WearNTear wear) { return AppliedIds.Contains(((Object)wear).GetInstanceID()); } } [BepInPlugin("ModdedWolf.TarCoating", "Tar Coating", "1.0.0")] public class TarCoatingPlugin : BaseUnityPlugin { public const string GUID = "ModdedWolf.TarCoating"; public const string NAME = "Tar Coating"; public const string VERSION = "1.0.0"; internal static TarCoatingPlugin Instance; internal static ManualLogSource Log; private void Awake() { //IL_0026: Unknown result type (might be due to invalid IL or missing references) Instance = this; Log = ((BaseUnityPlugin)this).Logger; TarConfig.Init(((BaseUnityPlugin)this).Config); LocalizationSetup.Apply(); new Harmony("ModdedWolf.TarCoating").PatchAll(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Tar Coating v1.0.0 loaded."); } } internal static class TarConfig { private const string TarEffectsSection = "2 - Tar Effects"; private const string OldTarEffectsSection = "2 - Tar Bonuses"; internal static ConfigEntry<bool> LockConfiguration; internal static ConfigEntry<bool> EnableSupportBonus; internal static ConfigEntry<int> SupportBonusPercent; internal static ConfigEntry<bool> EnableDurabilityBonus; internal static ConfigEntry<int> DamageReductionPercent; internal static ConfigEntry<int> TarPerPiece; internal static ConfigEntry<bool> AllowScrape; internal static ConfigEntry<bool> ShowTarredVisuals; internal static ConfigEntry<bool> ShowHoverTextWithVisuals; internal static ConfigEntry<bool> ShowTarredLabel; internal static ConfigEntry<bool> ShowAlreadyTarredLabel; internal static ConfigEntry<bool> ShowApplyLabel; internal static void Init(ConfigFile config) { //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Expected O, but got Unknown //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Expected O, but got Unknown //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Expected O, but got Unknown //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Expected O, but got Unknown //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Expected O, but got Unknown //IL_02a6: Unknown result type (might be due to invalid IL or missing references) //IL_02b0: Expected O, but got Unknown LockConfiguration = config.Bind<bool>("1 - General", "LockConfiguration", true, "Server only. When on, the server's values for synced settings override every client's."); EnableDurabilityBonus = config.Bind<bool>("2 - Tar Effects", "EnableDurabilityBonus", false, new ConfigDescription("Tarred wood pieces (and tarred boats) take less damage. *", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { DispName = "Less damage", Order = 4 } })); DamageReductionPercent = config.Bind<int>("2 - Tar Effects", "DamageReductionPercent", 0, new ConfigDescription("How much less damage tarred pieces take, in percent. *", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 50), new object[1] { new ConfigurationManagerAttributes { DispName = "Damage reduction", Order = 3 } })); EnableSupportBonus = config.Bind<bool>("2 - Tar Effects", "EnableSupportBonus", false, new ConfigDescription("Tarred wood pieces lose less structural support per piece, so builds can reach a little farther. *", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { DispName = "Stronger support", Order = 2 } })); SupportBonusPercent = config.Bind<int>("2 - Tar Effects", "SupportBonusPercent", 0, new ConfigDescription("How much less support tarred pieces lose, in percent. *", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 50), new object[1] { new ConfigurationManagerAttributes { DispName = "Support reduction", Order = 1 } })); TarPerPiece = config.Bind<int>("3 - Coating", "TarPerPiece", 1, new ConfigDescription("Tar used to coat one piece. *", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 10), Array.Empty<object>())); AllowScrape = config.Bind<bool>("3 - Coating", "AllowScrape", true, "Allow scraping tar off with a knife (hold block, then attack) to get 1 Tar back. *"); ShowTarredVisuals = config.Bind<bool>("Tarred Visuals", "ShowTarredVisuals", true, "Show the dark tar look on tarred wood. When off, hover text labels are shown instead."); ShowTarredVisuals.SettingChanged += delegate { StructureVisuals.RefreshAll(); }; ShowHoverTextWithVisuals = config.Bind<bool>("Tarred Visuals", "ShowHoverTextWithVisuals", false, "Also show hover text labels while tarred visuals are on. Off by default."); ShowTarredLabel = config.Bind<bool>("Hover Text", "ShowTarredLabel", false, "Show 'Tarred' when looking at a tarred piece without tar equipped. Only when hover text is allowed."); ShowAlreadyTarredLabel = config.Bind<bool>("Hover Text", "ShowAlreadyTarredLabel", true, "Show 'Already tarred' when holding tar and looking at a tarred piece. Only when hover text is allowed."); ShowApplyLabel = config.Bind<bool>("Hover Text", "ShowApplyLabel", false, "Show the left-click apply hint when holding tar and looking at un-tarred wood. Only when hover text is allowed."); config.Bind<string>("Notes", "SyncedNote", "", new ConfigDescription("* Synced from the server", (AcceptableValueBase)null, new object[1] { new ConfigurationManagerAttributes { HideSettingName = true, HideDefaultButton = true, ReadOnly = true, CustomDrawer = delegate { } } })); MigrateOldSection(config); RegisterServerSync(); } private static void MigrateOldSection(ConfigFile config) { MigrateEntry<bool>(config, EnableDurabilityBonus); MigrateEntry<int>(config, DamageReductionPercent); MigrateEntry<bool>(config, EnableSupportBonus); MigrateEntry<int>(config, SupportBonusPercent); config.Save(); } private static void MigrateEntry<T>(ConfigFile config, ConfigEntry<T> entry) { ConfigEntry<T> val = config.Bind<T>("2 - Tar Bonuses", ((ConfigEntryBase)entry).Definition.Key, (T)((ConfigEntryBase)entry).DefaultValue, (ConfigDescription)null); if (!object.Equals(val.Value, ((ConfigEntryBase)val).DefaultValue)) { entry.Value = val.Value; } config.Remove(((ConfigEntryBase)val).Definition); } private static void RegisterServerSync() { ServerConfigSync.Instance.AddLockingConfigEntry<bool>(LockConfiguration); ServerConfigSync.Synced<bool>(EnableSupportBonus); ServerConfigSync.Synced<int>(SupportBonusPercent); ServerConfigSync.Synced<bool>(EnableDurabilityBonus); ServerConfigSync.Synced<int>(DamageReductionPercent); ServerConfigSync.Synced<int>(TarPerPiece); ServerConfigSync.Synced<bool>(AllowScrape); ServerConfigSync.Local<bool>(ShowTarredVisuals); ServerConfigSync.Local<bool>(ShowHoverTextWithVisuals); ServerConfigSync.Local<bool>(ShowTarredLabel); ServerConfigSync.Local<bool>(ShowAlreadyTarredLabel); ServerConfigSync.Local<bool>(ShowApplyLabel); } internal static bool IsHoverTextAllowed() { if (ShowTarredVisuals.Value) { return ShowHoverTextWithVisuals.Value; } return true; } internal static int GetTarPerPiece() { return Mathf.Max(1, TarPerPiece.Value); } internal static float GetSupportLossMultiplier() { if (!EnableSupportBonus.Value) { return 1f; } return 1f - (float)Mathf.Clamp(SupportBonusPercent.Value, 0, 50) / 100f; } internal static float GetDamageMultiplier() { if (!EnableDurabilityBonus.Value) { return 1f; } return 1f - (float)Mathf.Clamp(DamageReductionPercent.Value, 0, 50) / 100f; } } internal static class TarItem { [HarmonyPatch(typeof(ObjectDB), "Awake")] private static class ObjectDBAwakePatch { private static void Postfix(ObjectDB __instance) { Register(__instance); } } [HarmonyPatch(typeof(ObjectDB), "CopyOtherDB")] private static class ObjectDBCopyPatch { private static void Postfix(ObjectDB __instance) { Register(__instance); } } [HarmonyPatch(typeof(Humanoid), "EquipItem")] private static class EquipItemPatch { private static void Prefix(ItemData item) { EnsureItemConfigured(item); } } [HarmonyPatch(typeof(ItemData), "GetTooltip", new Type[] { typeof(ItemData), typeof(int), typeof(bool), typeof(float), typeof(int), typeof(bool) })] private static class TooltipPatch { private static void Postfix(ItemData item, ref string __result) { if (IsTarItem(item)) { __result = __result.Replace("\n$item_twohanded", "\n$item_onehanded"); } } } [HarmonyPatch(typeof(VisEquipment), "SetRightBackItem")] private static class VisEquipmentSetRightBackItemPatch { private static void Prefix(ref int itemHash) { if (itemHash == PrefabHash) { itemHash = 0; } } } internal const string PrefabName = "Tar"; private const string VanillaItemName = "$item_tar"; private const string HandVisualName = "tarcoating_visual"; private static readonly int PrefabHash = StringExtensionMethods.GetStableHashCode("Tar"); private const float HandVisualSize = 0.18f; internal static GameObject Prefab { get; private set; } internal static ItemDrop ItemDrop { get; private set; } internal static void Register(ObjectDB db) { try { RegisterInternal(db); } catch (Exception arg) { TarCoatingPlugin.Log.LogError((object)$"Tar setup failed: {arg}"); } } private static void RegisterInternal(ObjectDB db) { if ((Object)(object)db == (Object)null) { return; } GameObject itemPrefab = db.GetItemPrefab("Tar"); if ((Object)(object)itemPrefab == (Object)null) { return; } ItemDrop component = itemPrefab.GetComponent<ItemDrop>(); if ((Object)(object)component == (Object)null) { TarCoatingPlugin.Log.LogWarning((object)"Vanilla Tar prefab has no ItemDrop; tar coating is disabled."); return; } bool num = (Object)(object)Prefab != (Object)(object)itemPrefab; Prefab = itemPrefab; ItemDrop = component; ConfigureSharedData(component.m_itemData.m_shared); SetupHandAttach(itemPrefab); if (num) { TarCoatingPlugin.Log.LogInfo((object)"Made vanilla Tar equippable for coating."); } } internal static void EnsureItemConfigured(ItemData item) { if (item != null && IsTarItem(item)) { if ((Object)(object)ObjectDB.instance != (Object)null) { Register(ObjectDB.instance); } if ((Object)(object)ItemDrop != (Object)null) { item.m_shared = ItemDrop.m_itemData.m_shared; } if ((Object)(object)Prefab != (Object)null && (Object)(object)item.m_dropPrefab == (Object)null) { item.m_dropPrefab = Prefab; } } } private static void ConfigureSharedData(SharedData shared) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Expected O, but got Unknown //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Expected O, but got Unknown //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Expected O, but got Unknown if (shared != null) { shared.m_itemType = (ItemType)19; shared.m_animationState = (AnimationState)1; shared.m_useDurability = false; shared.m_useDurabilityDrain = 0f; shared.m_attack = null; shared.m_secondaryAttack = null; shared.m_attackStatusEffect = null; shared.m_attackStatusEffectChance = 0f; shared.m_equipStatusEffect = null; shared.m_equipDuration = 0f; if (shared.m_equipEffect == null) { shared.m_equipEffect = new EffectList(); } if (shared.m_unequipEffect == null) { shared.m_unequipEffect = new EffectList(); } if (shared.m_holdStartEffect == null) { shared.m_holdStartEffect = new EffectList(); } shared.m_blockPower = 0f; shared.m_blockPowerPerLevel = 0f; shared.m_timedBlockBonus = 0f; shared.m_attackForce = 0f; shared.m_backstabBonus = 0f; shared.m_movementModifier = 0f; shared.m_blockable = false; shared.m_skillType = (SkillType)1; } } internal static bool IsTarItem(ItemData item) { if (item == null) { return false; } if ((Object)(object)item.m_dropPrefab != (Object)null) { return ((Object)item.m_dropPrefab).name == "Tar"; } if (item.m_shared != null) { return item.m_shared.m_name == "$item_tar"; } return false; } private static void SetupHandAttach(GameObject prefab) { //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Unknown result type (might be due to invalid IL or missing references) //IL_016e: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_017e: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_01b1: Unknown result type (might be due to invalid IL or missing references) Transform val = prefab.transform.Find("attach"); if ((Object)(object)val != (Object)null && (Object)(object)((Component)val).GetComponentInChildren<Renderer>(true) != (Object)null) { return; } MeshRenderer val2 = FindPrimaryDropRenderer(prefab); if ((Object)(object)val2 == (Object)null) { TarCoatingPlugin.Log.LogWarning((object)"Tar prefab has no mesh for hand attach."); return; } MeshFilter component = ((Component)val2).GetComponent<MeshFilter>(); if ((Object)(object)component == (Object)null || (Object)(object)component.sharedMesh == (Object)null) { TarCoatingPlugin.Log.LogWarning((object)"Tar prefab mesh filter is missing for hand attach."); return; } if ((Object)(object)val == (Object)null) { GameObject val3 = new GameObject("attach"); val3.SetActive(false); val3.transform.SetParent(prefab.transform, false); val3.transform.localPosition = Vector3.zero; val3.transform.localRotation = Quaternion.identity; val3.transform.localScale = Vector3.one; val = val3.transform; } if (!((Object)(object)val.Find("tarcoating_visual") != (Object)null)) { Bounds bounds = component.sharedMesh.bounds; float num = Mathf.Max(((Bounds)(ref bounds)).size.x, Mathf.Max(((Bounds)(ref bounds)).size.y, ((Bounds)(ref bounds)).size.z)); float num2 = ((num > 0.0001f) ? (0.18f / num) : 1f); Quaternion localRotation = ((Component)val2).transform.localRotation; GameObject val4 = new GameObject("tarcoating_visual"); val4.transform.SetParent(val, false); val4.transform.localPosition = -(localRotation * (((Bounds)(ref bounds)).center * num2)); val4.transform.localRotation = localRotation; val4.transform.localScale = Vector3.one * num2; val4.AddComponent<MeshFilter>().sharedMesh = component.sharedMesh; ((Renderer)val4.AddComponent<MeshRenderer>()).sharedMaterials = ((Renderer)val2).sharedMaterials; } } private static MeshRenderer FindPrimaryDropRenderer(GameObject prefab) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) MeshRenderer result = null; float num = 0f; MeshRenderer[] componentsInChildren = prefab.GetComponentsInChildren<MeshRenderer>(true); foreach (MeshRenderer val in componentsInChildren) { if (((Object)val).name == "tarcoating_visual") { continue; } MeshFilter component = ((Component)val).GetComponent<MeshFilter>(); if (!((Object)(object)component == (Object)null) && !((Object)(object)component.sharedMesh == (Object)null)) { Bounds bounds = component.sharedMesh.bounds; Vector3 size = ((Bounds)(ref bounds)).size; float num2 = size.x * size.y * size.z; if (!(num2 <= num)) { num = num2; result = val; } } } return result; } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } } namespace ServerSync { [PublicAPI] internal abstract class OwnConfigEntryBase { public object? LocalBaseValue; public bool SynchronizedConfig = true; public abstract ConfigEntryBase BaseConfig { get; } } [PublicAPI] internal class SyncedConfigEntry<T>(ConfigEntry<T> sourceConfig) : OwnConfigEntryBase() { public readonly ConfigEntry<T> SourceConfig = sourceConfig; public override ConfigEntryBase BaseConfig => (ConfigEntryBase)(object)SourceConfig; public T Value { get { return SourceConfig.Value; } set { SourceConfig.Value = value; } } public void AssignLocalValue(T value) { if (LocalBaseValue == null) { Value = value; } else { LocalBaseValue = value; } } } internal abstract class CustomSyncedValueBase { public object? LocalBaseValue; public readonly string Identifier; public readonly Type Type; private object? boxedValue; protected bool localIsOwner; public readonly int Priority; public object? BoxedValue { get { return boxedValue; } set { boxedValue = value; this.ValueChanged?.Invoke(); } } public event Action? ValueChanged; protected CustomSyncedValueBase(ConfigSync configSync, string identifier, Type type, int priority) { Priority = priority; Identifier = identifier; Type = type; configSync.AddCustomValue(this); localIsOwner = configSync.IsSourceOfTruth; configSync.SourceOfTruthChanged += delegate(bool truth) { localIsOwner = truth; }; } } [PublicAPI] internal sealed class CustomSyncedValue<T> : CustomSyncedValueBase { public T Value { get { return (T)base.BoxedValue; } set { base.BoxedValue = value; } } public CustomSyncedValue(ConfigSync configSync, string identifier, T value = default(T), int priority = 0) : base(configSync, identifier, typeof(T), priority) { Value = value; } public void AssignLocalValue(T value) { if (localIsOwner) { Value = value; } else { LocalBaseValue = value; } } } internal class ConfigurationManagerAttributes { [UsedImplicitly] public bool? ReadOnly = false; } [PublicAPI] internal class ConfigSync { [HarmonyPatch(typeof(ZRpc), "HandlePackage")] private static class SnatchCurrentlyHandlingRPC { public static ZRpc? currentRpc; [HarmonyPrefix] private static void Prefix(ZRpc __instance) { currentRpc = __instance; } } [HarmonyPatch(typeof(ZNet), "Awake")] internal static class RegisterRPCPatch { [HarmonyPostfix] private static void Postfix(ZNet __instance) { isServer = __instance.IsServer(); foreach (ConfigSync configSync2 in configSyncs) { ZRoutedRpc.instance.Register<ZPackage>(configSync2.Name + " ConfigSync", (Action<long, ZPackage>)configSync2.RPC_FromOtherClientConfigSync); if (isServer) { configSync2.InitialSyncDone = true; Debug.Log((object)("Registered '" + configSync2.Name + " ConfigSync' RPC - waiting for incoming connections")); } } if (isServer) { ((MonoBehaviour)__instance).StartCoroutine(WatchAdminListChanges()); } static void SendAdmin(List<ZNetPeer> peers, bool isAdmin) { ZPackage package = ConfigsToPackage(null, null, new PackageEntry[1] { new PackageEntry { section = "Internal", key = "lockexempt", type = typeof(bool), value = isAdmin } }); ConfigSync configSync = configSyncs.First(); if (configSync != null) { ((MonoBehaviour)ZNet.instance).StartCoroutine(configSync.sendZPackage(peers, package)); } } static IEnumerator WatchAdminListChanges() { MethodInfo listContainsId = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList adminList = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); List<string> CurrentList = new List<string>(adminList.GetList()); while (true) { yield return (object)new WaitForSeconds(30f); if (!adminList.GetList().SequenceEqual(CurrentList)) { CurrentList = new List<string>(adminList.GetList()); List<ZNetPeer> adminPeer = ZNet.instance.GetPeers().Where(delegate(ZNetPeer p) { string hostName = p.m_rpc.GetSocket().GetHostName(); return ((object)listContainsId == null) ? adminList.Contains(hostName) : ((bool)listContainsId.Invoke(ZNet.instance, new object[2] { adminList, hostName })); }).ToList(); List<ZNetPeer> nonAdminPeer = ZNet.instance.GetPeers().Except(adminPeer).ToList(); SendAdmin(nonAdminPeer, isAdmin: false); SendAdmin(adminPeer, isAdmin: true); } } } } } [HarmonyPatch(typeof(ZNet), "OnNewConnection")] private static class RegisterClientRPCPatch { [HarmonyPostfix] private static void Postfix(ZNet __instance, ZNetPeer peer) { if (__instance.IsServer()) { return; } foreach (ConfigSync configSync in configSyncs) { peer.m_rpc.Register<ZPackage>(configSync.Name + " ConfigSync", (Action<ZRpc, ZPackage>)configSync.RPC_FromServerConfigSync); } } } private class ParsedConfigs { public readonly Dictionary<OwnConfigEntryBase, object?> configValues = new Dictionary<OwnConfigEntryBase, object>(); public readonly Dictionary<CustomSyncedValueBase, object?> customValues = new Dictionary<CustomSyncedValueBase, object>(); } [HarmonyPatch(typeof(ZNet), "Shutdown")] private class ResetConfigsOnShutdown { [HarmonyPostfix] private static void Postfix() { ProcessingServerUpdate = true; foreach (ConfigSync configSync in configSyncs) { configSync.resetConfigsFromServer(); configSync.IsSourceOfTruth = true; configSync.InitialSyncDone = false; } ProcessingServerUpdate = false; } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] private class SendConfigsAfterLogin { private class BufferingSocket : ZPlayFabSocket, ISocket { public volatile bool finished = false; public volatile int versionMatchQueued = -1; public readonly List<ZPackage> Package = new List<ZPackage>(); public readonly ISocket Original; public BufferingSocket(ISocket original) { Original = original; ((ZPlayFabSocket)this)..ctor(); } public bool IsConnected() { return Original.IsConnected(); } public ZPackage Recv() { return Original.Recv(); } public int GetSendQueueSize() { return Original.GetSendQueueSize(); } public int GetCurrentSendRate() { return Original.GetCurrentSendRate(); } public bool IsHost() { return Original.IsHost(); } public void Dispose() { Original.Dispose(); } public bool GotNewData() { return Original.GotNewData(); } public void Close() { Original.Close(); } public string GetEndPointString() { return Original.GetEndPointString(); } public void GetAndResetStats(out int totalSent, out int totalRecv) { Original.GetAndResetStats(ref totalSent, ref totalRecv); } public void GetConnectionQuality(out float localQuality, out float remoteQuality, out int ping, out float outByteSec, out float inByteSec) { Original.GetConnectionQuality(ref localQuality, ref remoteQuality, ref ping, ref outByteSec, ref inByteSec); } public ISocket Accept() { return Original.Accept(); } public int GetHostPort() { return Original.GetHostPort(); } public bool Flush() { return Original.Flush(); } public string GetHostName() { return Original.GetHostName(); } public void VersionMatch() { if (finished) { Original.VersionMatch(); } else { versionMatchQueued = Package.Count; } } public void Send(ZPackage pkg) { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected O, but got Unknown int pos = pkg.GetPos(); pkg.SetPos(0); int num = pkg.ReadInt(); if ((num == StringExtensionMethods.GetStableHashCode("PeerInfo") || num == StringExtensionMethods.GetStableHashCode("RoutedRPC") || num == StringExtensionMethods.GetStableHashCode("ZDOData")) && !finished) { ZPackage val = new ZPackage(pkg.GetArray()); val.SetPos(pos); Package.Add(val); } else { pkg.SetPos(pos); Original.Send(pkg); } } } [HarmonyPriority(800)] [HarmonyPrefix] private static void Prefix(ref Dictionary<Assembly, BufferingSocket>? __state, ZNet __instance, ZRpc rpc) { //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Invalid comparison between Unknown and I4 if (!__instance.IsServer()) { return; } BufferingSocket bufferingSocket = new BufferingSocket(rpc.GetSocket()); AccessTools.DeclaredField(typeof(ZRpc), "m_socket").SetValue(rpc, bufferingSocket); object? obj = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc }); ZNetPeer val = (ZNetPeer)((obj is ZNetPeer) ? obj : null); if (val != null && (int)ZNet.m_onlineBackend > 0) { FieldInfo fieldInfo = AccessTools.DeclaredField(typeof(ZNetPeer), "m_socket"); object? value = fieldInfo.GetValue(val); ZPlayFabSocket val2 = (ZPlayFabSocket)((value is ZPlayFabSocket) ? value : null); if (val2 != null) { typeof(ZPlayFabSocket).GetField("m_remotePlayerId").SetValue(bufferingSocket, val2.m_remotePlayerId); } fieldInfo.SetValue(val, bufferingSocket); } if (__state == null) { __state = new Dictionary<Assembly, BufferingSocket>(); } __state[Assembly.GetExecutingAssembly()] = bufferingSocket; } [HarmonyPostfix] private static void Postfix(Dictionary<Assembly, BufferingSocket> __state, ZNet __instance, ZRpc rpc) { ZNetPeer peer; if (__instance.IsServer()) { object obj = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc }); peer = (ZNetPeer)((obj is ZNetPeer) ? obj : null); if (peer == null) { SendBufferedData(); } else { ((MonoBehaviour)__instance).StartCoroutine(sendAsync()); } } void SendBufferedData() { if (rpc.GetSocket() is BufferingSocket bufferingSocket) { AccessTools.DeclaredField(typeof(ZRpc), "m_socket").SetValue(rpc, bufferingSocket.Original); object? obj2 = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc }); ZNetPeer val = (ZNetPeer)((obj2 is ZNetPeer) ? obj2 : null); if (val != null) { AccessTools.DeclaredField(typeof(ZNetPeer), "m_socket").SetValue(val, bufferingSocket.Original); } } BufferingSocket bufferingSocket2 = __state[Assembly.GetExecutingAssembly()]; bufferingSocket2.finished = true; for (int i = 0; i < bufferingSocket2.Package.Count; i++) { if (i == bufferingSocket2.versionMatchQueued) { bufferingSocket2.Original.VersionMatch(); } bufferingSocket2.Original.Send(bufferingSocket2.Package[i]); } if (bufferingSocket2.Package.Count == bufferingSocket2.versionMatchQueued) { bufferingSocket2.Original.VersionMatch(); } } IEnumerator sendAsync() { foreach (ConfigSync configSync in configSyncs) { List<PackageEntry> entries = new List<PackageEntry>(); if (configSync.CurrentVersion != null) { entries.Add(new PackageEntry { section = "Internal", key = "serverversion", type = typeof(string), value = configSync.CurrentVersion }); } MethodInfo listContainsId = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList adminList = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); entries.Add(new PackageEntry { section = "Internal", key = "lockexempt", type = typeof(bool), value = (((object)listContainsId == null) ? ((object)adminList.Contains(rpc.GetSocket().GetHostName())) : listContainsId.Invoke(ZNet.instance, new object[2] { adminList, rpc.GetSocket().GetHostName() })) }); ZPackage package = ConfigsToPackage(configSync.allConfigs.Select((OwnConfigEntryBase c) => c.BaseConfig), configSync.allCustomValues, entries, partial: false); yield return ((MonoBehaviour)__instance).StartCoroutine(configSync.sendZPackage(new List<ZNetPeer> { peer }, package)); } SendBufferedData(); } } } private class PackageEntry { public string section = null; public string key = null; public Type type = null; public object? value; } [HarmonyPatch(typeof(ConfigEntryBase), "GetSerializedValue")] private static class PreventSavingServerInfo { [HarmonyPrefix] private static bool Prefix(ConfigEntryBase __instance, ref string __result) { OwnConfigEntryBase ownConfigEntryBase = configData(__instance); if (ownConfigEntryBase == null || isWritableConfig(ownConfigEntryBase)) { return true; } __result = TomlTypeConverter.ConvertToString(ownConfigEntryBase.LocalBaseValue, __instance.SettingType); return false; } } [HarmonyPatch(typeof(ConfigEntryBase), "SetSerializedValue")] private static class PreventConfigRereadChangingValues { [HarmonyPrefix] private static bool Prefix(ConfigEntryBase __instance, string value) { OwnConfigEntryBase ownConfigEntryBase = configData(__instance); if (ownConfigEntryBase == null || ownConfigEntryBase.LocalBaseValue == null) { return true; } try { ownConfigEntryBase.LocalBaseValue = TomlTypeConverter.ConvertToValue(value, __instance.SettingType); } catch (Exception ex) { Debug.LogWarning((object)$"Config value of setting \"{__instance.Definition}\" could not be parsed and will be ignored. Reason: {ex.Message}; Value: {value}"); } return false; } } private class InvalidDeserializationTypeException : Exception { public string expected = null; public string received = null; public string field = ""; } public static bool ProcessingServerUpdate; public readonly string Name; public string? DisplayName; public string? CurrentVersion; public string? MinimumRequiredVersion; public bool ModRequired = false; private bool? forceConfigLocking; private bool isSourceOfTruth = true; private static readonly HashSet<ConfigSync> configSyncs; private readonly HashSet<OwnConfigEntryBase> allConfigs = new HashSet<OwnConfigEntryBase>(); private HashSet<CustomSyncedValueBase> allCustomValues = new HashSet<CustomSyncedValueBase>(); private static bool isServer; private static bool lockExempt; private OwnConfigEntryBase? lockedConfig = null; private const byte PARTIAL_CONFIGS = 1; private const byte FRAGMENTED_CONFIG = 2; private const byte COMPRESSED_CONFIG = 4; private readonly Dictionary<string, SortedDictionary<int, byte[]>> configValueCache = new Dictionary<string, SortedDictionary<int, byte[]>>(); private readonly List<KeyValuePair<long, string>> cacheExpirations = new List<KeyValuePair<long, string>>(); private static long packageCounter; public bool IsLocked { get { bool? flag = forceConfigLocking; bool num; if (!flag.HasValue) { if (lockedConfig == null) { goto IL_0052; } num = ((IConvertible)lockedConfig.BaseConfig.BoxedValue).ToInt32(CultureInfo.InvariantCulture) != 0; } else { num = flag == true; } if (!num) { goto IL_0052; } int result = ((!lockExempt) ? 1 : 0); goto IL_0053; IL_0052: result = 0; goto IL_0053; IL_0053: return (byte)result != 0; } set { forceConfigLocking = value; } } public bool IsAdmin => lockExempt || isSourceOfTruth; public bool IsSourceOfTruth { get { return isSourceOfTruth; } private set { if (value != isSourceOfTruth) { isSourceOfTruth = value; this.SourceOfTruthChanged?.Invoke(value); } } } public bool InitialSyncDone { get; private set; } = false; public event Action<bool>? SourceOfTruthChanged; private event Action? lockedConfigChanged; static ConfigSync() { ProcessingServerUpdate = false; configSyncs = new HashSet<ConfigSync>(); lockExempt = false; packageCounter = 0L; RuntimeHelpers.RunClassConstructor(typeof(VersionCheck).TypeHandle); } public ConfigSync(string name) { Name = name; configSyncs.Add(this); new VersionCheck(this); } public SyncedConfigEntry<T> AddConfigEntry<T>(ConfigEntry<T> configEntry) { OwnConfigEntryBase ownConfigEntryBase = configData((ConfigEntryBase)(object)configEntry); SyncedConfigEntry<T> syncedEntry = ownConfigEntryBase as SyncedConfigEntry<T>; if (syncedEntry == null) { syncedEntry = new SyncedConfigEntry<T>(configEntry); AccessTools.DeclaredField(typeof(ConfigDescription), "<Tags>k__BackingField").SetValue(((ConfigEntryBase)configEntry).Description, new object[1] { new ConfigurationManagerAttributes() }.Concat(((ConfigEntryBase)configEntry).Description.Tags ?? Array.Empty<object>()).Concat(new SyncedConfigEntry<T>[1] { syncedEntry }).ToArray()); configEntry.SettingChanged += delegate { if (!ProcessingServerUpdate && syncedEntry.SynchronizedConfig) { Broadcast(0L, (ConfigEntryBase)configEntry); } }; allConfigs.Add(syncedEntry); } return syncedEntry; } public SyncedConfigEntry<T> AddLockingConfigEntry<T>(ConfigEntry<T> lockingConfig) where T : IConvertible { if (lockedConfig != null) { throw new Exception("Cannot initialize locking ConfigEntry twice"); } lockedConfig = AddConfigEntry<T>(lockingConfig); lockingConfig.SettingChanged += delegate { this.lockedConfigChanged?.Invoke(); }; return (SyncedConfigEntry<T>)lockedConfig; } internal void AddCustomValue(CustomSyncedValueBase customValue) { if (allCustomValues.Select((CustomSyncedValueBase v) => v.Identifier).Concat(new string[1] { "serverversion" }).Contains(customValue.Identifier)) { throw new Exception("Cannot have multiple settings with the same name or with a reserved name (serverversion)"); } allCustomValues.Add(customValue); allCustomValues = new HashSet<CustomSyncedValueBase>(allCustomValues.OrderByDescending((CustomSyncedValueBase v) => v.Priority)); customValue.ValueChanged += delegate { if (!ProcessingServerUpdate) { Broadcast(0L, customValue); } }; } private void RPC_FromServerConfigSync(ZRpc rpc, ZPackage package) { lockedConfigChanged += serverLockedSettingChanged; IsSourceOfTruth = false; if (HandleConfigSyncRPC(0L, package, clientUpdate: false)) { InitialSyncDone = true; } } private void RPC_FromOtherClientConfigSync(long sender, ZPackage package) { HandleConfigSyncRPC(sender, package, clientUpdate: true); } private bool HandleConfigSyncRPC(long sender, ZPackage package, bool clientUpdate) { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Expected O, but got Unknown //IL_0250: Unknown result type (might be due to invalid IL or missing references) //IL_0257: Expected O, but got Unknown //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Expected O, but got Unknown try { if (isServer && IsLocked) { ZRpc? currentRpc = SnatchCurrentlyHandlingRPC.currentRpc; object obj; if (currentRpc == null) { obj = null; } else { ISocket socket = currentRpc.GetSocket(); obj = ((socket != null) ? socket.GetHostName() : null); } string text = (string)obj; if (text != null) { MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList val = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); if (!(((object)methodInfo == null) ? val.Contains(text) : ((bool)methodInfo.Invoke(ZNet.instance, new object[2] { val, text })))) { return false; } } } cacheExpirations.RemoveAll(delegate(KeyValuePair<long, string> kv) { if (kv.Key < DateTimeOffset.Now.Ticks) { configValueCache.Remove(kv.Value); return true; } return false; }); byte b = package.ReadByte(); if ((b & 2) != 0) { long num = package.ReadLong(); string text2 = sender.ToString() + num; if (!configValueCache.TryGetValue(text2, out SortedDictionary<int, byte[]> value)) { value = new SortedDictionary<int, byte[]>(); configValueCache[text2] = value; cacheExpirations.Add(new KeyValuePair<long, string>(DateTimeOffset.Now.AddSeconds(60.0).Ticks, text2)); } int key = package.ReadInt(); int num2 = package.ReadInt(); value.Add(key, package.ReadByteArray()); if (value.Count < num2) { return false; } configValueCache.Remove(text2); package = new ZPackage(value.Values.SelectMany((byte[] a) => a).ToArray()); b = package.ReadByte(); } ProcessingServerUpdate = true; if ((b & 4) != 0) { byte[] buffer = package.ReadByteArray(); MemoryStream stream = new MemoryStream(buffer); MemoryStream memoryStream = new MemoryStream(); using (DeflateStream deflateStream = new DeflateStream(stream, CompressionMode.Decompress)) { deflateStream.CopyTo(memoryStream); } package = new ZPackage(memoryStream.ToArray()); b = package.ReadByte(); } if ((b & 1) == 0) { resetConfigsFromServer(); } ParsedConfigs parsedConfigs = ReadConfigsFromPackage(package); ConfigFile val2 = null; bool saveOnConfigSet = false; foreach (KeyValuePair<OwnConfigEntryBase, object> configValue in parsedConfigs.configValues) { if (!isServer && configValue.Key.LocalBaseValue == null) { configValue.Key.LocalBaseValue = configValue.Key.BaseConfig.BoxedValue; } if (val2 == null) { val2 = configValue.Key.BaseConfig.ConfigFile; saveOnConfigSet = val2.SaveOnConfigSet; val2.SaveOnConfigSet = false; } configValue.Key.BaseConfig.BoxedValue = configValue.Value; } if (val2 != null) { val2.SaveOnConfigSet = saveOnConfigSet; val2.Save(); } foreach (KeyValuePair<CustomSyncedValueBase, object> customValue in parsedConfigs.customValues) { if (!isServer) { CustomSyncedValueBase key2 = customValue.Key; if (key2.LocalBaseValue == null) { key2.LocalBaseValue = customValue.Key.BoxedValue; } } customValue.Key.BoxedValue = customValue.Value; } Debug.Log((object)string.Format("Received {0} configs and {1} custom values from {2} for mod {3}", parsedConfigs.configValues.Count, parsedConfigs.customValues.Count, (isServer || clientUpdate) ? $"client {sender}" : "the server", DisplayName ?? Name)); if (!isServer) { serverLockedSettingChanged(); } return true; } finally { ProcessingServerUpdate = false; } } private ParsedConfigs ReadConfigsFromPackage(ZPackage package) { ParsedConfigs parsedConfigs = new ParsedConfigs(); Dictionary<string, OwnConfigEntryBase> dictionary = allConfigs.Where((OwnConfigEntryBase c) => c.SynchronizedConfig).ToDictionary((OwnConfigEntryBase c) => c.BaseConfig.Definition.Section + "_" + c.BaseConfig.Definition.Key, (OwnConfigEntryBase c) => c); Dictionary<string, CustomSyncedValueBase> dictionary2 = allCustomValues.ToDictionary((CustomSyncedValueBase c) => c.Identifier, (CustomSyncedValueBase c) => c); int num = package.ReadInt(); for (int num2 = 0; num2 < num; num2++) { string text = package.ReadString(); string text2 = package.ReadString(); string text3 = package.ReadString(); Type type = Type.GetType(text3); if (text3 == "" || type != null) { object obj; try { obj = ((text3 == "") ? null : ReadValueWithTypeFromZPackage(package, type)); } catch (InvalidDeserializationTypeException ex) { Debug.LogWarning((object)("Got unexpected struct internal type " + ex.received + " for field " + ex.field + " struct " + text3 + " for " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ", expecting " + ex.expected)); continue; } OwnConfigEntryBase value2; if (text == "Internal") { CustomSyncedValueBase value; if (text2 == "serverversion") { if (obj?.ToString() != CurrentVersion) { Debug.LogWarning((object)("Received server version is not equal: server version = " + (obj?.ToString() ?? "null") + "; local version = " + (CurrentVersion ?? "unknown"))); } } else if (text2 == "lockexempt") { if (obj is bool flag) { lockExempt = flag; } } else if (dictionary2.TryGetValue(text2, out value)) { if ((text3 == "" && (!value.Type.IsValueType || Nullable.GetUnderlyingType(value.Type) != null)) || GetZPackageTypeString(value.Type) == text3) { parsedConfigs.customValues[value] = obj; continue; } Debug.LogWarning((object)("Got unexpected type " + text3 + " for internal value " + text2 + " for mod " + (DisplayName ?? Name) + ", expecting " + value.Type.AssemblyQualifiedName)); } } else if (dictionary.TryGetValue(text + "_" + text2, out value2)) { Type type2 = configType(value2.BaseConfig); if ((text3 == "" && (!type2.IsValueType || Nullable.GetUnderlyingType(type2) != null)) || GetZPackageTypeString(type2) == text3) { parsedConfigs.configValues[value2] = obj; continue; } Debug.LogWarning((object)("Got unexpected type " + text3 + " for " + text2 + " in section " + text + " for mod " + (DisplayName ??