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 balrond BannerColorizer v1.2.3
plugins/BalrondBannerColorizer.dll
Decompiled 3 weeks 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.Collections.Specialized; 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.InteropServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Text; using BalrondBannerColorizer.Assets; using BalrondBannerColorizer.Colorization; using BalrondBannerColorizer.Compatibility; using BalrondBannerColorizer.Config; using BalrondBannerColorizer.Infrastructure; using BalrondBannerColorizer.Patches; using BalrondBannerColorizer.Runtime; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using JetBrains.Annotations; using LitJson2; using Microsoft.CodeAnalysis; using ServerSync; using TMPro; using UnityEngine; using UnityEngine.Audio; using UnityEngine.Rendering; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyTitle("BalrondBannerColorizer")] [assembly: AssemblyDescription("Runtime dye and colorization support for Valheim building pieces")] [assembly: AssemblyCompany("Balrond")] [assembly: AssemblyProduct("BalrondBannerColorizer")] [assembly: ComVisible(false)] [assembly: Guid("f405ea1c-ac25-47a3-9aa2-a8f56c14bfd6")] [assembly: AssemblyFileVersion("1.2.3.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("1.2.3.0")] [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 BalrondBannerColorizer { public class BalrondTranslator { public static Dictionary<string, Dictionary<string, string>> translations = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase); internal static int LanguageCount => translations.Count; public static Dictionary<string, string> getLanguage(string language) { if (string.IsNullOrWhiteSpace(language)) { return null; } translations.TryGetValue(language.Trim(), out var value); return value; } internal static void Clear() { translations.Clear(); } internal static void SetLanguage(string language, Dictionary<string, string> entries) { if (!string.IsNullOrWhiteSpace(language) && entries != null) { translations[language.Trim()] = entries; } } } public class JsonLoader { public string defaultPath = string.Empty; public void loadJson() { LoadJson(); } public void justDefaultPath() { defaultPath = GetTranslationDirectory(); } public void createDefaultPath() { defaultPath = GetTranslationDirectory(); EnsureDirectory(defaultPath); } internal void LoadJson() { defaultPath = GetTranslationDirectory(); EnsureDirectory(defaultPath); BalrondTranslator.Clear(); LoadTranslations(defaultPath); } private static string GetTranslationDirectory() { return Path.Combine(Paths.ConfigPath, "BalrondBannerColorizer-translation"); } private static void EnsureDirectory(string path) { if (string.IsNullOrEmpty(path) || Directory.Exists(path)) { return; } try { Directory.CreateDirectory(path); } catch (Exception ex) { ModLog.Error("BalrondBannerColorizer: could not create translation directory '" + path + "': " + ex.Message); } } private static void LoadTranslations(string directory) { if (string.IsNullOrEmpty(directory) || !Directory.Exists(directory)) { return; } string[] files; try { files = Directory.GetFiles(directory, "*.json", SearchOption.TopDirectoryOnly); } catch (Exception ex) { ModLog.Error("BalrondBannerColorizer: could not enumerate translation files: " + ex.Message); return; } Array.Sort(files, (IComparer<string>?)StringComparer.OrdinalIgnoreCase); foreach (string text in files) { string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(text); if (string.IsNullOrWhiteSpace(fileNameWithoutExtension)) { continue; } try { JsonData jsonData = JsonMapper.ToObject(File.ReadAllText(text)); if (jsonData == null || !jsonData.IsObject) { ModLog.Warning("BalrondBannerColorizer: translation root must be a JSON object: " + text); continue; } Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal); foreach (string key in jsonData.Keys) { if (!string.IsNullOrWhiteSpace(key)) { JsonData jsonData2 = jsonData[key]; dictionary[key] = ((jsonData2 != null) ? jsonData2.ToString() : string.Empty); } } BalrondTranslator.SetLanguage(fileNameWithoutExtension, dictionary); } catch (Exception ex2) { ModLog.Error("BalrondBannerColorizer: failed to load translation file '" + text + "': " + ex2.Message); } } } } internal static class TranslationPatches { private static readonly MethodInfo AddWordMethod = AccessTools.Method(typeof(Localization), "AddWord", new Type[2] { typeof(string), typeof(string) }, (Type[])null); internal static void Register(Harmony harmony) { if (harmony != null) { PatchNamedPostfixes(harmony, typeof(Localization), "SetupLanguage", "LocalizationLifecyclePostfix"); PatchNamedPostfixes(harmony, typeof(Localization), "LoadCSV", "LocalizationLifecyclePostfix"); PatchSinglePostfix(harmony, typeof(FejdStartup), "SetupGui", "FejdStartupSetupGuiPostfix"); } } internal static void Clear() { } private static void LocalizationLifecyclePostfix(Localization __instance) { ApplySelectedLanguage(__instance); } private static void FejdStartupSetupGuiPostfix() { ApplySelectedLanguage(Localization.instance); } private static void ApplySelectedLanguage(Localization localization) { if (localization == null) { return; } string language = null; try { language = localization.GetSelectedLanguage(); } catch { } Dictionary<string, string> dictionary = BuildLanguageOverlay(language); if (dictionary == null || dictionary.Count == 0) { return; } if (AddWordMethod == null) { ModLog.Error("BalrondBannerColorizer: Localization.AddWord(string,string) was not found."); return; } foreach (KeyValuePair<string, string> item in dictionary) { if (!string.IsNullOrWhiteSpace(item.Key)) { try { AddWordMethod.Invoke(localization, new object[2] { item.Key, item.Value ?? string.Empty }); } catch (Exception ex) { ModLog.Warning("BalrondBannerColorizer: failed to add translation key '" + item.Key + "': " + ex.Message); } } } } private static Dictionary<string, string> BuildLanguageOverlay(string language) { Dictionary<string, string> language2 = BalrondTranslator.getLanguage("English"); Dictionary<string, string> dictionary = ((!string.IsNullOrWhiteSpace(language)) ? BalrondTranslator.getLanguage(language) : null); if (language2 == null && dictionary == null) { return null; } Dictionary<string, string> dictionary2 = new Dictionary<string, string>(StringComparer.Ordinal); if (language2 != null) { foreach (KeyValuePair<string, string> item in language2) { dictionary2[item.Key] = item.Value; } } if (dictionary != null && dictionary != language2) { foreach (KeyValuePair<string, string> item2 in dictionary) { dictionary2[item2.Key] = item2.Value; } } return dictionary2; } private static void PatchNamedPostfixes(Harmony harmony, Type type, string methodName, string patchName) { MethodInfo methodInfo = AccessTools.Method(typeof(TranslationPatches), patchName, (Type[])null, (Type[])null); if (methodInfo == null) { ModLog.Warning("BalrondBannerColorizer: translation patch method missing: " + patchName); return; } MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); int num = 0; foreach (MethodInfo methodInfo2 in methods) { if (!(methodInfo2 == null) && string.Equals(methodInfo2.Name, methodName, StringComparison.Ordinal)) { try { HarmonyMethod val = CreateBaoOrderedPostfix(methodInfo); harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); num++; } catch (Exception ex) { ModLog.Warning("BalrondBannerColorizer: optional translation patch disabled for " + type.Name + "." + methodName + ": " + ex.GetBaseException().Message); } } } if (num == 0) { ModLog.Warning("BalrondBannerColorizer: translation lifecycle target not found: " + type.Name + "." + methodName); } } private static void PatchSinglePostfix(Harmony harmony, Type type, string methodName, string patchName) { MethodInfo methodInfo = AccessTools.Method(type, methodName, (Type[])null, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(TranslationPatches), patchName, (Type[])null, (Type[])null); if (methodInfo == null || methodInfo2 == null) { ModLog.Warning("BalrondBannerColorizer: translation lifecycle target not found: " + type.Name + "." + methodName); return; } try { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, CreateBaoOrderedPostfix(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } catch (Exception ex) { ModLog.Warning("BalrondBannerColorizer: optional translation patch disabled for " + type.Name + "." + methodName + ": " + ex.GetBaseException().Message); } } private static HarmonyMethod CreateBaoOrderedPostfix(MethodInfo patch) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Expected O, but got Unknown HarmonyMethod val = new HarmonyMethod(patch); val.after = new string[1] { "balrond.astafaraios.BalrondAbsoluteOverhaul" }; return val; } } [BepInPlugin("balrond.astafaraios.BalrondBannerColorizer", "BalrondBannerColorizer", "1.2.3")] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class Launch : BaseUnityPlugin { public const string PluginGUID = "balrond.astafaraios.BalrondBannerColorizer"; public const string PluginName = "BalrondBannerColorizer"; public const string PluginVersion = "1.2.3"; private Harmony _harmony; private ModResourceLoader _resourceLoader; private ModAssetCatalog _assets; private JsonLoader _jsonLoader; internal static ContentRuntime Runtime { get; private set; } private void Awake() { //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Expected O, but got Unknown ModLog.Initialize(((BaseUnityPlugin)this).Logger); ModLog.Info("BalrondBannerColorizer 1.2.3 | MVID=" + typeof(Launch).Assembly.ManifestModule.ModuleVersionId.ToString() + " loading."); BaoCompatibilityBridge.LogStartupStatus(); ServerSyncBridge.Initialize("balrond.astafaraios.BalrondBannerColorizer", "BalrondBannerColorizer", "1.2.3"); _jsonLoader = new JsonLoader(); _jsonLoader.loadJson(); _assets = new ModAssetCatalog(); _resourceLoader = new ModResourceLoader(); if (!_resourceLoader.Load(_assets)) { ModLog.Error("BalrondBannerColorizer: content AssetBundle did not load completely. Runtime hooks will remain failure-isolated."); } ModConfig.Initialize(this, ResolveSerializedDyeStationCraftingStation()); ColorizerCatalog.Build(_assets); AudioRoutingService.Register(_assets.OwnedPrefabs); Runtime = new ContentRuntime(_assets); ModConfig.Changed += OnSynchronizedConfigurationChanged; _harmony = new Harmony("balrond.astafaraios.BalrondBannerColorizer"); TranslationPatches.Register(_harmony); PatchRegistrar.Register(_harmony); ModLog.Info("BalrondBannerColorizer loaded."); } private void OnSynchronizedConfigurationChanged() { Runtime?.OnConfigurationChanged(); } private string ResolveSerializedDyeStationCraftingStation() { Piece val = (((Object)(object)_assets?.DyeStation != (Object)null) ? _assets.DyeStation.GetComponent<Piece>() : null); CraftingStation val2 = (((Object)(object)val != (Object)null) ? val.m_craftingStation : null); if ((Object)(object)val2 == (Object)null || (Object)(object)((Component)val2).gameObject == (Object)null || string.IsNullOrEmpty(((Object)((Component)val2).gameObject).name)) { return "NONE"; } return ((Object)((Component)val2).gameObject).name; } private void OnDestroy() { try { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } catch { } ModConfig.Changed -= OnSynchronizedConfigurationChanged; Runtime?.Clear(); Runtime = null; ModConfig.Reset(); ServerSyncBridge.Reset(); AudioRoutingService.Clear(); ColorizerCatalog.Clear(); ShaderReplacement.Clear(); TranslationPatches.Clear(); BalrondTranslator.Clear(); BaoCompatibilityBridge.Reset(); if (_resourceLoader != null && _assets != null) { _resourceLoader.Unload(_assets); } } } } namespace BalrondBannerColorizer.Runtime { internal sealed class ContentRuntime { private readonly ModAssetCatalog _assets; private readonly PrefabLookupCache _prefabs = new PrefabLookupCache(); private readonly FxReplacement _fxReplacement = new FxReplacement(); private readonly DyeRecipeService _dyeRecipes; private readonly DyeStationService _dyeStation; private ObjectDB _currentObjectDb; private ZNetScene _currentScene; private bool _frontendReady; internal ContentRuntime(ModAssetCatalog assets) { _assets = assets; _dyeRecipes = new DyeRecipeService(assets); _dyeStation = new DyeStationService(assets); } internal void OnObjectDbReady(ObjectDB objectDb) { _currentObjectDb = objectDb; RunStage(delegate { _dyeRecipes.Apply(objectDb); }, "dye item and recipe registration"); RunStage(delegate { ColorizerInstaller.InstallOnObjectDb(objectDb); }, "Hammer colorizer installation"); RunStage(delegate { BaoCompatibilityBridge.ValidateObjectDb(objectDb, _assets); }, "BAO ObjectDB validation"); } internal void OnZNetScenePrefix(ZNetScene scene) { if (!((Object)(object)scene == (Object)null) && scene.m_prefabs != null && _assets != null) { AppendOwnedPrefabs(scene); } } internal void OnZNetScenePostfix(ZNetScene scene) { if (!((Object)(object)scene == (Object)null) && scene.m_prefabs != null) { if (_currentScene != scene) { ShaderReplacement.BeginScene(); } _currentScene = scene; _prefabs.Rebuild(scene.m_prefabs); RunStage(RegisterTrustedRuntimeShaders, "runtime shader registration"); RunStage(delegate { _dyeStation.ConfigureAndRegister(scene, _prefabs); }, "Dye Station registration"); BaoCompatibilityBridge.SharedBannerPieceSnapshot baoBannerState = null; RunStage(delegate { baoBannerState = BaoCompatibilityBridge.CaptureSharedBannerPieceState(_prefabs); }, "BAO shared-banner snapshot"); RunStage(delegate { ColorizerInstaller.Install(_prefabs); }, "colorizer installation"); RunStage(delegate { BaoCompatibilityBridge.ValidateSharedBannerPieceState(_prefabs, baoBannerState); }, "BAO shared-banner validation"); RunStage(delegate { _fxReplacement.RepairOwnedPrefabs(_assets.OwnedPrefabs, scene); }, "FX repair"); RunStage(AudioRoutingService.Refresh, "audio routing after FX"); RunStage(delegate { ShaderReplacement.RunMaterialFix(forceRefresh: true); }, "shader repair"); RunStage(delegate { BaoCompatibilityBridge.ValidateScene(scene, _prefabs, _assets); }, "BAO scene validation"); } } internal void OnConfigurationChanged() { if ((Object)(object)_currentObjectDb != (Object)null) { _dyeRecipes.Apply(_currentObjectDb); } if ((Object)(object)_currentScene != (Object)null && _currentScene.m_prefabs != null) { if (_prefabs.Count == 0) { _prefabs.Rebuild(_currentScene.m_prefabs); } RunStage(delegate { _dyeStation.ConfigureAndRegister(_currentScene, _prefabs); }, "Dye Station config refresh"); } } internal void OnFrontendReady() { _frontendReady = true; if ((Object)(object)_currentScene != (Object)null) { ShaderReplacement.RunMaterialFix(forceRefresh: true); } } internal void Clear() { _currentObjectDb = null; _currentScene = null; _frontendReady = false; _dyeRecipes.Clear(); _fxReplacement.Clear(); } private static void RunStage(Action action, string stage) { if (action == null) { return; } try { action(); } catch (Exception ex) { ModLog.Error("BalrondBannerColorizer: " + stage + " failed: " + ex); } } private void RegisterTrustedRuntimeShaders() { for (int i = 0; i < ColorizerCatalog.TargetPrefabNames.Length; i++) { ShaderReplacement.RegisterTrustedShaders(_prefabs.Get(ColorizerCatalog.TargetPrefabNames[i])); } ShaderReplacement.RegisterTrustedShaders(_prefabs.Get("piece_workbench")); ShaderReplacement.RegisterTrustedShaders(_prefabs.Get("Hammer")); } private void AppendOwnedPrefabs(ZNetScene scene) { HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); HashSet<int> hashSet2 = new HashSet<int>(); for (int i = 0; i < scene.m_prefabs.Count; i++) { GameObject val = scene.m_prefabs[i]; if (!((Object)(object)val == (Object)null) && !string.IsNullOrEmpty(((Object)val).name)) { hashSet.Add(((Object)val).name); hashSet2.Add(StringExtensionMethods.GetStableHashCode(((Object)val).name)); } } int num = 0; for (int j = 0; j < _assets.OwnedPrefabs.Count; j++) { GameObject val2 = _assets.OwnedPrefabs[j]; if ((Object)(object)val2 == (Object)null || string.IsNullOrEmpty(((Object)val2).name)) { continue; } int stableHashCode = StringExtensionMethods.GetStableHashCode(((Object)val2).name); if (hashSet.Contains(((Object)val2).name)) { GameObject val3 = FindByName(scene.m_prefabs, ((Object)val2).name); if (val3 != val2) { ModLog.Warning("BalrondBannerColorizer: ZNetScene prefab name already exists; existing prefab wins: " + ((Object)val2).name); } } else if (hashSet2.Contains(stableHashCode)) { ModLog.Warning("BalrondBannerColorizer: ZNetScene stable-hash collision; prefab skipped: " + ((Object)val2).name); } else { scene.m_prefabs.Add(val2); hashSet.Add(((Object)val2).name); hashSet2.Add(stableHashCode); num++; } } if (num > 0) { ModLog.Info("BalrondBannerColorizer" + $": registered {num} owned network prefab(s)."); } } private static GameObject FindByName(List<GameObject> prefabs, string name) { for (int i = 0; i < prefabs.Count; i++) { GameObject val = prefabs[i]; if ((Object)(object)val != (Object)null && string.Equals(((Object)val).name, name, StringComparison.Ordinal)) { return val; } } return null; } } internal sealed class DyeRecipeService { private readonly ModAssetCatalog _assets; private readonly ObjectDbLookupCache _lookup = new ObjectDbLookupCache(); private readonly Dictionary<string, Recipe> _recipes = new Dictionary<string, Recipe>(StringComparer.Ordinal); internal DyeRecipeService(ModAssetCatalog assets) { _assets = assets; } internal void Apply(ObjectDB objectDb) { if (!((Object)(object)objectDb == (Object)null) && objectDb.m_items != null && objectDb.m_recipes != null && _assets != null && !((Object)(object)FindItemByName(objectDb.m_items, "Amber") == (Object)null)) { RegisterItems(objectDb); _lookup.Rebuild(objectDb); RegisterRecipes(objectDb); } } internal void Clear() { _recipes.Clear(); } private void RegisterItems(ObjectDB objectDb) { foreach (GameObject dyeItem in _assets.DyeItems) { if ((Object)(object)dyeItem == (Object)null || (Object)(object)dyeItem.GetComponent<ItemDrop>() == (Object)null) { continue; } int stableHashCode = StringExtensionMethods.GetStableHashCode(((Object)dyeItem).name); GameObject val = FindItemByName(objectDb.m_items, ((Object)dyeItem).name); if ((Object)(object)val == (Object)null) { GameObject val2 = FindItemByStableHash(objectDb.m_items, stableHashCode, ((Object)dyeItem).name); if ((Object)(object)val2 != (Object)null) { ModLog.Warning("BalrondBannerColorizer: ObjectDB stable-hash collision; item skipped: " + ((Object)dyeItem).name + " conflicts with " + ((Object)val2).name); continue; } if (ObjectDbCompat.TryGetHashedItem(objectDb, stableHashCode, out var prefab) && (Object)(object)prefab != (Object)null && !string.Equals(((Object)prefab).name, ((Object)dyeItem).name, StringComparison.Ordinal)) { ModLog.Warning("BalrondBannerColorizer: ObjectDB hash dictionary collision; item skipped: " + ((Object)dyeItem).name + " conflicts with " + ((Object)prefab).name); continue; } objectDb.m_items.Add(dyeItem); val = dyeItem; } else if (val != dyeItem) { ModLog.Warning("BalrondBannerColorizer: ObjectDB item name already exists; existing prefab wins: " + ((Object)dyeItem).name); } ObjectDbCompat.SetHashedItem(objectDb, stableHashCode, val); } } private void RegisterRecipes(ObjectDB objectDb) { GameObject dyeStation = _assets.DyeStation; CraftingStation val = (((Object)(object)dyeStation != (Object)null) ? dyeStation.GetComponent<CraftingStation>() : null); if ((Object)(object)val == (Object)null) { ModLog.Warning("BalrondBannerColorizer: CraftingStation missing on piece_dyestation; dye recipes were not registered."); return; } for (int i = 0; i < ModAssetCatalog.DyePrefabNames.Length; i++) { string text = ModAssetCatalog.DyePrefabNames[i]; GameObject val2 = _lookup.Get(text); GameObject item = _assets.GetItem(text); if ((Object)(object)val2 != (Object)null && (Object)(object)item != (Object)null && val2 != item) { ModLog.Warning("BalrondBannerColorizer: dye recipe skipped because another ObjectDB item owns prefab ID " + text); continue; } ItemDrop val3 = (((Object)(object)val2 != (Object)null) ? val2.GetComponent<ItemDrop>() : null); if ((Object)(object)val3 == (Object)null) { ModLog.Warning("BalrondBannerColorizer: dye output missing from ObjectDB: " + text); continue; } string text2 = "Recipe_" + text; Recipe orCreateRecipe = GetOrCreateRecipe(text2); Requirement[] array = BuildRequirements(text); if (array != null) { ((Object)orCreateRecipe).name = text2; orCreateRecipe.m_enabled = true; orCreateRecipe.m_amount = 1; orCreateRecipe.m_minStationLevel = 1; orCreateRecipe.m_craftingStation = val; orCreateRecipe.m_repairStation = val; orCreateRecipe.m_item = val3; orCreateRecipe.m_resources = array; if (!ContainsRecipe(objectDb.m_recipes, text2)) { objectDb.m_recipes.Add(orCreateRecipe); } } } } private Requirement[] BuildRequirements(string dyeName) { string dyeRecipe = ModConfig.GetDyeRecipe(dyeName); if (RecipeCostParser.TryBuildRequirements(dyeRecipe, _lookup.GetItemDrop, out var requirements, out var error)) { return requirements; } string defaultDyeRecipe = ModConfig.GetDefaultDyeRecipe(dyeName); ModLog.Warning("BalrondBannerColorizer: invalid synchronized recipe for " + dyeName + " ('" + dyeRecipe + "'): " + error + ". Falling back to '" + defaultDyeRecipe + "'."); if (RecipeCostParser.TryBuildRequirements(defaultDyeRecipe, _lookup.GetItemDrop, out requirements, out var error2)) { return requirements; } ModLog.Error("BalrondBannerColorizer: built-in fallback recipe for " + dyeName + " could not be resolved: " + error2); return null; } private Recipe GetOrCreateRecipe(string recipeName) { if (_recipes.TryGetValue(recipeName, out var value) && (Object)(object)value != (Object)null) { return value; } value = ScriptableObject.CreateInstance<Recipe>(); ((Object)value).name = recipeName; _recipes[recipeName] = value; return value; } private static bool ContainsRecipe(List<Recipe> recipes, string recipeName) { for (int i = 0; i < recipes.Count; i++) { Recipe val = recipes[i]; if ((Object)(object)val != (Object)null && string.Equals(((Object)val).name, recipeName, StringComparison.Ordinal)) { return true; } } return false; } private static GameObject FindItemByStableHash(List<GameObject> items, int hash, string sameName) { if (items == null) { return null; } for (int i = 0; i < items.Count; i++) { GameObject val = items[i]; if (!((Object)(object)val == (Object)null) && !string.IsNullOrEmpty(((Object)val).name) && !string.Equals(((Object)val).name, sameName, StringComparison.Ordinal) && StringExtensionMethods.GetStableHashCode(((Object)val).name) == hash) { return val; } } return null; } private static GameObject FindItemByName(List<GameObject> items, string name) { if (items == null || string.IsNullOrEmpty(name)) { return null; } for (int i = 0; i < items.Count; i++) { GameObject val = items[i]; if ((Object)(object)val != (Object)null && string.Equals(((Object)val).name, name, StringComparison.Ordinal)) { return val; } } return null; } } internal sealed class DyeStationService { private readonly ModAssetCatalog _assets; private bool _warnedPieceCollision; internal DyeStationService(ModAssetCatalog assets) { _assets = assets; } internal void ConfigureAndRegister(ZNetScene scene, PrefabLookupCache prefabs) { GameObject val = _assets?.DyeStation; if (!((Object)(object)val == (Object)null) && !((Object)(object)scene == (Object)null) && prefabs != null) { ConfigurePiece(val, prefabs); ConfigureRavenGuide(val, prefabs); AddToHammer(val, prefabs); } } private static void ConfigurePiece(GameObject station, PrefabLookupCache prefabs) { //IL_002a: 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) Piece component = station.GetComponent<Piece>(); if ((Object)(object)component == (Object)null) { ModLog.Error("BalrondBannerColorizer: Piece missing on piece_dyestation."); return; } component.m_enabled = true; component.m_category = (PieceCategory)1; component.m_usage = (UsageTagFlags)2; ApplyResourceCost(component, prefabs); ApplyCraftingStation(component, prefabs); } private static void ApplyResourceCost(Piece piece, PrefabLookupCache prefabs) { string text = ((ModConfig.DyeStationResourceCost != null) ? ModConfig.DyeStationResourceCost.Value : "Wood:5,Resin:5"); if (TryBuildResourceCost(text, prefabs, out var requirements, out var error)) { piece.m_resources = requirements; return; } ModLog.Warning("BalrondBannerColorizer: invalid Dye Station resource cost ('" + text + "'): " + error + ". Falling back to 'Wood:5,Resin:5'."); if (TryBuildResourceCost("Wood:5,Resin:5", prefabs, out requirements, out var error2)) { piece.m_resources = requirements; } else { ModLog.Error("BalrondBannerColorizer: built-in Dye Station resource cost could not be resolved: " + error2); } } private static bool TryBuildResourceCost(string specification, PrefabLookupCache prefabs, out Requirement[] requirements, out string error) { return RecipeCostParser.TryBuildRequirements(specification, delegate(string prefabName) { GameObject val = prefabs.Get(prefabName); return ((Object)(object)val != (Object)null) ? val.GetComponent<ItemDrop>() : null; }, out requirements, out error); } private static void ApplyCraftingStation(Piece piece, PrefabLookupCache prefabs) { string text = ((ModConfig.DyeStationCraftingStation != null) ? ModConfig.DyeStationCraftingStation.Value : ModConfig.DefaultDyeStationCraftingStation); if (TryResolveCraftingStation(text, prefabs, out var station, out var error)) { piece.m_craftingStation = station; return; } string defaultDyeStationCraftingStation = ModConfig.DefaultDyeStationCraftingStation; ModLog.Warning("BalrondBannerColorizer: invalid Dye Station crafting station ('" + text + "'): " + error + ". Falling back to '" + defaultDyeStationCraftingStation + "'."); if (TryResolveCraftingStation(defaultDyeStationCraftingStation, prefabs, out station, out var error2)) { piece.m_craftingStation = station; return; } piece.m_craftingStation = null; ModLog.Error("BalrondBannerColorizer: built-in Dye Station crafting-station fallback could not be resolved: " + error2); } private static bool TryResolveCraftingStation(string prefabName, PrefabLookupCache prefabs, out CraftingStation station, out string error) { station = null; error = null; string text = (prefabName ?? string.Empty).Trim(); if (text.Length == 0 || string.Equals(text, "NONE", StringComparison.OrdinalIgnoreCase)) { return true; } GameObject val = prefabs.Get(text); if ((Object)(object)val == (Object)null) { error = "prefab not found: " + text; return false; } station = val.GetComponent<CraftingStation>(); if ((Object)(object)station == (Object)null) { error = "prefab has no CraftingStation component: " + text; return false; } return true; } private void AddToHammer(GameObject station, PrefabLookupCache prefabs) { GameObject val = prefabs.Get("Hammer"); PieceTable val2 = (((Object)(object)val != (Object)null) ? val.GetComponent<ItemDrop>() : null)?.m_itemData?.m_shared?.m_buildPieces; if ((Object)(object)val2 == (Object)null) { ModLog.Warning("BalrondBannerColorizer: Hammer PieceTable was not available."); return; } if (val2.m_pieces == null) { val2.m_pieces = new List<GameObject>(); } for (int i = 0; i < val2.m_pieces.Count; i++) { GameObject val3 = val2.m_pieces[i]; if ((Object)(object)val3 != (Object)null && string.Equals(((Object)val3).name, ((Object)station).name, StringComparison.Ordinal)) { if (val3 != station && !_warnedPieceCollision) { _warnedPieceCollision = true; ModLog.Warning("BalrondBannerColorizer: Hammer already contains a different prefab using the Dye Station ID; preserving its owner."); } return; } } val2.m_pieces.Add(station); InvalidateBuildPieceCache(); } private static void InvalidateBuildPieceCache() { ObjectDbCompat.InvalidateBuildPieceCache(ObjectDB.instance); } private static void ConfigureRavenGuide(GameObject station, PrefabLookupCache prefabs) { Transform val = station.transform.Find("GuidePoint"); GameObject val2 = prefabs.Get("piece_workbench"); Transform val3 = (((Object)(object)val2 != (Object)null) ? val2.transform.Find("GuidePoint") : null); GuidePoint val4 = (((Object)(object)val != (Object)null) ? ((Component)val).GetComponent<GuidePoint>() : null); GuidePoint val5 = (((Object)(object)val3 != (Object)null) ? ((Component)val3).GetComponent<GuidePoint>() : null); if (!((Object)(object)val4 == (Object)null) && !((Object)(object)val5 == (Object)null) && !((Object)(object)val5.m_ravenPrefab == (Object)null)) { val4.m_ravenPrefab = val5.m_ravenPrefab; } } } internal static class ObjectDbCompat { private static readonly FieldInfo ItemByHashField = typeof(ObjectDB).GetField("m_itemByHash", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo BuildPiecesField = typeof(ObjectDB).GetField("m_buildPieces", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static bool _warnedItemHashUnavailable; private static bool _warnedBuildPiecesUnavailable; internal static bool TryGetHashedItem(ObjectDB objectDb, int hash, out GameObject prefab) { prefab = null; Dictionary<int, GameObject> itemHashDictionary = GetItemHashDictionary(objectDb); return itemHashDictionary != null && itemHashDictionary.TryGetValue(hash, out prefab) && (Object)(object)prefab != (Object)null; } internal static bool SetHashedItem(ObjectDB objectDb, int hash, GameObject prefab) { if ((Object)(object)prefab == (Object)null) { return false; } Dictionary<int, GameObject> itemHashDictionary = GetItemHashDictionary(objectDb); if (itemHashDictionary == null) { return false; } itemHashDictionary[hash] = prefab; return true; } internal static bool InvalidateBuildPieceCache(ObjectDB objectDb) { if ((Object)(object)objectDb == (Object)null) { return false; } if (BuildPiecesField == null) { WarnBuildPiecesUnavailableOnce("field metadata was not found"); return false; } try { BuildPiecesField.SetValue(objectDb, null); return true; } catch (Exception ex) { WarnBuildPiecesUnavailableOnce(ex.Message); return false; } } private static Dictionary<int, GameObject> GetItemHashDictionary(ObjectDB objectDb) { if ((Object)(object)objectDb == (Object)null || ItemByHashField == null) { WarnItemHashUnavailableOnce(); return null; } try { Dictionary<int, GameObject> dictionary = ItemByHashField.GetValue(objectDb) as Dictionary<int, GameObject>; if (dictionary == null) { WarnItemHashUnavailableOnce(); } return dictionary; } catch (Exception ex) { if (!_warnedItemHashUnavailable) { _warnedItemHashUnavailable = true; ModLog.Warning("BalrondBannerColorizer: ObjectDB item hash cache could not be accessed: " + ex.Message); } return null; } } private static void WarnItemHashUnavailableOnce() { if (!_warnedItemHashUnavailable) { _warnedItemHashUnavailable = true; ModLog.Warning("BalrondBannerColorizer: ObjectDB.m_itemByHash is unavailable; item hash cache could not be updated."); } } private static void WarnBuildPiecesUnavailableOnce(string reason) { if (!_warnedBuildPiecesUnavailable) { _warnedBuildPiecesUnavailable = true; ModLog.Warning("BalrondBannerColorizer: ObjectDB.m_buildPieces cache could not be invalidated" + (string.IsNullOrEmpty(reason) ? "." : (": " + reason))); } } } internal sealed class ObjectDbLookupCache { private readonly Dictionary<string, GameObject> _items = new Dictionary<string, GameObject>(StringComparer.Ordinal); internal void Rebuild(ObjectDB objectDb) { _items.Clear(); if ((Object)(object)objectDb == (Object)null || objectDb.m_items == null) { return; } for (int i = 0; i < objectDb.m_items.Count; i++) { GameObject val = objectDb.m_items[i]; if (!((Object)(object)val == (Object)null) && !string.IsNullOrEmpty(((Object)val).name) && !_items.ContainsKey(((Object)val).name)) { _items.Add(((Object)val).name, val); } } } internal GameObject Get(string prefabName) { if (string.IsNullOrEmpty(prefabName)) { return null; } _items.TryGetValue(prefabName, out var value); return value; } internal ItemDrop GetItemDrop(string prefabName) { GameObject val = Get(prefabName); return ((Object)(object)val != (Object)null) ? val.GetComponent<ItemDrop>() : null; } } internal sealed class PrefabLookupCache { private readonly Dictionary<string, GameObject> _byName = new Dictionary<string, GameObject>(StringComparer.Ordinal); internal int Count => _byName.Count; internal void Rebuild(IList<GameObject> prefabs) { _byName.Clear(); if (prefabs == null) { return; } for (int i = 0; i < prefabs.Count; i++) { GameObject val = prefabs[i]; if (!((Object)(object)val == (Object)null) && !string.IsNullOrEmpty(((Object)val).name) && !_byName.ContainsKey(((Object)val).name)) { _byName.Add(((Object)val).name, val); } } } internal bool TryGet(string prefabName, out GameObject prefab) { if (string.IsNullOrEmpty(prefabName)) { prefab = null; return false; } return _byName.TryGetValue(prefabName, out prefab) && (Object)(object)prefab != (Object)null; } internal GameObject Get(string prefabName) { TryGet(prefabName, out var prefab); return prefab; } } } namespace BalrondBannerColorizer.Patches { internal static class PatchRegistrar { internal static void Register(Harmony harmony) { if (harmony != null) { string[] after = new string[1] { "balrond.astafaraios.BalrondAbsoluteOverhaul" }; Patch(harmony, typeof(ObjectDB), "Awake", null, null, "ObjectDbAwakePostfix", after); Patch(harmony, typeof(ObjectDB), "CopyOtherDB", new Type[1] { typeof(ObjectDB) }, null, "ObjectDbCopyPostfix", after); Patch(harmony, typeof(ZNetScene), "Awake", null, "ZNetSceneAwakePrefix", "ZNetSceneAwakePostfix", after); Patch(harmony, typeof(AudioMan), "Awake", null, null, "AudioManAwakePostfix", after); Patch(harmony, typeof(Piece), "Awake", null, null, "PieceAwakePostfix", after); Patch(harmony, typeof(FejdStartup), "SetupGui", null, null, "FejdStartupSetupGuiPostfix", after); } } private static void Patch(Harmony harmony, Type type, string methodName, Type[] argumentTypes, string prefix = null, string postfix = null, string[] after = null) { //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) MethodInfo methodInfo = ((argumentTypes == null) ? AccessTools.Method(type, methodName, (Type[])null, (Type[])null) : AccessTools.Method(type, methodName, argumentTypes, (Type[])null)); if (methodInfo == null) { ModLog.Warning("BalrondBannerColorizer: Harmony target not found: " + type.Name + "." + methodName); return; } try { HarmonyMethod val = ((prefix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(PatchRegistrar), prefix, (Type[])null)); HarmonyMethod val2 = ((postfix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(PatchRegistrar), postfix, (Type[])null)); if (val != null && after != null) { val.after = after; } if (val2 != null && after != null) { val2.after = after; } harmony.Patch((MethodBase)methodInfo, val, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } catch (Exception ex) { ModLog.Error("BalrondBannerColorizer: failed to patch " + type.Name + "." + methodName + ": " + ex.Message); } } private static void ObjectDbAwakePostfix(ObjectDB __instance) { Safe(delegate { Launch.Runtime?.OnObjectDbReady(__instance); }, "ObjectDB.Awake postfix"); } private static void ObjectDbCopyPostfix(ObjectDB __instance) { Safe(delegate { Launch.Runtime?.OnObjectDbReady(__instance); }, "ObjectDB.CopyOtherDB postfix"); } private static void ZNetSceneAwakePrefix(ZNetScene __instance) { Safe(delegate { Launch.Runtime?.OnZNetScenePrefix(__instance); }, "ZNetScene.Awake prefix"); } private static void ZNetSceneAwakePostfix(ZNetScene __instance) { Safe(delegate { Launch.Runtime?.OnZNetScenePostfix(__instance); }, "ZNetScene.Awake postfix"); } private static void PieceAwakePostfix(Piece __instance) { try { ColorizerInstaller.InstallOnRuntimePiece(__instance); } catch (Exception ex) { ModLog.Error("BalrondBannerColorizer: Piece.Awake colorizer install failed: " + ex); } } private static void AudioManAwakePostfix(AudioMan __instance) { Safe(delegate { AudioRoutingService.OnAudioManAwake(__instance); }, "AudioMan.Awake postfix"); } private static void FejdStartupSetupGuiPostfix() { Safe(delegate { Launch.Runtime?.OnFrontendReady(); }, "FejdStartup.SetupGui shader retry"); } private static void Safe(Action action, string context) { try { action?.Invoke(); } catch (Exception ex) { ModLog.Error("BalrondBannerColorizer: " + context + " failed: " + ex); } } } } namespace BalrondBannerColorizer.Infrastructure { internal static class ModLog { private static ManualLogSource _logger; internal static void Initialize(ManualLogSource logger) { _logger = logger; } internal static void Info(string message) { if (_logger != null) { _logger.LogInfo((object)message); } } internal static void Warning(string message) { if (_logger != null) { _logger.LogWarning((object)message); } } internal static void Error(string message) { if (_logger != null) { _logger.LogError((object)message); } } } } namespace BalrondBannerColorizer.Compatibility { public static class BaoCompatibilityBridge { internal sealed class SharedBannerPieceSnapshot { internal readonly Dictionary<string, PieceState> States = new Dictionary<string, PieceState>(StringComparer.Ordinal); } internal sealed class PieceState { private readonly PieceCategory _category; private readonly UsageTagFlags _usage; private readonly CraftingStation _craftingStation; private readonly string _requirements; private PieceState(PieceCategory category, UsageTagFlags usage, CraftingStation craftingStation, string requirements) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) _category = category; _usage = usage; _craftingStation = craftingStation; _requirements = requirements ?? string.Empty; } internal static PieceState Capture(Piece piece) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) return new PieceState(piece.m_category, piece.m_usage, piece.m_craftingStation, BuildRequirementSignature(piece.m_resources)); } public override bool Equals(object obj) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) return obj is PieceState pieceState && _category == pieceState._category && _usage == pieceState._usage && _craftingStation == pieceState._craftingStation && string.Equals(_requirements, pieceState._requirements, StringComparison.Ordinal); } public override int GetHashCode() { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Expected I4, but got Unknown //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected I4, but got Unknown int num = (int)_category; num = (num * 397) ^ _usage; num = (num * 397) ^ (((Object)(object)_craftingStation != (Object)null) ? ((Object)_craftingStation).GetInstanceID() : 0); return (num * 397) ^ _requirements.GetHashCode(); } private static string BuildRequirementSignature(Requirement[] requirements) { if (requirements == null) { return "<null>"; } StringBuilder stringBuilder = new StringBuilder(requirements.Length * 24); for (int i = 0; i < requirements.Length; i++) { Requirement val = requirements[i]; if (i > 0) { stringBuilder.Append('|'); } if (val == null) { stringBuilder.Append("<null>"); continue; } ItemDrop resItem = val.m_resItem; stringBuilder.Append(((Object)(object)resItem != (Object)null && (Object)(object)((Component)resItem).gameObject != (Object)null) ? ((Object)((Component)resItem).gameObject).name : "<missing>"); stringBuilder.Append(':').Append(val.m_amount); stringBuilder.Append(':').Append(val.m_amountPerLevel); stringBuilder.Append(':').Append(val.m_recover ? '1' : '0'); } return stringBuilder.ToString(); } } public const string BaoGuid = "balrond.astafaraios.BalrondAbsoluteOverhaul"; public const string MinimumSupportedBaoVersion = "2.0.0"; private static readonly string[] BaoSharedBannerPrefabs = new string[11] { "piece_banner01", "piece_banner02", "piece_banner03", "piece_banner04", "piece_banner05", "piece_banner06", "piece_banner07", "piece_banner08", "piece_banner09", "piece_banner10", "piece_banner11" }; private static bool _startupStatusLogged; private static bool _objectDbAuditLogged; private static bool _sceneAuditLogged; public static bool IsBaoLoaded => Chainloader.PluginInfos != null && Chainloader.PluginInfos.ContainsKey("balrond.astafaraios.BalrondAbsoluteOverhaul"); public static string LoadedBaoVersion { get { if (!IsBaoLoaded) { return string.Empty; } try { PluginInfo val = Chainloader.PluginInfos["balrond.astafaraios.BalrondAbsoluteOverhaul"]; return (val != null && val.Metadata != null && val.Metadata.Version != null) ? val.Metadata.Version.ToString() : string.Empty; } catch { return string.Empty; } } } public static bool IsSupportedBaoVersion { get { if (!IsBaoLoaded) { return true; } if (!Version.TryParse(LoadedBaoVersion, out Version result) || !Version.TryParse("2.0.0", out Version result2)) { return false; } return result.CompareTo(result2) >= 0; } } public static string IntegrationMode { get { if (!IsBaoLoaded) { return "Standalone"; } return IsSupportedBaoVersion ? "BAO integrated" : "BAO compatibility warning"; } } public static bool OwnsPrefabId(string prefabId) { if (string.IsNullOrEmpty(prefabId)) { return false; } if (string.Equals(prefabId, "piece_dyestation", StringComparison.Ordinal)) { return true; } for (int i = 0; i < ModAssetCatalog.DyePrefabNames.Length; i++) { if (string.Equals(prefabId, ModAssetCatalog.DyePrefabNames[i], StringComparison.Ordinal)) { return true; } } return false; } public static bool SupportsColorizationTarget(string prefabId) { if (string.IsNullOrEmpty(prefabId)) { return false; } for (int i = 0; i < ColorizerCatalog.TargetPrefabNames.Length; i++) { if (string.Equals(prefabId, ColorizerCatalog.TargetPrefabNames[i], StringComparison.Ordinal)) { return true; } } return false; } internal static void LogStartupStatus() { if (!_startupStatusLogged) { _startupStatusLogged = true; if (!IsBaoLoaded) { ModLog.Info("BalrondBannerColorizer: BAO compatibility: standalone mode."); } else if (!IsSupportedBaoVersion) { ModLog.Warning("BalrondBannerColorizer: BAO compatibility: detected BAO " + (string.IsNullOrEmpty(LoadedBaoVersion) ? "unknown version" : LoadedBaoVersion) + "; tested baseline is >= 2.0.0. Standalone ownership remains active and no BAO internals will be called."); } else { ModLog.Info("BalrondBannerColorizer: BAO compatibility: integrated mode with BAO " + LoadedBaoVersion + "."); } } } internal static SharedBannerPieceSnapshot CaptureSharedBannerPieceState(PrefabLookupCache prefabs) { if (!IsBaoLoaded || prefabs == null) { return null; } SharedBannerPieceSnapshot sharedBannerPieceSnapshot = new SharedBannerPieceSnapshot(); for (int i = 0; i < BaoSharedBannerPrefabs.Length; i++) { string text = BaoSharedBannerPrefabs[i]; GameObject val = prefabs.Get(text); Piece val2 = (((Object)(object)val != (Object)null) ? val.GetComponent<Piece>() : null); if (!((Object)(object)val2 == (Object)null)) { sharedBannerPieceSnapshot.States[text] = PieceState.Capture(val2); } } return sharedBannerPieceSnapshot; } internal static void ValidateSharedBannerPieceState(PrefabLookupCache prefabs, SharedBannerPieceSnapshot before) { if (!IsBaoLoaded || prefabs == null || before == null) { return; } foreach (KeyValuePair<string, PieceState> state in before.States) { GameObject val = prefabs.Get(state.Key); Piece val2 = (((Object)(object)val != (Object)null) ? val.GetComponent<Piece>() : null); if ((Object)(object)val2 == (Object)null) { ModLog.Warning("BalrondBannerColorizer: BAO compatibility: shared banner disappeared during integration: " + state.Key); continue; } PieceState obj = PieceState.Capture(val2); if (!state.Value.Equals(obj)) { ModLog.Warning("BalrondBannerColorizer: BAO compatibility: shared Piece metadata changed while installing colorization on " + state.Key + ". BannerColorizer must not own BAO build costs/station metadata."); } } } internal static void ValidateObjectDb(ObjectDB db, ModAssetCatalog assets) { if (!IsBaoLoaded || _objectDbAuditLogged || (Object)(object)db == (Object)null || assets == null) { return; } int num = 0; for (int i = 0; i < ModAssetCatalog.DyePrefabNames.Length; i++) { string text = ModAssetCatalog.DyePrefabNames[i]; GameObject item = assets.GetItem(text); GameObject val = FindItem(db, text); if (!((Object)(object)val == (Object)null) && val != item) { num++; } } if (num > 0) { ModLog.Warning("BalrondBannerColorizer: BAO compatibility: " + num + " dye prefab ID collision(s) detected in ObjectDB; existing registrations remain authoritative."); } _objectDbAuditLogged = true; } internal static void ValidateScene(ZNetScene scene, PrefabLookupCache prefabs, ModAssetCatalog assets) { if (!IsBaoLoaded || _sceneAuditLogged || (Object)(object)scene == (Object)null || prefabs == null || assets == null) { return; } int num = 0; int num2 = 0; for (int i = 0; i < BaoSharedBannerPrefabs.Length; i++) { GameObject val = prefabs.Get(BaoSharedBannerPrefabs[i]); if (!((Object)(object)val == (Object)null)) { num++; if ((Object)(object)val.GetComponent<BannerColorizer>() != (Object)null) { num2++; } } } int num3 = 0; int num4 = 0; for (int j = 0; j < assets.OwnedPrefabs.Count; j++) { GameObject val2 = assets.OwnedPrefabs[j]; if (!((Object)(object)val2 == (Object)null) && !string.IsNullOrEmpty(((Object)val2).name)) { GameObject val3 = prefabs.Get(((Object)val2).name); if (val3 == val2) { num3++; } else if ((Object)(object)val3 != (Object)null) { num4++; } } } if (num2 != num) { ModLog.Warning("BalrondBannerColorizer: BAO compatibility: colorizer installed on " + num2 + "/" + num + " shared BAO/vanilla banner prefabs."); } if (num4 > 0) { ModLog.Warning("BalrondBannerColorizer: BAO compatibility: " + num4 + " BBC-owned network prefab ID collision(s); first registered prefab remains authoritative."); } if (num2 == num && num4 == 0) { ModLog.Info("BalrondBannerColorizer: BAO compatibility check passed (shared banners=" + num2 + ", BBC-owned prefabs=" + num3 + ")."); } _sceneAuditLogged = true; } internal static void Reset() { _startupStatusLogged = false; _objectDbAuditLogged = false; _sceneAuditLogged = false; } private static GameObject FindItem(ObjectDB db, string prefabId) { if ((Object)(object)db == (Object)null || db.m_items == null || string.IsNullOrEmpty(prefabId)) { return null; } for (int i = 0; i < db.m_items.Count; i++) { GameObject val = db.m_items[i]; if ((Object)(object)val != (Object)null && string.Equals(((Object)val).name, prefabId, StringComparison.Ordinal)) { return val; } } return null; } } } namespace BalrondBannerColorizer.Colorization { [Serializable] public sealed class BannerColorizer : MonoBehaviour, Hoverable, Interactable { private const string RpcPaint = "RPC_Paint"; private const string ZdoColorKey = "colorizerSet"; private const string ZdoActiveKey = "colorizerIsActive"; private readonly List<MeshRenderer> _bodyRenderers = new List<MeshRenderer>(8); private readonly List<MeshRenderer> _destructionRenderers = new List<MeshRenderer>(8); private ZNetView _znetView; private Piece _piece; private MeshRenderer _mainRenderer; private string _prefabName; private string _skinName; private string _defaultSkinName; private bool _renderersReady; private bool _paintRpcRegistered; private bool _rpcRegistrationWarningLogged; private void Awake() { _prefabName = NormalizePrefabName(((Object)((Component)this).gameObject).name); if (ColorizerCatalog.IsTarget(_prefabName)) { _piece = ((Component)this).GetComponent<Piece>(); _defaultSkinName = ColorizerCatalog.GetDefaultSkin(_prefabName); if (!SetupRenderers()) { ModLog.Warning("BalrondBannerColorizer: no supported renderer layout found for " + _prefabName); return; } _renderersReady = true; _znetView = ((Component)this).GetComponent<ZNetView>(); TryRegisterPaintRpc(); RefreshInitialSkin(); } } private void Start() { if (_renderersReady) { TryRegisterPaintRpc(); RefreshInitialSkin(); } } private void TryRegisterPaintRpc() { if (_paintRpcRegistered) { return; } if ((Object)(object)_znetView == (Object)null) { _znetView = ((Component)this).GetComponent<ZNetView>(); } if ((Object)(object)_znetView == (Object)null || !_znetView.IsValid() || _znetView.GetZDO() == null) { return; } try { _znetView.Register<string>("RPC_Paint", (Action<long, string>)RPC_Paint); _paintRpcRegistered = true; RefreshInitialSkin(); } catch (Exception ex) { if (!_rpcRegistrationWarningLogged) { _rpcRegistrationWarningLogged = true; ModLog.Warning("BalrondBannerColorizer: paint RPC registration failed for " + _prefabName + ": " + ex.Message); } } } private void RefreshInitialSkin() { ZDO val = (((Object)(object)_znetView != (Object)null && _znetView.IsValid()) ? _znetView.GetZDO() : null); string text = ((val != null && val.GetBool("colorizerIsActive", false)) ? val.GetString("colorizerSet", "") : _defaultSkinName); if (string.IsNullOrWhiteSpace(text)) { text = _defaultSkinName; } if (!string.Equals(_skinName, text, StringComparison.Ordinal)) { ApplySkin(text); } } public string GetHoverName() { return ((Object)(object)_piece != (Object)null) ? _piece.m_name : ((Object)((Component)this).gameObject).name; } public string GetHoverText() { if (!_paintRpcRegistered) { TryRegisterPaintRpc(); } string text = Localize(GetHoverName()); string text2 = Localize("$bbc_add_dye"); string text3 = Localize("$bbc_current_dye"); string text4 = Localize("$bbc_default_dye"); string text5 = LocalizeColor(_skinName); string text6 = LocalizeColor(_defaultSkinName); return text + "\n[<color=yellow><b>1-8</b></color>] " + text2 + "\n[" + text3 + "] " + text5 + "\n[<color=yellow>" + text4 + "</color>] " + text6; } public float GetHoverOffset() { return 0f; } public bool Interact(Humanoid character, bool repeat, bool alt) { return false; } public bool UseItem(Humanoid user, ItemData item) { //IL_005d: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)user == (Object)null || item == null) { return false; } TryRegisterPaintRpc(); if ((Object)(object)_znetView != (Object)null && _znetView.IsValid() && !_paintRpcRegistered) { return false; } if (!PrivateArea.CheckAccess(((Component)this).transform.position, 0f, true, false)) { return true; } string dyePrefabName = (((Object)(object)item.m_dropPrefab != (Object)null) ? NormalizePrefabName(((Object)item.m_dropPrefab).name) : string.Empty); if (!ColorizerCatalog.TryGetByDye(_prefabName, dyePrefabName, out var pack)) { ((Character)user).Message((MessageType)2, "$msg_wrongitem", 0, (Sprite)null, false); return false; } if (string.Equals(_skinName, pack.Skin, StringComparison.Ordinal)) { ((Character)user).Message((MessageType)2, "$msg_wrongitem", 0, (Sprite)null, false); return false; } ((Character)user).Message((MessageType)2, "$msg_added " + item.m_shared.m_name, 0, (Sprite)null, false); user.GetInventory().RemoveItem(item.m_shared.m_name, 1, -1, true); BroadcastPaint(pack.Skin); return true; } private void BroadcastPaint(string skin) { if ((Object)(object)_znetView == (Object)null || !_znetView.IsValid()) { ApplySkin(skin); return; } _znetView.InvokeRPC(ZNetView.Everybody, "RPC_Paint", new object[1] { skin }); } private void RPC_Paint(long caller, string skin) { if (string.IsNullOrWhiteSpace(skin)) { skin = _defaultSkinName; } if ((Object)(object)_znetView != (Object)null && _znetView.IsValid() && _znetView.IsOwner()) { ZDO zDO = _znetView.GetZDO(); if (zDO != null) { zDO.Set("colorizerSet", skin); zDO.Set("colorizerIsActive", true); } } ApplySkin(skin); } private void ApplySkin(string skin) { if (!ColorizerCatalog.TryGetBySkin(_prefabName, skin, out var pack)) { if (string.Equals(skin, _defaultSkinName, StringComparison.Ordinal) || !ColorizerCatalog.TryGetBySkin(_prefabName, _defaultSkinName, out pack)) { ModLog.Warning("BalrondBannerColorizer: color pack '" + skin + "' not found for " + _prefabName); return; } skin = _defaultSkinName; } ApplyMaterialList(_bodyRenderers, pack.Body); if ((Object)(object)_mainRenderer != (Object)null && (Object)(object)pack.Main != (Object)null) { ApplyMaterial(_mainRenderer, pack.Main); } if ((Object)(object)pack.Destruction != (Object)null) { ApplyMaterialList(_destructionRenderers, pack.Destruction); } _skinName = pack.Skin; } private bool SetupRenderers() { _bodyRenderers.Clear(); _destructionRenderers.Clear(); _mainRenderer = null; if (ColorizerCatalog.IsRoof(_prefabName)) { Transform parent = ((Component)this).transform.Find("New"); AddRendererFromChild(parent, 0, _bodyRenderers); AddRendererFromChild(parent, 1, _bodyRenderers); AddRenderersFromTransform(((Component)this).transform.Find("Worn"), _destructionRenderers); AddRenderersFromTransform(((Component)this).transform.Find("Broken"), _destructionRenderers); AddRenderersFromTransform(((Component)this).transform.Find("Destruction"), _destructionRenderers); return _bodyRenderers.Count > 0; } switch (_prefabName) { case "crystal_wall_1x1": { Transform parent2 = ((Component)this).transform.Find("New"); AddRendererFromChild(parent2, 0, _bodyRenderers); AddRendererFromChild(parent2, 1, _bodyRenderers); return _bodyRenderers.Count > 0; } case "piece_banner01": case "piece_banner02": case "piece_banner03": case "piece_banner04": case "piece_banner05": case "piece_banner06": case "piece_banner07": case "piece_banner08": case "piece_banner09": case "piece_banner10": case "piece_banner11": AddRendererFromPath("default", _bodyRenderers); return _bodyRenderers.Count > 0; case "piece_cloth_hanging_door": AddRendererFromPath("new/cloth", _bodyRenderers); return _bodyRenderers.Count > 0; case "piece_cloth_hanging_door_blue": { Transform parent7 = ((Component)this).transform.Find("new"); AddRendererFromChild(parent7, 0, _bodyRenderers); AddRendererFromChild(parent7, 1, _bodyRenderers); return _bodyRenderers.Count > 0; } case "piece_cloth_hanging_door_blue2": { Transform parent3 = ((Component)this).transform.Find("new"); Transform parent4 = ((Component)this).transform.Find("worn"); Transform parent5 = ((Component)this).transform.Find("broken"); Transform parent6 = ((Component)this).transform.Find("destruction"); AddRendererFromChild(parent3, 1, _bodyRenderers); AddRendererFromChild(parent3, 2, _bodyRenderers); AddRendererFromChild(parent4, 1, _bodyRenderers); AddRendererFromChild(parent4, 2, _bodyRenderers); AddRendererFromChild(parent5, 1, _bodyRenderers); AddRendererFromChild(parent5, 2, _bodyRenderers); AddRendererFromChild(parent6, 0, _bodyRenderers); return _bodyRenderers.Count > 0; } case "jute_carpet": case "jute_carpet_blue": _mainRenderer = GetRendererFromChildIndex(((Component)this).transform, 1); AddRendererFromRootChildIndex(2, _bodyRenderers); AddRendererFromRootChildIndex(3, _bodyRenderers); AddRenderersFromTransform(((Component)this).transform.Find("destruction"), _destructionRenderers); return _bodyRenderers.Count > 0 || (Object)(object)_mainRenderer != (Object)null; default: return false; } } private static void ApplyMaterialList(List<MeshRenderer> renderers, Material material) { if (renderers != null && !((Object)(object)material == (Object)null)) { for (int i = 0; i < renderers.Count; i++) { ApplyMaterial(renderers[i], material); } } } private static void ApplyMaterial(MeshRenderer renderer, Material material) { MaterialVariantBinder.Assign(renderer, material); } private void AddRendererFromPath(string path, List<MeshRenderer> target) { Transform val = ((Component)this).transform.Find(path); if (!((Object)(object)val == (Object)null)) { MeshRenderer component = ((Component)val).GetComponent<MeshRenderer>(); if ((Object)(object)component != (Object)null) { target.Add(component); } } } private static void AddRendererFromChild(Transform parent, int childIndex, List<MeshRenderer> target) { if (!((Object)(object)parent == (Object)null) && childIndex >= 0 && childIndex < parent.childCount) { MeshRenderer component = ((Component)parent.GetChild(childIndex)).GetComponent<MeshRenderer>(); if ((Object)(object)component != (Object)null) { target.Add(component); } } } private void AddRendererFromRootChildIndex(int childIndex, List<MeshRenderer> target) { MeshRenderer rendererFromChildIndex = GetRendererFromChildIndex(((Component)this).transform, childIndex); if ((Object)(object)rendererFromChildIndex != (Object)null) { target.Add(rendererFromChildIndex); } } private static MeshRenderer GetRendererFromChildIndex(Transform parent, int childIndex) { if ((Object)(object)parent == (Object)null || childIndex < 0 || childIndex >= parent.childCount) { return null; } return ((Component)parent.GetChild(childIndex)).GetComponent<MeshRenderer>(); } private static void AddRenderersFromTransform(Transform source, List<MeshRenderer> target) { if (!((Object)(object)source == (Object)null)) { MeshRenderer[] componentsInChildren = ((Component)source).GetComponentsInChildren<MeshRenderer>(true); if (componentsInChildren != null && componentsInChildren.Length != 0) { target.AddRange(componentsInChildren); } } } private static string NormalizePrefabName(string value) { if (string.IsNullOrEmpty(value)) { return string.Empty; } string text = value.Replace("(Clone)", string.Empty); int num = text.IndexOf("(", StringComparison.Ordinal); if (num >= 0) { text = text.Substring(0, num); } return text.Trim(); } private static string Localize(string value) { if (string.IsNullOrEmpty(value) || Localization.instance == null) { return value ?? string.Empty; } return Localization.instance.Localize(value); } private static string LocalizeColor(string skin) { if (string.IsNullOrEmpty(skin)) { return string.Empty; } string text = Localize("$bbc_color_" + skin); return string.IsNullOrEmpty(text) ? skin : text; } } internal static class ColorizerCatalog { private sealed class PieceSet { internal readonly Dictionary<string, ColorizerPack> BySkin = new Dictionary<string, ColorizerPack>(StringComparer.Ordinal); internal readonly Dictionary<string, ColorizerPack> ByDye = new Dictionary<string, ColorizerPack>(StringComparer.Ordinal); } internal static readonly string[] TargetPrefabNames = new string[25] { "piece_banner01", "piece_banner02", "piece_banner03", "piece_banner04", "piece_banner05", "piece_banner06", "piece_banner07", "piece_banner08", "piece_banner09", "piece_banner10", "piece_banner11", "piece_cloth_hanging_door", "piece_cloth_hanging_door_blue", "piece_cloth_hanging_door_blue2", "jute_carpet", "jute_carpet_blue", "crystal_wall_1x1", "darkwood_roof_icorner", "darkwood_roof_icorner_45", "darkwood_roof_ocorner", "darkwood_roof_ocorner_45", "darkwood_roof_top", "darkwood_roof_top_45", "darkwood_roof_45", "darkwood_roof" }; private static readonly string[] RoofPrefabNames = new string[8] { "darkwood_roof_icorner", "darkwood_roof_icorner_45", "darkwood_roof_ocorner", "darkwood_roof_ocorner_45", "darkwood_roof_top", "darkwood_roof_top_45", "darkwood_roof_45", "darkwood_roof" }; private static readonly Dictionary<string, string> DefaultSkinByPrefab = new Dictionary<string, string>(StringComparer.Ordinal) { { "darkwood_roof_icorner", "black" }, { "darkwood_roof_icorner_45", "black" }, { "darkwood_roof_ocorner", "black" }, { "darkwood_roof_ocorner_45", "black" }, { "darkwood_roof_top", "black" }, { "darkwood_roof_top_45", "black" }, { "darkwood_roof_45", "black" }, { "darkwood_roof", "black" }, { "crystal_wall_1x1", "white" }, { "piece_banner01", "black" }, { "piece_banner02", "blue" }, { "piece_banner03", "red" }, { "piece_banner04", "red" }, { "piece_banner05", "green" }, { "piece_banner06", "blue" }, { "piece_banner07", "blue" }, { "piece_banner08", "yellow" }, { "piece_banner09", "purple" }, { "piece_banner10", "orange" }, { "piece_banner11", "white" }, { "piece_cloth_hanging_door", "red" }, { "piece_cloth_hanging_door_blue", "blue" }, { "piece_cloth_hanging_door_blue2", "blue" }, { "jute_carpet", "red" }, { "jute_carpet_blue", "blue" } }; private static readonly HashSet<string> RoofSet = new HashSet<string>(RoofPrefabNames, StringComparer.Ordinal); private static readonly Dictionary<string, PieceSet> Sets = new Dictionary<string, PieceSet>(StringComparer.Ordinal); internal static void Build(ModAssetCatalog assets) { Sets.Clear(); if (assets == null) { return; } AddSimple(assets, "crystal_wall_1x1", "crystalWall1"); for (int i = 1; i <= 11; i++) { AddSimple(assets, "piece_banner" + i.ToString("00"), "banner" + i.ToString("00")); } AddSimple(assets, "piece_cloth_hanging_door", "bannerHanging"); AddSimple(assets, "piece_cloth_hanging_door_blue", "bannerHanging2"); AddSimple(assets, "piece_cloth_hanging_door_blue2", "bannerHanging3"); AddComplex(assets, "jute_carpet", "juteCarpet", "juteCarpetMain", "juteCarpetDestruction"); AddComplex(assets, "jute_carpet_blue", "juteCarpet2", "juteCarpetMain2", "juteCarpetDestruction2"); for (int j = 0; j < RoofPrefabNames.Length; j++) { AddComplex(assets, RoofPrefabNames[j], "roofNew", "roofNew", "roofWorn"); } int num = TargetPrefabNames.Length * ModAssetCatalog.ColorNames.Length; int num2 = 0; foreach (PieceSet value in Sets.Values) { num2 += value.BySkin.Count; } if (num2 != num) { ModLog.Warning("BalrondBannerColorizer" + $": color material catalog is incomplete ({num2}/{num} variants)."); } } internal static bool IsTarget(string prefabName) { return !string.IsNullOrEmpty(prefabName) && Sets.ContainsKey(prefabName); } internal static bool IsTargetInstanceName(string name) { if (string.IsNullOrEmpty(name)) { return false; } if (IsTarget(name)) { return true; } for (int i = 0; i < TargetPrefabNames.Length; i++) { string text = TargetPrefabNames[i]; if (name.Length >= text.Length + 7 && string.CompareOrdinal(name, 0, text, 0, text.Length) == 0) { string a = name.Substring(text.Length).Trim(); if (string.Equals(a, "(Clone)", StringComparison.Ordinal)) { return IsTarget(text); } } } return false; } internal static bool IsRoof(string prefabName) { return !string.IsNullOrEmpty(prefabName) && RoofSet.Contains(prefabName); } internal static string GetDefaultSkin(string prefabName) { if (!string.IsNullOrEmpty(prefabName) && DefaultSkinByPrefab.TryGetValue(prefabName, out var value)) { return value; } return "black"; } internal static bool TryGetBySkin(string prefabName, string skin, out ColorizerPack pack) { pack = null; if (string.IsNullOrEmpty(prefabName) || string.IsNullOrEmpty(skin) || !Sets.TryGetValue(prefabName, out var value)) { return false; } return value.BySkin.TryGetValue(skin, out pack) && pack != null; } internal static bool TryGetByDye(string prefabName, string dyePrefabName, out ColorizerPack pack) { pack = null; if (string.IsNullOrEmpty(prefabName) || string.IsNullOrEmpty(dyePrefabName) || !Sets.TryGetValue(prefabName, out var value)) { return false; } return value.ByDye.TryGetValue(dyePrefabName, out pack) && pack != null; } internal static void Clear() { Sets.Clear(); } private static void AddSimple(ModAssetCatalog assets, string pieceName, string bodySet) { AddComplex(assets, pieceName, bodySet, null, null); } private static void AddComplex(ModAssetCatalog assets, string pieceName, string bodySet, string mainSet, string destructionSet) { PieceSet pieceSet = new PieceSet(); for (int i = 0; i < ModAssetCatalog.ColorNames.Length; i++) { string text = ModAssetCatalog.ColorNames[i]; string text2 = ModAssetCatalog.DyePrefabNames[i]; Material material = assets.GetMaterial(bodySet, text); Material val = ((mainSet != null) ? assets.GetMaterial(mainSet, text) : null); Material val2 = ((destructionSet != null) ? assets.GetMaterial(destructionSet, text) : null); if ((Object)(object)material == (Object)null || (mainSet != null && (Object)(object)val == (Object)null) || (destructionSet != null && (Object)(object)val2 == (Object)null)) { ModLog.Warning("BalrondBannerColorizer: material variant missing for " + pieceName + "/" + text); continue; } ColorizerPack value = new ColorizerPack(pieceName, text, text2, material, val, val2); pieceSet.BySkin[text] = value; pieceSet.ByDye[text2] = value; } Sets[pieceName] = pieceSet; } } internal static class ColorizerInstaller { private static readonly Dictionary<string, string> ExplicitDefaultMaterialPieces = new Dictionary<string, string>(StringComparer.Ordinal) { { "piece_banner08", "yellow" }, { "piece_banner05", "green" }, { "piece_banner07", "blue" }, { "piece_banner09", "purple" }, { "piece_banner10", "orange" }, { "piece_banner11", "white" } }; internal static void Install(PrefabLookupCache prefabs) { if (prefabs == null) { return; } int num = 0; int num2 = 0; for (int i = 0; i < ColorizerCatalog.TargetPrefabNames.Length; i++) { string text = ColorizerCatalog.TargetPrefabNames[i]; GameObject val = prefabs.Get(text); if ((Object)(object)val == (Object)null) { ModLog.Warning("BalrondBannerColorizer: compatible vanilla prefab not found: " + text); continue; } num2++; if (InstallOnPiece(val)) { num++; } if (ExplicitDefaultMaterialPieces.TryGetValue(text, out var value)) { ApplyLegacyDefaultMaterial(val, text, value); } } ModLog.Info("BalrondBannerColorizer" + $": colorizer scene prefabs: {num2}/{ColorizerCatalog.TargetPrefabNames.Length} targets found, {num} new component(s)."); GameObject hammer = prefabs.Get("Hammer"); InstallOnHammer(hammer); } internal static void InstallOnObjectDb(ObjectDB db) { if ((Object)(object)db == (Object)null || db.m_items == null) { return; } for (int i = 0; i < db.m_items.Count; i++) { GameObject val = db.m_items[i]; if ((Object)(object)val != (Object)null && string.Equals(((Object)val).name, "Hammer", StringComparison.Ordinal)) { InstallOnHammer(val); break; } } } internal static void InstallOnRuntimePiece(Piece piece) { if (!((Object)(object)piece == (Object)null) && !((Object)(object)((Component)piece).gameObject == (Object)null) && ColorizerCatalog.IsTargetInstanceName(((Object)((Component)piece).gameObject).name)) { InstallOnPiece(((Component)piece).gameObject); } } private static bool InstallOnPiece(GameObject prefab) { if ((Object)(object)prefab == (Object)null || (Object)(object)prefab.GetComponent<BannerColorizer>() != (Object)null) { return false; } prefab.AddComponent<BannerColorizer>(); return (Object)(object)prefab.GetComponent<BannerColorizer>() != (Object)null; } private static void InstallOnHammer(GameObject hammer) { ItemDrop val = (((Object)(object)hammer != (Object)null) ? hammer.GetComponent<ItemDrop>() : null); PieceTable val2 = (((Object)(object)val != (Object)null && val.m_itemData != null && val.m_itemData.m_shared != null) ? val.m_itemData.m_shared.m_buildPieces : null); if ((Object)(object)val2 == (Object)null || val2.m_pieces == null) { return; } int num = 0; for (int i = 0; i < val2.m_pieces.Count; i++) { GameObject val3 = val2.m_pieces[i]; if ((Object)(object)val3 != (Object)null && ColorizerCatalog.IsTarget(((Object)val3).name) && InstallOnPiece(val3)) { num++; } } if (num > 0) { ModLog.Info("BalrondBannerColorizer" + $": colorizer attached to {num} separate Hammer PieceTable prefab(s)."); } } private static void ApplyLegacyDefaultMaterial(GameObject prefab, string prefabName, string skin) { if (!ColorizerCatalog.TryGetBySkin(prefabName, skin, out var pack) || (Object)(object)pack.Body == (Object)null) { return; } Transform val = prefab.transform.Find("default"); if (!((Object)(object)val == (Object)null)) { MeshRenderer component = ((Component)val).GetComponent<MeshRenderer>(); if ((Object)(object)component != (Object)null) { MaterialVariantBinder.Assign(component, pack.Body); } } } } internal sealed class ColorizerPack { internal readonly Material Body; internal readonly Material Main; internal readonly Material Destruction; internal readonly string PieceName; internal readonly string Skin; internal readonly string DyePrefabName; internal ColorizerPack(string pieceName, string skin, string dyePrefabName, Material body, Material main = null, Material destruction = null) { PieceName = pieceName; Skin = skin; DyePrefabName = dyePrefabName; Body = body; Main = main; Destruction = destruction; } } internal static class MaterialVariantBinder { internal static void Assign(MeshRenderer renderer, Material immutableVariant) { if (!((Object)(object)renderer == (Object)null) && !((Object)(object)immutableVariant == (Object)null) && ((Renderer)renderer).sharedMaterial != immutableVariant) { ((Renderer)renderer).sharedMaterial = immutableVariant; } } } } namespace BalrondBannerColorizer.Config { internal static class ModConfig { private const string ServerSection = "00 - Server"; private const string DyeRecipeSection = "10 - Dye Recipes"; private const string DyeStationSection = "20 - Dye Station"; private static readonly Dictionary<string, string> DefaultDyeRecipes = new Dictionary<string, string>(StringComparer.Ordinal) { { "DyeBlack", "Coal:1" }, { "DyeBlue", "Blueberries:2" }, { "DyeGreen", "Guck:1" }, { "DyeOrange", "Raspberry:1,Cloudberry:1" }, { "DyePurple", "Raspberry:1,Blueberries:1" }, { "DyeRed", "Bloodbag:1" }, { "DyeWhite", "BoneFragments:2" }, { "DyeYellow", "Cloudberry:2" } }; private static readonly Dictionary<string, ConfigEntry<string>> DyeRecipeEntries = new Dictionary<string, ConfigEntry<string>>(StringComparer.Ordinal); internal const string DefaultDyeStationResourceCost = "Wood:5,Resin:5"; internal const string NoCraftingStation = "NONE"; internal static ConfigEntry<bool> LockConfiguration { get; private set; } internal static ConfigEntry<string> DyeStationResourceCost { get; private set; } internal static ConfigEntry<string> DyeStationCraftingStation { get; private set; } internal static string DefaultDyeStationCraftingStation { get; private set; } = "NONE"; internal static event Action Changed; internal static void Initialize(Launch plugin, string defaultDyeStationCraftingStation) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Expected O, but got Unknown //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Expected O, but got Unknown //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Expected O, but got Unknown //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Expected O, but got Unknown if ((Object)(object)plugin == (Object)null) { throw new ArgumentNullException("plugin"); } ResetEntries(); DefaultDyeStationCraftingStation = NormalizeCraftingStationDefault(defaultDyeStationCraftingStation); LockConfiguration = ((BaseUnityPlugin)plugin).Config.Bind<bool>("00 - Server", "Lock Configuration", true, new ConfigDescription("When enabled, all BalrondBannerColorizer recipe and Dye Station settings are controlled by the server/host.", (AcceptableValueBase)null, Array.Empty<object>())); ServerSyncBridge.AddLockingConfigEntry(LockConfiguration); foreach (KeyValuePair<string, string> defaultDyeRecipe in DefaultDyeRecipes) { ConfigEntry<string> val = ((BaseUnityPlugin)plugin).Config.Bind<string>("10 - Dye Recipes", defaultDyeRecipe.Key, defaultDyeRecipe.Value, new ConfigDescription("Resource cost for " + defaultDyeRecipe.Key + ". Format: ItemPrefab:Amount,ItemPrefab:Amount. Use NONE for a zero-cost recipe.", (AcceptableValueBase)null, Array.Empty<object>())); val.SettingChanged += OnSettingChanged; DyeRecipeEntries[defaultDyeRecipe.Key] = val; ServerSyncBridge.AddConfigEntry<string>(val); } DyeStationResourceCost = ((BaseUnityPlugin)plugin).Config.Bind<string>("20 - Dye Station", "Resource Cost", "Wood:5,Resin:5", new ConfigDescription("Build resource cost for piece_dyestation. Format: ItemPrefab:Amount,ItemPrefab:Amount. Use NONE for zero build cost.", (AcceptableValueBase)null, Array.Empty<object>())); DyeStationResourceCost.SettingChanged += OnSettingChanged; ServerSyncBridge.AddConfigEntry<string>(DyeStationResourceCost); DyeStationCraftingStation = ((BaseUnityPlugin)plugin).Config.Bind<string>("20 - Dye Station", "Crafting Station", DefaultDyeStationCraftingStation, new ConfigDescription("Crafting-station prefab required to build piece_dyestation, for example piece_workbench or forge. Use NONE for no crafting-station requirement.", (AcceptableValueBase)null, Array.Empty<object>())); DyeStationCraftingStation.SettingChanged += OnSettingChanged; ServerSyncBridge.AddConfigEntry<string>(DyeStationCraftingStation); } internal static string GetDyeRecipe(string dyePrefabName) { if (!string.IsNullOrEmpty(dyePrefabName) && DyeRecipeEntries.TryGetValue(dyePrefabName, out var value) && value != null) { return value.Value; } return GetDefaultDyeRecipe(dyePrefabName); } internal static string GetDefaultDyeRecipe(string dyePrefabName) { if (!string.IsNullOrEmpty(dyePrefabName) && DefaultDyeRecipes.TryGetValue(dyePrefabName, out var value)) { return value; } return "NONE"; } internal static void Reset() { ResetEntries(); LockConfiguration = null; DyeStationResourceCost = null; DyeStationCraftingStation = null; DefaultDyeStationCraftingStation = "NONE"; ModConfig.Changed = null; } private static void ResetEntries() { foreach (ConfigEntry<string> value in DyeRecipeEntries.Values) { if (value != null) { value.SettingChanged -= OnSettingChanged; } } DyeRecipeEntries.Clear(); if (DyeStationResourceCost != null) { DyeStationResourceCost.SettingChanged -= OnSettingChanged; } if (DyeStationCraftingStation != null) { DyeStationCraftingStation.SettingChanged -= OnSettingChanged; } } private static string NormalizeCraftingStationDefault(string value) { string text = (value ?? string.Empty).Trim(); return (text.Length == 0) ? "NONE" : text; } private static void OnSettingChanged(object sender, EventArgs args) { ModConfig.Changed?.Invoke(); } } internal static class RecipeCostParser { internal static bool TryBuildRequirements(string specification, Func<string, ItemDrop> itemResolver, out Requirement[] requirements, out string error) { //IL_0258: Unknown result type (might be due to invalid IL or missing references) //IL_025d: Unknown result type (might be due to invalid IL or missing references) //IL_0265: Unknown result type (might be due to invalid IL or missing references) //IL_0273: Unknown result type (might be due to invalid IL or missing references) //IL_027a: Unknown result type (might be due to invalid IL or missing references) //IL_0282: Expected O, but got Unknown requirements = null; error = null; if (itemResolver == null) { error = "item resolver is unavailable"; return false; } string text = (specification ?? string.Empty).Trim(); if (text.Length == 0) { error = "recipe is empty"; return false; } if (string.Equals(text, "NONE", StringComparison.OrdinalIgnoreCase)) { requirements = Array.Empty<Requirement>(); return true; } string[] array = text.Split(new char[1] { ',' }); List<string> list = new List<string>(array.Length); Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.Ordinal); for (int i = 0; i < array.Length; i++) { string text2 = array[i].Trim(); if (text2.Length == 0) { error = "empty ingredient entry"; return false; } int num = text2.LastIndexOf(':'); if (num <= 0 || num == text2.Length - 1) { error = "ingredient '" + text2 + "' must use PrefabName:Amount"; return false; } string text3 = text2.Substring(0, num).Trim(); string s = text2.Substring(num + 1).Trim(); if (text3.Length == 0) { error = "ingredient prefab name is empty"; return false; } if (!int.TryParse(s, out var result) || result <= 0) { error = "ingredient '" + text2 + "' has an invalid amount"; return false; } if (dictionary.TryGetValue(text3, out var value)) { long num2 = (long)value + (long)result; if (num2 > int.MaxValue) { error = "ingredient amount overflow for '" + text3 + "'"; return false; } dictionary[text3] = (int)num2; } else { list.Add(text3); dictionary.Add(text3, result); } } Requirement[] array2 = (Requirement[])(object)new Requirement[list.Count]; for (int j = 0; j < list.Count; j++) { string text4 = list[j]; ItemDrop val = itemResolver(text4); if ((Object)(object)val == (Object)null) { error = "item prefab not found: " + text4; return false; } array2[j] = new Requirement { m_resItem = val, m_amount = dictionary[text4], m_amountPerLevel = 0, m_recover = true }; } requirements = array2; return true; } } internal static class ServerSyncBridge { private static ConfigSync _sync; internal static void Initialize(string pluginGuid, string displayName, string version) { if (string.IsNullOrWhiteSpace(pluginGuid)) { throw new ArgumentException("Plugin GUID is required.", "pluginGuid"); } _sync = new ConfigSync(pluginGuid) { DisplayName = displayName, CurrentVersion = version, MinimumRequiredVersion = version }; } internal static void AddLockingConfigEntry(ConfigEntry<bool> entry) { if (entry == null) { throw new ArgumentNullException("entry"); } EnsureInitialized().AddLockingConfigEntry<bool>(entry); } internal static void AddConfigEntry<T>(ConfigEntry<T> entry) { if (entry == null) { throw new ArgumentNullException("entry"); } SyncedConfigEntry<T> syncedConfigEntry = EnsureInitialized().AddConfigEntry<T>(entry); syncedConfigEntry.SynchronizedConfig = true; } internal static void Reset() { _sync = null; } private static ConfigSync EnsureInitialized() { if (_sync == null) { throw new InvalidOperationException("ServerSyncBridge was used before initialization."); } return _sync; } } } namespace BalrondBannerColorizer.Assets { internal static class AudioRoutingService { private sealed class ReferenceComparer : IEqualityComparer<GameObject> { internal static readonly ReferenceComparer Instance = new ReferenceComparer(); public bool Equals(GameObject x, GameObject y) { return x == y; } public int GetHashCode(GameObject obj) { return RuntimeHelpers.GetHashCode(obj); } } private static readonly List<GameObject> Roots = new List<GameObject>(); private static readonly HashSet<GameObject> RootSet = new HashSet<GameObject>(ReferenceComparer.Instance); private static readonly HashSet<string> WarningKeys = new HashSet<string>(StringComparer.Ordinal); private static readonly Dictionary<string, FieldInfo> AudioManFields = new Dictionary<string, FieldInfo>(StringComparer.Ordinal); private static AudioMan _liveAudioMan; private static bool _reportedNoSources; internal static void Register(IEnumerable<GameObject> roots) { if (roots == null) { return; } foreach (GameObject root in roots) { if ((Object)(object)root != (Object)null && RootSet.Add(root)) { Roots.Add(root); } } } internal static void OnAudioManAwake(AudioMan audioMan) { if (!((Object)(object)audioMan == (Object)null)) { _liveAudioMan = audioMan; Refresh(); } } internal static void Refresh() { int num = 0; for (int i = 0; i < Roots.Count; i++) { GameObject val = Roots[i]; if ((Object)(object)val != (Object)null) { num += val.GetComponentsInChildren<AudioSource>(true).Length; } } if (num == 0) { if (!_reportedNoSources) { _reportedNoSources = true; ModLog.Info("BalrondBannerColorizer: audio routing not applicable: no owned direct AudioSource components; external FX remain with Valheim."); } } else { if ((Object)(object)_liveAudioMan == (Object)null) { return; } AudioMixer val2 = ResolveLiveMixer(_liveAudioMan); AudioMixerGroup val3 = (((Object)(object)val2 != (Object)null) ? (FindExactGroup(val2, "SFX") ?? FindExactGroup(val2, "Effects")) : null); if ((Object)(object)val2 == (Object)null || (Object)(object)val3 == (Object)null) { WarnOnce("sfx-unavailable", "BalrondBannerColorizer: live SFX mixer group is unavailable; preserved existing audio routes (no Master fallback)."); return; } int num2 = 0; int num3 = 0; int num4 = 0; for (int j = 0; j < Roots.Count; j++) { GameObject val4 = Roots[j]; if ((Object)(object)val4 == (Object)null) { continue; } AudioSource[] componentsInChildren = val4.GetComponentsInChildren<AudioSource>(true); foreach (AudioSource val5 in componentsInChildren) { if ((Object)(object)val5 == (Object)null) { continue; } AudioMixerGroup outputAudioMixerGroup = val5.outputAudioMixerGroup; if ((Object)(object)outputAudioMixerGroup != (Object)null && ResolveMixer(outputAudioMixerGroup) == val2 && !string.Equals(((Object)outputAudioMixerGroup).name, "Master", StringComparison.OrdinalIgnoreCase)) { num2++; continue; } try { if (outputAudioMixerGroup != val3) { val5.outputAudioMixerGroup = val3; } if (val5.outputAudioMixerGroup == val3) { num3++; } else { num4++; } } catch (Exception ex) { num4++; WarnOnce("source-route", "BalrondBannerColorizer: audio source routing failed: " + ex.Message); } } } if (num4 == 0) { ModLog.Info("BalrondBannerColorizer" + $": audio routing verified: {num2} live group(s) preserved, {num3} routed to SFX."); } else { ModLog.Warning("BalrondBannerColorizer" + $": audio routing incomplete: {num4} owned source(s) unresolved."); } } } internal static void Clear() { Roots.Clear(); RootSet.Clear(); WarningKeys.Clear(); AudioManFields.Clear(); _liveAudioMan = null; _reportedNoSources = false; } private static AudioMixer ResolveLiveMixer(AudioMan audioMan) { return ResolveMixer(ReadField(audioMan, "m_masterMixer")) ?? ResolveMixer(ReadField(audioMan, "m_ambientMixer")) ?? ResolveMixer(ReadField(audioMan, "m_musicMixer")); } private static object ReadField(AudioMan audioMan, string name) { try { if (!AudioManFields.TryGetValue(name, out var value)) { value = typeof(AudioMan).GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); AudioManFields[name] = value; } return (value != null) ? value.GetValue(audioMan) : null; } catch (Exception ex) { WarnOnce("field-" + name, "BalrondBannerColorizer: cannot read AudioMan." + name + ": " + ex.Message); return null; } } private static AudioMixer ResolveMixer(object source) { AudioMixer val = (AudioMixer)((source is AudioMixer) ? source : null); if (val != null) { return val; } AudioMixerGroup val2 = (AudioMixerGroup)((source is AudioMixerGroup) ? source : null); if (val2 != null) { try { return val2.audioMixer; } catch { return null; } } AudioSource val3 = (AudioSource)((source is AudioSource) ? source : null); if (val3 != null) { try { return ((Object)(object)val3.outputAudioMixerGroup != (Object)null) ? val3.outputAudioMixerGroup.audioMixer : null; } catch { return null; } } return null; } private static AudioMixerGroup FindExactGroup(AudioMixer mixer, string name) { try { AudioMixerGroup[] array = mixer.FindMatchingGroups(name); if (array != null) { for (int i = 0; i < array.Length; i++) { if ((Object)(object)array[i] != (Object)null && string.Equals(((Object)array[i]).name, name, StringComparison.OrdinalIgnoreCase)) { return array[i]; } } } } catch (Exception ex) { WarnOnce("group-" + name, "BalrondBannerColorizer: SFX group lookup failed: " + ex.Message); } return null; } private static void WarnOnce(string key, string message) { if (WarningKeys.Add(key)) { ModLog.Warning(message); } } } internal sealed class FxReplacement { private readonly Dictionary<string, GameObject> _prefabs = new Dictionary<string, GameObject>(StringComparer.Ordinal); private ZNetScene _scene; private int _prefabCount = -1; internal void Prepare(ZNetScene scene) { EnsureLookup(scene); } internal void RepairOwnedPrefabs(IEnumerable<GameObject> roots, ZNetScene scene) { if (roots == null || (Object)(object)scene == (Object)null) { return; } EnsureLookup(scene); foreach (GameObject root in roots) { RepairPrefab(root, scene); } } internal void Clear() { _scene = null; _prefabCount = -1; _prefabs.Clear(); } private void RepairPrefab(GameObject root, ZNetScene scene) { if (!((Object)(object)root == (Object)null)) { Piece component = root.GetComponent<Piece>(); if ((Object)(object)component != (Object)null) { Repair(component.m_placeEffect, scene); } CraftingStation component2 = root.GetComponent<CraftingStation>(); if ((Object)(object)component2 != (Object)null) { Repair(component2.m_craftItemEffects, scene); Repair(component2.m_repairItemDoneEffects, scene); } WearNTear component3 = root.GetComponent<WearNTear>(); if ((Object)(object)component3 != (Object)null) { Repair(component3.m_hitEffect, scene); Repair(component3.m_destroyedEffect, scene); } Destructible component4 = root.GetComponent<Destructible>(); if ((Object)(object)component4 != (Object)null) { Repair(component4.m_hitEffect, scene); Repair(component4.m_destroyedEffect, scene); } SpawnArea component5 = root.GetComponent<SpawnArea>(); if ((Object)(object)component5 != (Object)null) { Repair(component5.m_spawnEffects, scene); } Projectile component6 = root.GetComponent<Projectile>(); if ((Object)(object)component6 != (Object)null) { Repair(component6.m_hitEffects, scene); Repair(component6.m_hitWaterEffects, scene); Repair(component6.m_spawnOnHitEffects, scene); } Pickable component7 = root.GetComponent<Pickable>(); if ((Object)(object)component7 != (Object)null) { Repair(component7.m_pickEffector, scene); } MineRock5 component8 = root.GetComponent<MineRock5>(); if ((Object)(object)component8 != (Object)null) { Repair(component8.m_hitEffect, scene); Repair(component8.m_destroyedEffect, scene); } MineRock component9 = root.GetComponent<MineRock>(); if ((Object)(object)component9 != (Object)null) { Repair(component9.m_hitEffect, scene); Repair(component9.m_destroyedEffect, scene); } ItemDrop component10 = root.GetComponent<ItemDrop>(); if ((Object)(object)component10 != (Object)null && component10.m_itemData != null && component10.m_itemData.m_shared != null) { SharedData shared = component10.m_itemData.m_shared; Repair(shared.m_hitEffect, scene); Repair(shared.m_hitTerrainEffect, scene); Repair(shared.m_holdStartEffect, scene); Repair(shared.m_trailStartEffect, scene); Repair(shared.m_blockEffect, scene); } } } private void Repair(EffectList effectList, ZNetScene scene) { if (effectList == null || effectList.m_effectPrefabs == null) { return; } EnsureLookup(scene); EffectData[] effectPrefabs = effectList.m_effectPrefabs; foreach (EffectData val in effectPrefabs) { if (val != null && !((Object)(object)val.m_prefab == (Object)null)) { string name = ((Object)val.m_prefab).name; if (_prefabs.TryGetValue(name, out var value) && (Object)(object)value != (Object)null) { val.m_prefab = value; } } } } private void EnsureLookup(ZNetScene scene) { if ((Object)(object)scene == (Object)null || scene.m_prefabs == null) { Clear(); return; } int count = scene.m_prefabs.Count; if (_scene == scene && _prefabCount == count) { return; } _scene = scene; _prefabCount = count; _prefabs.Clear(); for (int i = 0; i < scene.m_prefabs.Count; i++) { GameObject val = scene.m_prefabs[i]; if (!((Object)(object)val == (Object)null) && !string.IsNullOrEmpty(((Object)val).name) && !_prefabs.ContainsKey(((Object)val).name)) { _prefabs.Add(((Object)val).name, val); } } } } internal sealed class ModAssetCatalog { internal static readonly string[] ColorNames = new string[8] { "black", "blue", "green", "orange", "purple", "red", "white", "yellow" }; internal static readonly string[] DyePrefabNames = new string[8] { "DyeBlack", "DyeBlue", "DyeGreen", "DyeOrange", "DyePurple", "DyeRed", "DyeWhite", "DyeYellow" }; private readonly Dictionary<string, GameObject> _items = new Dictionary<string, GameObject>(StringComparer.Ordinal); private readonly Dictionary<string, Dictionary<string, Material>> _materialSets = new Dictionary<string, Dictionary<string, Material>>(StringComparer.Ordinal); private readonly List<GameObject> _ownedPrefabs = new List<GameObject>(); internal AssetBundle AssetBundle { get; set; } internal GameObject DyeStation { get; set; } internal IReadOnlyList<GameObject> OwnedPrefabs => _ownedPrefabs; internal IEnumerable<GameObject> DyeItems => _items.Values; internal void AddItem(GameObject prefab) { if (!((Object)(object)prefab == (Object)null) && !string.IsNullOrEmpty(((Object)prefab).name)) { _items[((Object)prefab).name] = prefab; AddOwnedPrefab(prefab); } } internal void AddOwnedPrefab(GameObject prefab) { if ((Object)(object)prefab == (Object)null) { return; } for (int i = 0; i < _ownedPrefabs.Count; i++) { if (_ownedPrefabs[i] == prefab) { return; } } _ownedPrefabs.Add(prefab); } internal GameObject GetItem(string prefabName) { if (string.IsNullOrEmpty(prefabName)) { return null; } _items.TryGetValue(prefabName, out var value); return value; } internal void SetMaterial(string setName, string colorName, Material material) { if (!string.IsNullOrEmpty(setName) && !string.IsNullOrEmpty(colorName)) { if (!_materialSets.TryGetValue(setName, out var value)) { value = new Dictionary<string, Material>(StringComparer.Ordinal); _materialSets.Add(setName, value); } value[colorName] = material; } } internal Material GetMaterial(string setName, string colorName) { if (string.IsNullOrEmpty(setName) || string.IsNullOrEmpty(colorName)) { return null; } if (!_materialSets.TryGetValue(setName, out var value)) { return null; } value.TryGetValue(colorName, out var value2); return value2; } internal void Clear() { _items.Clear(); _materialSets.Clear(); _ownedPrefabs.Clear(); DyeStation = null; } } internal sealed class ModResourceLoader { private const string BundleFileName = "bannercolorizer"; private const string AssetRoot = "Assets/Custom/BalrondBannerColorizer/"; private static readonly string[] MaterialSetNames = new string[23] { "crystalWall1", "banner01", "banner02", "banner03", "banner04", "banner05", "banner06", "banner07", "banner08", "banner09", "banner10", "banner11", "bannerHanging", "bannerHanging2", "bannerHanging3", "juteCarpet", "juteCarpetMain", "juteCarpetDestruction", "juteCarpet2", "juteCarpetMain2", "juteCarpetDestruction2", "roofNew", "roofWorn" }; internal bool Load(ModAssetCatalog catalog) { if (catalog == null) { return false; } catalog.Clear(); AssetBundle val = LoadEmbeddedBundle("bannercolorizer"); if ((Object)(object)val == (Object)null) { return false; } catalog.AssetBundle = val; LoadDyeItems(val, catalog); LoadDyeStation(val, catalog); LoadMaterials(val, catalog); if ((Object)(object)catalog.DyeStation != (Object)null) { catalog.AddOwnedPrefab(catalog.DyeStation); ShaderReplacement.Replace(catalog.DyeStation); } foreach (GameObject dyeItem in catalog.DyeItems) { ShaderReplacement.Replace(dyeItem); } return (Object)(object)catalog.DyeStation != (Object)null; } internal void Unload(ModAssetCatalog catalog) { if ((Object)(object)catalog?.AssetBundle == (Object)null) { return; } try { catalog.AssetBundle.Unload(false); } catch (Exception ex) { ModLog.Warning("BalrondBannerColorizer: AssetBundle unload failed: " + ex.Message); } } private static void LoadDyeItems(AssetBundle bundle, ModAssetCatalog catalog) { for (int i = 0; i < ModAssetCatalog.DyePrefabNames.Length; i++) { string text = ModAssetCatalog.DyePrefabNames[i]; string text2 = "Assets/Custom/BalrondBannerColorizer/prefabs/" + text + ".prefab"; GameObject val = bundle.LoadAsset<GameObject>(text2); if ((Object)(object)val == (Object)null) { ModLog.Warning("BalrondBannerColorizer: Missing prefab " + text2); continue; } ConfigureDyeItem(val, text); catalog.AddItem(val); } } private static void ConfigureDyeItem(GameObject prefab, string prefabName) {