/// <summary> /// Adds the techs for every registered building to the database. /// </summary> internal static void AddAllTechs() { if (buildingTable == null) { throw new InvalidOperationException("Building table not loaded"); } lock (PSharedData.GetLock(PRegistry.KEY_BUILDING_LOCK)) { PRegistry.LogPatchDebug("Register techs for {0:D} buildings".F( buildingTable.Count)); foreach (var building in buildingTable) { if (building != null) { try { var trBuilding = Traverse.Create(building); // Building is of type object because it is in another assembly var addTech = Traverse.Create(building).Method(nameof(AddTech)); if (addTech.MethodExists()) { addTech.GetValue(); } else { PRegistry.LogPatchWarning("Invalid building technology!"); } } catch (System.Reflection.TargetInvocationException e) { // Log errors when registering building from another mod PUtil.LogError("Unable to add building tech for " + building.GetType().Assembly?.GetNameSafe() + ":"); PUtil.LogException(e.GetBaseException()); } } } } }
/// <summary> /// Initializes the Imagination Loader. PLib will be initialized if not already done. /// </summary> /// <param name="rootType">The Type that will be used as the base for this Imagination /// mod's assembly. The namespace for that Type will be registered as the name for /// other Imagination mods to see.</param> public static void Init(Type rootType) { if (rootType == null) { throw new ArgumentNullException("rootType"); } var asm = Assembly.GetExecutingAssembly(); PUtil.InitLibrary(); // Create imagination mod table var imag = PSharedData.GetData <IDictionary <string, Assembly> >(IMAGINATION_TABLE); if (imag == null) { PSharedData.PutData(IMAGINATION_TABLE, imag = new Dictionary <string, Assembly>(8)); } var rootNS = rootType.Namespace; if (imag.ContainsKey(rootNS)) { PUtil.LogWarning("Reimagination mod {0} is loaded more than once. This may " + "cause severe problems!".F(rootType.FullName)); } else { imag.Add(rootNS, asm); PUtil.LogDebug("Imagination Loader registered mod ID: " + rootNS); } }
/// <summary> /// Registers the specified assembly for automatic PLib localization. If null is /// passed, the calling assembly is registered. /// </summary> /// <param name="assembly">The assembly to register for PLib localization.</param> public static void Register(Assembly assembly = null) { if (assembly == null) { assembly = Assembly.GetCallingAssembly(); } lock (PSharedData.GetLock(PRegistry.KEY_LOCALE_LOCK)) { // Get list holding locale information var list = PSharedData.GetData <IList <Assembly> >(PRegistry.KEY_LOCALE_TABLE); if (list == null) { PSharedData.PutData(PRegistry.KEY_LOCALE_TABLE, list = new List <Assembly>(8)); } list.Add(assembly); } var types = assembly.GetTypes(); if (types == null || types.Length == 0) { PUtil.LogWarning("Registered assembly " + assembly.GetName()?.Name + " that had no types for localization!"); } else { // This call searches all types in the assembly implicitly Localization.RegisterForTranslation(types[0]); } }
/// <summary> /// Initializes and stores the options table for quicker lookups later. /// </summary> internal static void Init() { lock (PSharedData.GetLock(PRegistry.KEY_OPTIONS_LOCK)) { modOptions = PSharedData.GetData <OptionsTable>(PRegistry.KEY_OPTIONS_TABLE); PSharedData.PutData(PRegistry.KEY_OPTIONS_LATEST, typeof(POptions)); } }
/// <summary> /// Registers a building to properly display its name, description, and tech tree /// entry. PLib must be initialized using InitLibrary before using this method. Each /// building should only be registered once, either in OnLoad or a post-load patch. /// </summary> /// <param name="building">The building to register.</param> public static void Register(PBuilding building) { if (building == null) { throw new ArgumentNullException("building"); } // In case this call is used before the library was initialized if (!PUtil.PLibInit) { PUtil.InitLibrary(false); PUtil.LogWarning("PUtil.InitLibrary was not called before using " + "PBuilding.Register!"); } // Must use object as the building table type lock (PSharedData.GetLock(PRegistry.KEY_BUILDING_LOCK)) { var table = PSharedData.GetData <ICollection <object> >(PRegistry. KEY_BUILDING_TABLE); if (table == null) { PSharedData.PutData(PRegistry.KEY_BUILDING_TABLE, table = new List <object>(64)); } #if DEBUG PUtil.LogDebug("Registered building: {0}".F(building.ID)); #endif table.Add(building); } }
/// <summary> /// Registers a class as a mod options class. /// </summary> /// <param name="optionsType">The class which will represent the options for this mod.</param> public static void RegisterOptions(Type optionsType) { if (optionsType == null) { throw new ArgumentNullException("optionsType"); } var assembly = optionsType.Assembly; var id = Path.GetFileName(GetModDir(assembly)); // Prevent concurrent modification (should be impossible anyways) lock (PSharedData.GetLock(PRegistry.KEY_OPTIONS_LOCK)) { // Get options table var options = PSharedData.GetData <OptionsTable>(PRegistry.KEY_OPTIONS_TABLE); if (options == null) { PSharedData.PutData(PRegistry.KEY_OPTIONS_TABLE, options = new Dictionary <string, Type>(8)); } if (options.ContainsKey(id)) { PUtil.LogWarning("Duplicate mod ID: " + id); } else { // Add as options for this mod options.Add(id, optionsType); PUtil.LogDebug("Registered mod options class {0} for {1}".F( optionsType.Name, assembly.GetName()?.Name)); } } }
/// <summary> /// Adds the save/restore lists buttons to the bottom of the Mods screen. /// </summary> /// <param name="instance">The object hosting the mods screen.</param> /// <param name="bottom">The panel where the buttons should be added.</param> internal static void AddExtraButtons(GameObject instance, GameObject bottom) { var handler = instance.AddOrGet <AllModsHandler>(); var cb = new PCheckBox("AllMods") { CheckSize = new Vector2(24.0f, 24.0f), Text = DebugNotIncludedStrings. BUTTON_ALL, ToolTip = DebugNotIncludedStrings.TOOLTIP_ALL, Margin = new RectOffset(5, 5, 0, 0) }; // When clicked, enable/disable all if (handler != null) { cb.OnChecked = handler.OnClick; } handler.checkbox = cb.AddTo(bottom, 0); handler.UpdateCheckedState(); // Current PLib version string version = PSharedData.GetData <string>("PLib.Version"); string name = ModDebugRegistry.Instance.OwnerOfAssembly(DebugNotIncludedPatches. RunningPLibAssembly)?.ModName ?? "Unknown"; new PLabel("PLibVersion") { TextStyle = PUITuning.Fonts.UILightStyle, Text = string.Format( DebugNotIncludedStrings.LABEL_PLIB, version ?? PVersion.VERSION), ToolTip = string.Format(DebugNotIncludedStrings.TOOLTIP_PLIB, name), Margin = new RectOffset(5, 5, 0, 0) }.AddTo(bottom, 0); }
/// <summary> /// Adds the techs for every registered building to the database. /// </summary> internal static void AddAllTechs() { if (buildingTable == null) { throw new InvalidOperationException("Building table not loaded"); } lock (PSharedData.GetLock(PRegistry.KEY_BUILDING_LOCK)) { PRegistry.LogPatchDebug("Register techs for {0:D} buildings".F( buildingTable.Count)); foreach (var building in buildingTable) { if (building != null) { var trBuilding = Traverse.Create(building); // Building is of type object because it is in another assembly var addTech = Traverse.Create(building).Method(nameof(AddTech)); if (addTech.MethodExists()) { addTech.GetValue(); } else { PRegistry.LogPatchWarning("Invalid building technology!"); } } } } }
/// <summary> /// Registers a class as a mod options class. The type is registered for its defining /// assembly, not for the calling assembly, for compatibility reasons. /// </summary> /// <param name="optionsType">The class which will represent the options for this mod.</param> public static void RegisterOptions(Type optionsType) { if (optionsType == null) { throw new ArgumentNullException("optionsType"); } #if OPTIONS_ONLY var assembly = optionsType.Assembly; var id = Path.GetFileName(GetModDir(assembly)); // Local options type if (modOptions == null) { modOptions = new Dictionary <string, Type>(4); } if (modOptions.ContainsKey(id)) { PUtil.LogWarning("Duplicate mod ID: " + id); } else { // Add as options for this mod modOptions.Add(id, optionsType); PUtil.LogDebug("Registered mod options class {0} for {1}".F(optionsType.Name, assembly.GetName()?.Name)); } #else // In case this call is used before the library was initialized if (!PUtil.PLibInit) { PUtil.InitLibrary(false); PUtil.LogWarning("PUtil.InitLibrary was not called before using " + "RegisterOptions!"); } var assembly = optionsType.Assembly; var id = Path.GetFileName(GetModDir(assembly)); // Prevent concurrent modification (should be impossible anyways) lock (PSharedData.GetLock(PRegistry.KEY_OPTIONS_LOCK)) { // Get options table var options = PSharedData.GetData <OptionsTable>(PRegistry.KEY_OPTIONS_TABLE); if (options == null) { PSharedData.PutData(PRegistry.KEY_OPTIONS_TABLE, options = new Dictionary <string, Type>(8)); } if (options.ContainsKey(id)) { PUtil.LogWarning("Duplicate mod ID: " + id); } else { // Add as options for this mod options.Add(id, optionsType); PUtil.LogDebug("Registered mod options class {0} for {1}".F(optionsType. Name, assembly.GetName()?.Name)); } } #endif }
/// <summary> /// Gets the lock object for the achievement list. /// </summary> /// <returns>An object used to synchronize mods accessing the achievement list.</returns> private static object GetAchievementLock() { var obj = PSharedData.GetData <object>(ACHIEVEMENTS_API_LOCK); if (obj == null) { PSharedData.PutData(ACHIEVEMENTS_API_LOCK, obj = new object()); } return(obj); }
/// <summary> /// Registers a light shape handler. /// </summary> /// <param name="identifier">A unique identifier for this shape. If another mod has /// already registered that identifier, the previous mod will take precedence.</param> /// <param name="handler">The handler for that shape.</param> /// <returns>The light shape which can be used.</returns> public static PLightShape Register(string identifier, CastLight handler) { PLightShape lightShape; // In case this call is used before the library was initialized if (!PUtil.PLibInit) { PUtil.InitLibrary(false); PUtil.LogWarning("PUtil.InitLibrary was not called before using " + "PLightShape.Register!"); } lock (PSharedData.GetLock(PRegistry.KEY_LIGHTING_LOCK)) { // Get list holding lighting information var list = PSharedData.GetData <IList <object> >(PRegistry.KEY_LIGHTING_TABLE); if (list == null) { PSharedData.PutData(PRegistry.KEY_LIGHTING_TABLE, list = new List <object>(8)); } // Try to find a match for this identifier object ls = null; int n = list.Count, index = 0; for (int i = 0; i < n; i++) { var light = list[i]; // Might be from another assembly so the types may or may not be compatible if (light != null && light.ToString() == identifier && light.GetType(). Name == typeof(PLightShape).Name) { index = i; break; } } if (ls == null) { // Not currently existing lightShape = new PLightShape(n + 1, identifier, handler); PUtil.LogDebug("Registered new light shape: " + identifier); list.Add(lightShape); } else { // Exists already PUtil.LogDebug("Found existing light shape: " + identifier); lightShape = new PLightShape(n + 1, identifier, null); } } return(lightShape); }
/// <summary> /// Checks for globally registered buildings and puts them into this assembly's /// building cache if present. /// </summary> /// <returns>true if buildings must be patched in, or false otherwise</returns> internal static bool CheckBuildings() { bool any = false; lock (PSharedData.GetLock(PRegistry.KEY_BUILDING_LOCK)) { var table = PSharedData.GetData <ICollection <object> >(PRegistry. KEY_BUILDING_TABLE); if (table != null && table.Count > 0) { buildingTable = table; any = true; } } return(any); }
/// <summary> /// Gets the information for the specified achievement. The achievement lock must be /// held for this method to work properly. /// </summary> /// <param name="id">The achievement ID to look up.</param> /// <returns>The achievement information.</returns> private static Traverse GetAchievement(string id) { var data = PSharedData.GetData <AchievementDict>(ACHIEVEMENTS_API_INFO); if (data == null) { PSharedData.PutData(ACHIEVEMENTS_API_INFO, data = new Dictionary <string, object>(32)); } if (!data.TryGetValue(id, out object info)) { data.Add(id, info = new AchievementInfo(id)); } return(Traverse.Create(info)); }
/// <summary> /// Debug dumps the translation templates for ALL registered PLib localized mods. /// </summary> internal static void DumpAll() { lock (PSharedData.GetLock(PRegistry.KEY_LOCALE_LOCK)) { // Get list holding locale information var list = PSharedData.GetData <IList <Assembly> >(PRegistry.KEY_LOCALE_TABLE); if (list != null) { foreach (var mod in list) { if (mod != null) { ModUtil.RegisterForTranslation(mod.GetTypes()[0]); } } } } }
private static void RegisterEntry(Assembly modAssembly, string lockKey, string tableKey, string entryPath, string debugLine) { // Store the path to the creatures folder on disk for use in loading codex entries string dir = Options.POptions.GetModDir(modAssembly); lock (PSharedData.GetLock(lockKey)) { var table = PSharedData.GetData <IList <string> >(tableKey); if (table == null) { PSharedData.PutData(tableKey, table = new List <string>(8)); } #if DEBUG PUtil.LogDebug(debugLine.F(dir)); #endif table.Add(Path.Combine(dir, entryPath)); } }
/// <summary> /// Loads all codex entries for all mods registered. /// </summary> /// <param name="lockKey">Key for shared data lock.</param> /// <param name="tableKey">Key for shared data table.</param> /// <param name="category">The codex category under which these data entries should be loaded.</param> /// <returns>The list of entries that were loaded.</returns> private static IList <CodexEntry> LoadEntries(string lockKey, string tableKey, string category) { var entries = new List <CodexEntry>(32); lock (PSharedData.GetLock(lockKey)) { var table = PSharedData.GetData <IList <string> >(tableKey); if (table != null) { foreach (string dir in table) { #if DEBUG PUtil.LogDebug("Loaded codex entries from directory: {0}".F(dir)); #endif LoadFromDirectory(entries, dir, category); } } } return(entries); }
/// <summary> /// Loads all codex entries for all mods registered. /// </summary> /// <param name="lockKey">Key for shared data lock.</param> /// <param name="tableKey">Key for shared data table.</param> /// <param name="category">The codex category under which these data entries should be loaded.</param> /// <returns>The list of entries that were loaded.</returns> private static IList <CodexEntry> LoadEntries(string lockKey, string tableKey, string category) { var entries = new List <CodexEntry>(32); lock (PSharedData.GetLock(lockKey)) { var table = PSharedData.GetData <IList <string> >(tableKey); if (table != null) { foreach (string dir in table) { #if DEBUG PUtil.LogDebug("Loaded codex entries from directory: {0}".F(dir)); #endif string[] codexFiles = new string[0]; try { // List codex data files in the codex directory codexFiles = Directory.GetFiles(dir, CODEX_FILES); } catch (UnauthorizedAccessException ex) { PUtil.LogExcWarn(ex); } catch (IOException ex) { PUtil.LogExcWarn(ex); } var widgetTagMappings = Traverse.Create(typeof(CodexCache)). GetField <List <Tuple <string, Type> > >("widgetTagMappings"); foreach (string str in codexFiles) { try { string filename = str; var codexEntry = YamlIO.LoadFile <CodexEntry>(filename, PUtil. YamlParseErrorCB, widgetTagMappings); if (codexEntry != null) { codexEntry.category = category; entries.Add(codexEntry); } } catch (IOException ex) { PUtil.LogException(ex); } }
/// <summary> /// Creates and initializes the lighting manager instance. /// </summary> /// <returns>true if the lighting manager was initialized and has something to do, /// or false otherwise.</returns> internal static bool InitInstance() { bool patch = false; lock (PSharedData.GetLock(PRegistry.KEY_LIGHTING_LOCK)) { // Only run if any lights were registered var list = PSharedData.GetData <IList <object> >(PRegistry.KEY_LIGHTING_TABLE); if (list != null) { Instance = new PLightManager(); Instance.Init(list); patch = true; } } // Initialize anyways if smooth lighting is forced on if (!patch && ForceSmoothLight) { Instance = new PLightManager(); patch = true; } return(patch); }
/// <summary> /// Registers the specified assembly for automatic PLib localization. If null is /// passed, the calling assembly is registered. /// </summary> /// <param name="assembly">The assembly to register for PLib localization.</param> public static void Register(Assembly assembly = null) { if (!PUtil.PLibInit) { PUtil.InitLibrary(false); PUtil.LogWarning("PUtil.InitLibrary was not called before using " + "PLocalization.Register!"); } if (assembly == null) { assembly = Assembly.GetCallingAssembly(); } lock (PSharedData.GetLock(PRegistry.KEY_LOCALE_LOCK)) { // Get list holding locale information var list = PSharedData.GetData <IList <Assembly> >(PRegistry.KEY_LOCALE_TABLE); if (list == null) { PSharedData.PutData(PRegistry.KEY_LOCALE_TABLE, list = new List <Assembly>(8)); } list.Add(assembly); } var types = assembly.GetTypes(); if (types == null || types.Length == 0) { PUtil.LogWarning("Registered assembly " + assembly.GetNameSafe() + " that had no types for localization!"); } else { // This call searches all types in the assembly implicitly Localization.RegisterForTranslation(types[0]); #if DEBUG PUtil.LogDebug("Localizing assembly {0} using base namespace {1}".F(assembly. GetNameSafe(), types[0].Namespace)); #endif } }
/// <summary> /// Shows a mod options dialog now, as if Options was used inside the Mods menu. /// </summary> /// <param name="optionsType">The type of the options to show. The mod to configure, /// configuration directory, and so forth will be retrieved from the provided type. /// This type must be the same type configured in RegisterOptions for the mod.</param> /// <param name="title">The title to show in the dialog. If null, a default title /// will be used.</param> /// <param name="onClose">The method to call when the dialog is closed.</param> public static void ShowNow(Type optionsType, string title = null, Action <object> onClose = null) { #if OPTIONS_ONLY ShowDialog(optionsType, title, onClose); #else Type forwardType; if (optionsType == null) { throw new ArgumentNullException("optionsType"); } // Find latest version if possible lock (PSharedData.GetLock(PRegistry.KEY_OPTIONS_LOCK)) { forwardType = PSharedData.GetData <Type>(PRegistry.KEY_OPTIONS_LATEST); } if (forwardType == null) { forwardType = typeof(POptions); } try { var method = forwardType.GetMethod(nameof(ShowDialog), BindingFlags.Static | BindingFlags.NonPublic, null, new Type[] { typeof(Type), typeof(string), typeof(Action <object>) }, null); // Forward call to that version if (method != null) { method.Invoke(null, new object[] { optionsType, title, onClose }); } else { PUtil.LogWarning("No call to show options dialog found!"); ShowDialog(optionsType, title, onClose); } } catch (AmbiguousMatchException e) { PUtil.LogException(e); } #endif }
/// <summary> /// Localizes all mods which registered for it. /// </summary> /// <param name="locale">The locale to use.</param> internal static void LocalizeAll(Localization.Locale locale) { if (locale == null) { throw new ArgumentNullException("locale"); } lock (PSharedData.GetLock(PRegistry.KEY_LOCALE_LOCK)) { // Get list holding locale information var list = PSharedData.GetData <IList <Assembly> >(PRegistry.KEY_LOCALE_TABLE); if (list != null) { PUtil.LogDebug("Localizing {0:D} mods to locale {1}".F(list.Count, locale.Code)); foreach (var mod in list) { if (mod != null) { Localize(mod, POptions.GetModDir(mod), locale); } } } } }
/// <summary> /// Reports whether another Reimagination mod with the specified mod ID is loaded. /// Only usable in postload patches or later. /// </summary> /// <param name="modID">The mod ID to check, typically the namespace of its root /// type.</param> /// <returns>true if that mod has been loaded and called Init(), or false otherwise.</returns> public static bool IsModLoaded(string modID) { var imag = PSharedData.GetData <IDictionary <string, Assembly> >(IMAGINATION_TABLE); return(imag != null && imag.ContainsKey(modID)); }