/// <summary>
        /// Returns the filepath of the mod assembly.
        /// </summary>
        /// <returns>Mod assembly filepath</returns>
        private string GetAssemblyPath()
        {
            // Get list of currently active plugins.
            IEnumerable <PluginManager.PluginInfo> plugins = PluginManager.instance.GetPluginsInfo();

            // Iterate through list.
            foreach (PluginManager.PluginInfo plugin in plugins)
            {
                try
                {
                    // Get all (if any) mod instances from this plugin.
                    IUserMod[] mods = plugin.GetInstances <IUserMod>();

                    // Check to see if the primary instance is this mod.
                    if (mods.FirstOrDefault() is PloppableRICOMod)
                    {
                        // Found it! Return path.
                        return(plugin.modPath);
                    }
                }
                catch
                {
                    // Don't care.
                }
            }

            // If we got here, then we didn't find the assembly.
            Debugging.Message("assembly path not found");
            throw new FileNotFoundException(PloppableRICOMod.ModName + ": assembly path not found!");
        }
示例#2
0
        /// <summary>
        /// Attaches an event hook to options panel visibility, to create/destroy our options panel as appropriate.
        /// Destroying when not visible saves UI overhead and performance impacts, especially with so many UITextFields.
        /// </summary>
        public static void OptionsEventHook()
        {
            // Get options panel instance.
            gameOptionsPanel = UIView.library.Get <UIPanel>("OptionsPanel");

            if (gameOptionsPanel == null)
            {
                Debugging.Message("couldn't find OptionsPanel");
            }
            else
            {
                // Simple event hook to create/destroy GameObject based on appropriate visibility.
                gameOptionsPanel.eventVisibilityChanged += (control, isVisible) =>
                {
                    // Create/destroy based on visible.
                    if (isVisible)
                    {
                        Create();
                    }
                    else
                    {
                        Close();
                    }
                };

                // Recreate panel on system locale change.
                LocaleManager.eventLocaleChanged += LocaleChanged;
            }
        }
        /// <summary>
        /// Saves the current RICO settings to file.
        /// </summary>
        private void Save()
        {
            // Read current settings from UI elements and convert to XML.
            SettingsPanel.Panel.Save();

            // If the local settings file doesn't already exist, create a new blank template.
            if (!File.Exists("LocalRICOSettings.xml"))
            {
                var newLocalSettings = new PloppableRICODefinition();
                var xmlSerializer    = new XmlSerializer(typeof(PloppableRICODefinition));

                // Create blank file template.
                using (XmlWriter writer = XmlWriter.Create("LocalRICOSettings.xml"))
                {
                    xmlSerializer.Serialize(writer, newLocalSettings);
                }
            }

            // Check that file exists before continuing (it really should at this point, but just in case).
            if (File.Exists("LocalRICOSettings.xml"))
            {
                PloppableRICODefinition oldLocalSettings;
                PloppableRICODefinition newLocalSettings = new PloppableRICODefinition();
                XmlSerializer           xmlSerializer    = new XmlSerializer(typeof(PloppableRICODefinition));

                // Read existing file.
                using (StreamReader streamReader = new StreamReader("LocalRICOSettings.xml"))
                {
                    oldLocalSettings = xmlSerializer.Deserialize(streamReader) as PloppableRICODefinition;
                }

                // Loop though all buildings in the existing file. If they aren't the current selection, write them back to the replacement file.
                foreach (var buildingDef in oldLocalSettings.Buildings)
                {
                    if (buildingDef.name != currentSelection.name)
                    {
                        newLocalSettings.Buildings.Add(buildingDef);
                    }
                }

                // If current selection has local settings, add them to the replacement file.
                if (currentSelection.hasLocal)
                {
                    newLocalSettings.Buildings.Add(currentSelection.local);
                }

                // Write replacement file to disk.
                using (TextWriter writer = new StreamWriter("LocalRICOSettings.xml"))
                {
                    xmlSerializer.Serialize(writer, newLocalSettings);
                }
            }
            else
            {
                Debugging.Message("couldn't find local settings file to save");
            }

            // Force an update of all panels with current values.
            SettingsPanel.Panel.UpdateSelectedBuilding(currentSelection);
        }
 /// <summary>
 /// Load settings from XML file.
 /// </summary>
 internal static void LoadSettings()
 {
     try
     {
         // Check to see if configuration file exists.
         if (File.Exists(SettingsFileName))
         {
             // Read it.
             using (StreamReader reader = new StreamReader(SettingsFileName))
             {
                 XmlSerializer xmlSerializer = new XmlSerializer(typeof(XMLSettingsFile));
                 if (!(xmlSerializer.Deserialize(reader) is XMLSettingsFile xmlSettingsFile))
                 {
                     Debugging.Message("couldn't deserialize settings file");
                 }
             }
         }
         else
         {
             Debugging.Message("no settings file found");
         }
     }
     catch (Exception e)
     {
         Debugging.Message("exception reading XML settings file");
         Debugging.LogException(e);
     }
 }
示例#5
0
        /// <summary>
        /// Checks to see if another mod is installed, based on a provided assembly name.
        /// </summary>
        /// <param name="assemblyName">Name of the mod assembly</param>
        /// <param name="enabledOnly">True if the mod needs to be enabled for the purposes of this check; false if it doesn't matter</param>
        /// <returns>True if the mod is installed (and, if enabledOnly is true, is also enabled), false otherwise</returns>
        internal static bool IsModInstalled(string assemblyName, bool enabledOnly = false)
        {
            // Convert assembly name to lower case.
            string assemblyNameLower = assemblyName.ToLower();

            // Iterate through the full list of plugins.
            foreach (PluginManager.PluginInfo plugin in PluginManager.instance.GetPluginsInfo())
            {
                foreach (Assembly assembly in plugin.GetAssemblies())
                {
                    if (assembly.GetName().Name.ToLower().Equals(assemblyNameLower))
                    {
                        Debugging.Message("found mod assembly " + assemblyName);
                        if (enabledOnly)
                        {
                            return(plugin.isEnabled);
                        }
                        else
                        {
                            return(true);
                        }
                    }
                }
            }

            // If we've made it here, then we haven't found a matching assembly.
            return(false);
        }
        /// <summary>
        /// Saves the current RICO settings to file and then applies them live in-game.
        /// </summary>
        private void SaveAndApply()
        {
            // Find current prefab instance.
            BuildingData currentBuildingData = Loading.xmlManager.prefabHash[currentSelection.prefab];

            // Save first.
            Save();

            // If we're converting a residential building to something else, then we first should clear out all households.
            if (currentBuildingData.prefab.GetService() == ItemClass.Service.Residential && !IsCurrentResidential())
            {
                // removeAll argument to true to remove all households.
                UpdateHouseholds(currentBuildingData.prefab.name, removeAll: true);
            }

            // Get the currently applied RICO settings (local, author, mod).
            RICOBuilding currentData = RICOUtils.CurrentRICOSetting(currentSelection);

            if (currentData != null)
            {
                // Convert the 'live' prefab (instance in PrefabCollection) and update household count and builidng level for all current instances.
                Loading.convertPrefabs.ConvertPrefab(currentData, PrefabCollection <BuildingInfo> .FindLoaded(currentBuildingData.prefab.name));
                UpdateHouseholds(currentBuildingData.prefab.name, currentData.level);
            }
            else
            {
                Debugging.Message("no current RICO settings to apply to prefab " + currentBuildingData);
            }

            // Force an update of all panels with current values.
            SettingsPanel.Panel.UpdateSelectedBuilding(currentSelection);
        }
        /// <summary>
        /// Called by the game when level loading is complete.
        /// </summary>
        /// <param name="mode">Loading mode (e.g. game, editor, scenario, etc.)</param>
        public override void OnLevelLoaded(LoadMode mode)
        {
            // Alert the user to any mod conflicts.
            ModUtils.NotifyConflict();

            // Don't do anything further if mod hasn't activated (conflicting mod detected, or loading into editor instead of game).
            if (!isModEnabled)
            {
                return;
            }

            base.OnLevelLoaded(mode);

            // Don't do anything if in asset editor.
            if (mode == LoadMode.NewAsset || mode == LoadMode.LoadAsset)
            {
                return;
            }

            // Wait for loading to fully complete.
            while (!LoadingManager.instance.m_loadingComplete)
            {
            }

            // Check watchdog flag.
            if (!patchOperating)
            {
                // Patch wasn't operating; display warning notification and exit.
                HarmonyNotification notification = new HarmonyNotification();
                notification.Create();
                notification.Show();

                return;
            }

            // Init Ploppable Tool panel.
            PloppableTool.Initialize();

            // Add buttons to access building details from zoned building info panels.
            SettingsPanel.AddInfoPanelButtons();

            // Deactivate the ploppable panel as it starts hidden.  Don't need to deactivate the settings panel as it's not instantiated until first shown.
            PloppableTool.Instance.gameObject.SetActive(false);

            // Report any loading errors.
            Debugging.ReportErrors();

            Debugging.Message("loading complete");

            // Load settings file and check if we need to display update notification.
            settingsFile = Configuration <SettingsFile> .Load();

            if (settingsFile.NotificationVersion != 2)
            {
                // No update notification "Don't show again" flag found; show the notification.
                UpdateNotification notification = new UpdateNotification();
                notification.Create();
                notification.Show();
            }
        }
示例#8
0
        /// <summary>
        /// Returns the translation for the given key in the current language.
        /// </summary>
        /// <param name="key">Translation key to transate</param>
        /// <returns>Translation </returns>
        public string Translate(string key)
        {
            // Check that a valid current language is set.
            if (currentLanguage != null)
            {
                // Check that the current key is included in the translation.
                if (currentLanguage.translationDictionary.ContainsKey(key))
                {
                    // All good!  Return translation.
                    return(currentLanguage.translationDictionary[key]);
                }
                else
                {
                    Debugging.Message("no translation for language " + currentLanguage.uniqueName + " found for key " + key);

                    // Attempt fallack language; if even that fails, just return the key.
                    return(FallbackLanguage().translationDictionary.ContainsKey(key) ? FallbackLanguage().translationDictionary[key] ?? key : key);
                }
            }
            else
            {
                Debugging.Message("no current language set when translating key " + key);
            }

            // If we've made it this far, something went wrong; just return the key.
            return(key);
        }
        /// <summary>
        /// Postfix to attempt to catch issues where homecounts seem to be reset to zero for specific buildings.
        /// </summary>
        /// <param name="__instance">Original object instance reference</param>
        /// <param name="buildingID">Building instance ID</param>
        /// <param name="data">Building data</param>
        /// <param name="version">Version</param>
        private static void Postfix(PrivateBuildingAI __instance, ushort buildingID, ref Building data, uint version)
        {
            // Check to see if this is one of ours.
            if (__instance is GrowableResidentialAI)
            {
                // Check to see if no citizen units are set.
                if (data.m_citizenUnits == 0)
                {
                    // Uh oh...
                    Debugging.Message("no citizenUnits for building " + buildingID + " : " + data.Info.name + "; attempting reset");

                    // Backup resetOnLoad setting and force to true for reset attempt.
                    bool oldReset = ModSettings.resetOnLoad;
                    ModSettings.resetOnLoad = true;

                    // Attempt reset for this building.
                    Prefix(__instance, buildingID, ref data, version);

                    // Restore original resetOnLoad seeting.
                    ModSettings.resetOnLoad = oldReset;
                }
                else
                {
                    Debugging.Message("building " + buildingID + " : " + data.Info.name + " passed CitizenUnits check");
                }
            }
        }
示例#10
0
        /// <summary>
        /// Adds a Ploppable RICO button to a building info panel to directly access that building's RICO settings.
        /// The button will be added to the right of the panel with a small margin from the panel edge, at the relative Y position specified.
        /// </summary>
        /// <param name="infoPanel">Infopanel to apply the button to</param>
        /// <param name="relativeY">The relative Y position of the button within the panel</param>
        private static void AddInfoPanelButton(BuildingWorldInfoPanel infoPanel)
        {
            UIButton panelButton = infoPanel.component.AddUIComponent <UIButton>();

            // Basic button setup.
            panelButton.size             = new Vector2(34, 34);
            panelButton.normalBgSprite   = "ToolbarIconGroup6Normal";
            panelButton.normalFgSprite   = "IconPolicyBigBusiness";
            panelButton.focusedBgSprite  = "ToolbarIconGroup6Focused";
            panelButton.hoveredBgSprite  = "ToolbarIconGroup6Hovered";
            panelButton.pressedBgSprite  = "ToolbarIconGroup6Pressed";
            panelButton.disabledBgSprite = "ToolbarIconGroup6Disabled";
            panelButton.name             = "PloppableButton";
            panelButton.tooltip          = Translations.Translate("PRR_SET_RICO");

            // Find ProblemsPanel relative position to position button.
            // We'll use 40f as a default relative Y in case something doesn't work.
            UIComponent problemsPanel;
            float       relativeY = 40f;

            // Player info panels have wrappers, zoned ones don't.
            UIComponent wrapper = infoPanel.Find("Wrapper");

            if (wrapper == null)
            {
                problemsPanel = infoPanel.Find("ProblemsPanel");
            }
            else
            {
                problemsPanel = wrapper.Find("ProblemsPanel");
            }

            try
            {
                // Position button vertically in the middle of the problems panel.  If wrapper panel exists, we need to add its offset as well.
                relativeY = (wrapper == null ? 0 : wrapper.relativePosition.y) + problemsPanel.relativePosition.y + ((problemsPanel.height - 34) / 2);
            }
            catch
            {
                // Don't really care; just use default relative Y.
                Debugging.Message("couldn't find ProblemsPanel relative position");
            }

            // Set position.
            panelButton.AlignTo(infoPanel.component, UIAlignAnchor.TopRight);
            panelButton.relativePosition += new Vector3(-5f, relativeY, 0f);

            // Event handler.
            panelButton.eventClick += (control, clickEvent) =>
            {
                // Select current building in the building details panel and show.
                Open(InstanceManager.GetPrefabInfo(WorldInfoPanel.GetCurrentInstanceID()) as BuildingInfo);

                // Manually unfocus control, otherwise it can stay focused until next UI event (looks untidy).
                control.Unfocus();
            };
        }
示例#11
0
        /// <summary>
        /// Checks for known mod conflicts and function extenders.
        /// </summary>
        /// <returns>Whether or not Ploppable RICO should load</returns>
        internal static bool CheckMods()
        {
            // Check for conflicting mods.
            if (IsModEnabled(586012417ul))
            {
                // Original Ploppable RICO mod detected.
                conflictingMod = true;
                Debugging.Message("Original Ploppable RICO detected - RICO Revisited exiting");
                conflictMessage = Translations.Translate("PRR_CON_OPR") + " - " + Translations.Translate("PRR_CON_DWN") + "\r\n\r\n" + Translations.Translate("PRR_CON_ONE");
                return(false);
            }
            else if (IsModInstalled("EnhancedBuildingCapacity"))
            {
                // Enhanced Building Capacity mod detected.
                conflictingMod = true;
                Debugging.Message("Enhanced Building Capacity mod detected - RICO Revisited exiting");
                conflictMessage = Translations.Translate("PRR_CON_EBC") + " - " + Translations.Translate("PRR_CON_DWN") + "\r\n\r\n" + Translations.Translate("PRR_CON_ONE");
                return(false);
            }
            else if (IsModInstalled("VanillaGarbageBinBlocker"))
            {
                // Garbage bin controller mod detected.
                conflictingMod = true;
                Debugging.Message("Garbage Bin Controller mod detected - RICO Revisited exiting");
                conflictMessage = Translations.Translate("PRR_CON_GBC") + " - " + Translations.Translate("PRR_CON_DWN") + "\r\n\r\n" + Translations.Translate("PRR_CON_ONE");
                return(false);
            }
            else if (IsModInstalled(1372431101ul))
            {
                // Painter mod detected.
                conflictingMod = true;
                Debugging.Message("Painter detected - RICO Revisited exiting");
                conflictMessage = Translations.Translate("PRR_CON_PTR") + " - " + Translations.Translate("PRR_CON_DWN") + "\r\n\r\n" + Translations.Translate("PRR_CON_PTR1");
                return(false);
            }

            // No conflicts - now check for realistic population mods.
            realPopEnabled = (IsModInstalled("RealPopRevisited", true) || IsModInstalled("WG_BalancedPopMod", true));

            // Check for Workshop RICO settings mod.
            if (IsModEnabled(629850626uL))
            {
                Debugging.Message("found Workshop RICO settings mod");
                Loading.mod1RicoDef = RICOReader.ParseRICODefinition("", Path.Combine(Util.SettingsModPath("629850626"), "WorkshopRICOSettings.xml"), false);
            }

            // Check for Ryuichi Kaminogi's "RICO Settings for Modern Japan CCP"
            Package modernJapanRICO = PackageManager.GetPackage("2035770233");

            if (modernJapanRICO != null)
            {
                Debugging.Message("found RICO Settings for Modern Japan CCP");
                Loading.mod2RicoDef = RICOReader.ParseRICODefinition("", Path.Combine(Path.GetDirectoryName(modernJapanRICO.packagePath), "PloppableRICODefinition.xml"), false);
            }

            return(true);
        }
        /// <summary>
        /// Actions to update the UI on a language change go here.
        /// </summary>
        public void UpdateUILanguage()
        {
            Debugging.Message("setting language to " + (currentIndex < 0 ? "system" : languages.Values[currentIndex].uniqueName));

            // UI update code goes here.

            // TOOO:  Add dynamic UI update.
            // Update ploppable tool, if it's been created.
            PloppableTool.Instance?.SetText();
        }
        /// <summary>
        /// Updates household counts for all buildings in scene with the given prefab name.
        /// Can also remove all housholds, setting the total to zero.
        /// </summary>
        /// <param name="prefabName">Prefab name</param>
        /// <param name="removeAll">If true, all households will be removed (count set to 0)</param>
        private void UpdateHouseholds(string prefabName, int level = 0, bool removeAll = false)
        {
            int homeCount        = 0;
            int visitCount       = 0;
            int homeCountChanged = 0;

            // Get building manager instance.
            var instance = Singleton <BuildingManager> .instance;


            // Iterate through each building in the scene.
            for (ushort i = 0; i < instance.m_buildings.m_buffer.Length; i++)
            {
                // Check for matching name.
                if (instance.m_buildings.m_buffer[i].Info != null && instance.m_buildings.m_buffer[i].Info.name != null && instance.m_buildings.m_buffer[i].Info.name.Equals(prefabName))
                {
                    // Got a match!  Check level if applicable.
                    if (level > 0)
                    {
                        // m_level is one less than building.level.
                        byte newLevel = (byte)(level - 1);

                        if (instance.m_buildings.m_buffer[i].m_level != newLevel)
                        {
                            Debugging.Message("found building '" + prefabName + "' with level " + (instance.m_buildings.m_buffer[i].m_level + 1) + ", overriding to level " + level);
                            instance.m_buildings.m_buffer[i].m_level = newLevel;
                        }
                    }

                    // Update homecounts for any residential buildings.
                    PrivateBuildingAI thisAI = instance.m_buildings.m_buffer[i].Info.GetAI() as ResidentialBuildingAI;
                    if (thisAI != null)
                    {
                        // This is residential! If we're not removing all households, recalculate home and visit counts using AI method.
                        if (!removeAll)
                        {
                            homeCount  = thisAI.CalculateHomeCount((ItemClass.Level)instance.m_buildings.m_buffer[i].m_level, new Randomizer(i), instance.m_buildings.m_buffer[i].Width, instance.m_buildings.m_buffer[i].Length);
                            visitCount = thisAI.CalculateVisitplaceCount((ItemClass.Level)instance.m_buildings.m_buffer[i].m_level, new Randomizer(i), instance.m_buildings.m_buffer[i].Width, instance.m_buildings.m_buffer[i].Length);
                        }

                        // Apply changes via direct call to EnsureCitizenUnits prefix patch from this mod and increment counter.
                        RealisticCitizenUnits.EnsureCitizenUnits(ref thisAI, i, ref instance.m_buildings.m_buffer[i], homeCount, 0, visitCount, 0);
                        homeCountChanged++;
                    }

                    // Clear any problems.
                    instance.m_buildings.m_buffer[i].m_problems = 0;
                }
            }

            Debugging.Message("set household counts to " + homeCount + " for " + homeCountChanged + " '" + prefabName + "' buildings");
        }
        public static void UnpatchAll()
        {
            // Only unapply if patches appplied.
            if (_patched)
            {
                Debugging.Message("reverting Harmony patches");

                // Unapply patches, but only with our HarmonyID.
                Harmony harmonyInstance = new Harmony(harmonyID);
                harmonyInstance.UnpatchAll(harmonyID);
                _patched = false;
            }
        }
示例#15
0
        /// <summary>
        /// Constructor.
        /// </summary>
        public ThumbnailGenerator()
        {
            if (ModSettings.debugLogging)
            {
                Debugging.Message("creating thumbnail generator");
            }

            // Get local reference from parent.
            renderer = ThumbnailManager.Renderer;

            // Size and setting for thumbnail images: 109 x 100, doubled for anti-aliasing.
            renderer.Size           = new Vector2(109, 100) * 2f;
            renderer.CameraRotation = 30f;
        }
        /// <summary>
        /// Simple Prefix patch to catch Monuments panel setup exceptions.
        /// All we do is call (via reverse patch) the original method and painlessly catch any exceptions.
        /// </summary>
        /// <param name="__instance">Harmony original instance reference</param>
        /// <returns></returns>
        private static bool Prefix(UnlockingPanel __instance)
        {
            try
            {
                RefreshMonumentsPanelRev(__instance);
            }
            catch
            {
                Debugging.Message("caught monuments panel exception");
            }

            // Don't call base method after this.
            return(false);
        }
        /// <summary>
        /// Harmony transpiler removing two checks from BuildingInfo.InitializePrefab.
        /// </summary>
        /// <param name="instructions">CIL code to alter.</param>
        /// <returns></returns>
        private static IEnumerable <CodeInstruction> Transpiler(IEnumerable <CodeInstruction> instructions)
        {
            // The checks we're targeting for removal are fortunately clearly defined by their exception operands.
            // We're only going to remove the exception throw itself (including stack loading), and not the preceeding conditional check.
            // This minimises our footprint and reduces the chance of conflict with other transpilers,
            // and also means we don't have to worry about dealing with any branches to the start of the check.
            string[] targetOperands =
            {
                "Private building cannot have manual placement style",
                "Private building cannot include roads or other net types"
            };

            var codes = new List <CodeInstruction>(instructions);

            // Deal with each of the operands consecutively and independently to avoid risk of error.
            foreach (string targetOperand in targetOperands)
            {
                // Stores the number of operands to cut.
                int cutCount = 0;

                // Iterate through each opcode in the CIL, looking for an ldarg.0 immediately followed by an ldstr.
                for (int i = 0; i < codes.Count; i++)
                {
                    if ((codes[i].opcode == OpCodes.Ldarg_0) && (codes[i + 1].opcode == OpCodes.Ldstr))
                    {
                        // Found a matching combo - now check the ldstr operand aginst our target.
                        if (codes[i + 1].operand.Equals(targetOperand))
                        {
                            // Operand match - now count forward from this operand until we encounter an exception throw.
                            while (codes[i + cutCount].opcode != OpCodes.Throw)
                            {
                                cutCount++;
                            }

                            Debugging.Message("InitPrefab transpiler removing CIL (offset " + cutCount + ") from " + i + " (" + codes[i].opcode + " to " + codes[i + cutCount].opcode + ") - " + targetOperand);

                            // Remove the CIL from the ldarg.0 to the throw (inclusive).
                            // +1 to avoid fencepost error (need to include original instruction as well).
                            codes.RemoveRange(i, cutCount + 1);

                            // We're done with this one - no point in continuing the loop.
                            break;
                        }
                    }
                }
            }

            return(codes.AsEnumerable());
        }
        /// <summary>
        /// Regenerates all thumbnails.
        /// Useful for e.g. regenerating thumbnails.
        /// </summary>
        internal void RegenerateThumbnails()
        {
            // Only do this if the ploppable tool has been created.
            if (Instance != null)
            {
                Debugging.Message("regenerating all thumbnails");

                // Step through each loaded and active RICO prefab.
                foreach (BuildingData buildingData in Loading.xmlManager.prefabHash.Values)
                {
                    // Cancel its atlas.
                    buildingData.thumbnailAtlas = null;
                }
            }
        }
        /// <summary>
        /// Fills the Ploppable Tool panel with building buttons.
        /// </summary>
        private void PopulateButtons()
        {
            Debugging.Message("populating building buttons");

            // Step through each loaded and active RICO prefab.
            foreach (var buildingData in Loading.xmlManager.prefabHash.Values)
            {
                if (buildingData != null)
                {
                    // Get the prefab.
                    var prefab = PrefabCollection <BuildingInfo> .FindLoaded(buildingData.name);

                    // Local settings first.
                    if (buildingData.hasLocal)
                    {
                        // Only if enabled.
                        if (buildingData.local.ricoEnabled)
                        {
                            // Add button to panel and remove any existing UI button (in other base game ploppable panels).
                            AddBuildingButton(buildingData, buildingData.local.uiCategory);
                            RemoveUIButton(prefab);
                            continue;
                        }
                    }
                    // Author settings second.
                    else if (buildingData.hasAuthor)
                    {
                        // Only if enabled.
                        if (buildingData.author.ricoEnabled)
                        {
                            // Add button to panel and remove any existing UI button (in other base game ploppable panels).
                            AddBuildingButton(buildingData, buildingData.author.uiCategory);
                            RemoveUIButton(prefab);
                            continue;
                        }
                    }
                    // Mod settings third.  Don't have to worry about enablement here.
                    else if (buildingData.hasMod)
                    {
                        AddBuildingButton(buildingData, buildingData.mod.uiCategory);
                        RemoveUIButton(prefab);
                    }
                }
            }

            // Set active tab as default.
            TabClicked(BuildingPanels[0], TabSprites[0]);
        }
 /// <summary>
 /// Save settings to XML file.
 /// </summary>
 internal static void SaveSettings()
 {
     try
     {
         // Pretty straightforward.  Serialisation is within GBRSettingsFile class.
         using (StreamWriter writer = new StreamWriter(SettingsFileName))
         {
             XmlSerializer xmlSerializer = new XmlSerializer(typeof(XMLSettingsFile));
             xmlSerializer.Serialize(writer, new XMLSettingsFile());
         }
     }
     catch (Exception e)
     {
         Debugging.Message("exception saving XML settings file");
         Debugging.LogException(e);
     }
 }
        /// <summary>
        /// Returns the translation for the given key in the current language.
        /// </summary>
        /// <param name="key">Translation key to transate</param>
        /// <returns>Translation </returns>
        public string Translate(string key)
        {
            Language currentLanguage;


            // Check to see if we're using system settings.
            if (currentIndex < 0)
            {
                // Using system settings - initialise system language if we haven't already.
                if (systemLanguage == null)
                {
                    SetSystemLanguage();
                }

                currentLanguage = systemLanguage;
            }
            else
            {
                currentLanguage = languages.Values[currentIndex];
            }

            // Check that a valid current language is set.
            if (currentLanguage != null)
            {
                // Check that the current key is included in the translation.
                if (currentLanguage.translationDictionary.ContainsKey(key))
                {
                    // All good!  Return translation.
                    return(currentLanguage.translationDictionary[key]);
                }
                else
                {
                    Debugging.Message("no translation for language " + currentLanguage.uniqueName + " found for key " + key);

                    // Attempt fallack translation.
                    return(FallbackTranslation(currentLanguage.uniqueName, key));
                }
            }
            else
            {
                Debugging.Message("no current language when translating key " + key);
            }

            // If we've made it this far, something went wrong; just return the key.
            return(key);
        }
示例#22
0
        /// <summary>
        /// Creates the panel object in-game and displays it.
        /// </summary>
        internal static void Open(BuildingInfo selected = null)
        {
            try
            {
                // If no instance already set, create one.
                if (uiGameObject == null)
                {
                    // Give it a unique name for easy finding with ModTools.
                    uiGameObject = new GameObject("RICOSettingsPanel");
                    uiGameObject.transform.parent = UIView.GetAView().transform;

                    _panel = uiGameObject.AddComponent <RICOSettingsPanel>();

                    // Set up panel.
                    Panel.Setup();
                }

                // Select appropriate building if there's a preselection.
                if (selected != null)
                {
                    Debugging.Message("selecting preselected building " + selected.name);
                    Panel.SelectBuilding(selected);
                }
                else if (lastSelection != null)
                {
                    Panel.SelectBuilding(lastSelection);

                    // Restore previous filter state.
                    if (lastFilter != null)
                    {
                        Panel.SetFilter(lastFilter);
                    }

                    // Restore previous building selection list postion and selected item.
                    Panel.SetListPosition(lastIndex, lastPostion);
                }

                Panel.Show();
            }
            catch (Exception e)
            {
                Debugging.LogException(e);
                return;
            }
        }
        /// <summary>
        /// Loads languages from XML files.
        /// </summary>
        private void LoadLanguages()
        {
            // Clear existing dictionary.
            languages.Clear();

            // Get the current assembly path and append our locale directory name.
            string assemblyPath = GetAssemblyPath();

            if (!assemblyPath.IsNullOrWhiteSpace())
            {
                string localePath = Path.Combine(assemblyPath, "Translations");

                // Ensure that the directory exists before proceeding.
                if (Directory.Exists(localePath))
                {
                    // Load each file in directory and attempt to deserialise as a translation file.
                    string[] translationFiles = Directory.GetFiles(localePath);
                    foreach (string translationFile in translationFiles)
                    {
                        using (StreamReader reader = new StreamReader(translationFile))
                        {
                            XmlSerializer xmlSerializer = new XmlSerializer(typeof(Language));
                            if (xmlSerializer.Deserialize(reader) is Language translation)
                            {
                                // Got one!  add it to the list.
                                languages.Add(translation.uniqueName, translation);
                            }
                            else
                            {
                                Debugging.Message("couldn't deserialize translation file '" + translationFile);
                            }
                        }
                    }
                }
                else
                {
                    Debugging.Message("translations directory not found");
                }
            }
            else
            {
                Debugging.Message("assembly path was empty");
            }
        }
        /// <summary>
        /// Removes a given prefab's existing UI button, if any (e.g. from the game's Park menu if the prefab was originally a park, etc.)
        /// </summary>
        /// <param name="prefab">Building prefab</param>
        private void RemoveUIButton(BuildingInfo prefab)
        {
            UIButton refButton = new UIButton();

            if (prefab?.name != null)
            {
                Debugging.Message("attempting to find UI button for " + prefab.name);
                // Find any existing UI component linked to this prefab.
                refButton = UIView.GetAView()?.FindUIComponent <UIButton>(prefab.name);
            }

            if (refButton != null)
            {
                // We found one - destry it.
                Debugging.Message("destroying UI button for " + prefab.name);
                refButton.isVisible = false;
                GameObject.Destroy(refButton.gameObject);
            }
        }
        /// <summary>
        /// Destroys all building buttons and generates a new set.
        /// Useful for e.g. regenerating thumbnails.
        /// </summary>
        internal void RebuildButtons()
        {
            // Only do this if the ploppable tool has been created.
            if (Instance != null)
            {
                Debugging.Message("destroying all building buttons");

                // Step through each loaded and active RICO prefab.
                foreach (var buildingData in Loading.xmlManager.prefabHash.Values)
                {
                    // Destroy all existing building buttons.
                    if (buildingData.buildingButton != null)
                    {
                        GameObject.Destroy(buildingData.buildingButton);
                    }
                }

                // Repopulate building buttons.
                Instance.PopulateButtons();
            }
        }
        /// <summary>
        /// Apply all Harmony patches.
        /// </summary>
        public static void PatchAll()
        {
            // Don't do anything if already patched.
            if (!_patched)
            {
                // Ensure Harmony is ready before patching.
                if (HarmonyHelper.IsHarmonyInstalled)
                {
                    Debugging.Message("deploying Harmony patches");

                    // Apply all annotated patches and update flag.
                    Harmony harmonyInstance = new Harmony(harmonyID);
                    harmonyInstance.PatchAll();
                    _patched = true;
                }
                else
                {
                    Debugging.Message("Harmony not ready");
                }
            }
        }
        /// <summary>
        /// Called by the game when the mod is initialised at the start of the loading process.
        /// </summary>
        /// <param name="loading">Loading mode (e.g. game, editor, scenario, etc.)</param>
        public override void OnCreated(ILoading loading)
        {
            // Don't do anything if not in game (e.g. if we're going into an editor).
            if (loading.currentMode != AppMode.Game)
            {
                isModEnabled = false;
                Debugging.Message("not loading into game, skipping activation");
            }
            else
            {
                // Check for conflicting (and other) mods.
                isModEnabled = ModUtils.CheckMods();
            }

            // If we're not enabling the mod due to one of the above checks failing, unapply Harmony patches before returning without doing anything.
            if (!isModEnabled)
            {
                Patcher.UnpatchAll();
                return;
            }

            // Make sure patches have been applied before proceeding.
            if (!Patcher.Patched)
            {
                Debugging.Message("Harmony patches not applied, exiting");
                isModEnabled = false;
                return;
            }

            // Otherwise, game on!
            Debugging.Message("v" + PloppableRICOMod.Version + " loading");

            // Ensure patch watchdog flag is properly initialised.
            patchOperating = false;

            // Create instances if they don't already exist.
            if (convertPrefabs == null)
            {
                convertPrefabs = new ConvertPrefabs();
            }

            if (xmlManager == null)
            {
                xmlManager = new RICOPrefabManager
                {
                    prefabHash = new Dictionary <BuildingInfo, BuildingData>(),
                    prefabList = new List <BuildingData>()
                };
            }

            // Read mod settings.
            SettingsFile settingsFile = Configuration <SettingsFile> .Load();

            Settings.plainThumbs  = settingsFile.PlainThumbs;
            Settings.debugLogging = settingsFile.DebugLogging;
            Settings.resetOnLoad  = settingsFile.ResetOnLoad;

            // Read any local RICO settings.
            string ricoDefPath = "LocalRICOSettings.xml";

            localRicoDef = null;

            if (!File.Exists(ricoDefPath))
            {
                Debugging.Message("no " + ricoDefPath + " file found");
            }
            else
            {
                localRicoDef = RICOReader.ParseRICODefinition("", ricoDefPath, insanityOK: true);

                if (localRicoDef == null)
                {
                    Debugging.Message("no valid definitions in " + ricoDefPath);
                }
            }

            base.OnCreated(loading);
        }
 /// <summary>
 /// Harmony Transpiler to add checks to see if extractor buildings should be demolished if they're outside of a district with relevant specialization settings.
 /// </summary>
 /// <param name="instructions">Original ILCode</param>
 /// <returns>Replacement (patched) ILCode</returns>
 private static IEnumerable <CodeInstruction> Transpiler(IEnumerable <CodeInstruction> instructions)
 {
     Debugging.Message("transpiler patching specialized building checks in IndustrialExtractorAI.SimulationStep");
     return(CheckSpecTranspiler.Transpiler(instructions));
 }
        /// <summary>
        /// Interpret and apply RICO settings to a building prefab.
        /// </summary>
        /// <param name="buildingData">RICO building data to apply</param>
        /// <param name="prefab">The building prefab to be changed</param>
        internal void ConvertPrefab(RICOBuilding buildingData, BuildingInfo prefab)
        {
            // AI class  for prefab init.
            string aiClass;


            if (prefab != null)
            {
                // Check eligibility for any growable assets.
                if (buildingData.growable)
                {
                    // Growables can't have any dimension greater than 4.
                    if (prefab.GetWidth() > 4 || prefab.GetLength() > 4)
                    {
                        buildingData.growable = false;
                        Debugging.Message("building '" + prefab.name + "' can't be growable because it is too big");
                    }

                    // Growables can't have net structures.
                    if (prefab.m_paths != null && prefab.m_paths.Length != 0)
                    {
                        buildingData.growable = false;
                        Debugging.Message("building '" + prefab.name + "' can't be growable because it contains network assets");
                    }
                }

                // Apply AI based on service.
                switch (buildingData.service)
                {
                // Dummy AI.
                case "dummy":

                    // Get AI.
                    DummyBuildingAI dummyAI = prefab.gameObject.AddComponent <DummyBuildingAI>();

                    // Use beautification ItemClass to avoid issues, and never make growable.
                    InitializePrefab(prefab, dummyAI, "Beautification Item", false);

                    // Final circular reference.
                    prefab.m_buildingAI.m_info = prefab;

                    // Dummy is a special case, and we're done here.
                    return;

                // Residential AI.
                case "residential":

                    // Get AI.
                    GrowableResidentialAI residentialAI = buildingData.growable ? prefab.gameObject.AddComponent <GrowableResidentialAI>() : prefab.gameObject.AddComponent <PloppableResidentialAI>();
                    if (residentialAI == null)
                    {
                        throw new Exception("Ploppable RICO residential AI not found.");
                    }

                    // Assign basic parameters.
                    residentialAI.m_ricoData         = buildingData;
                    residentialAI.m_constructionCost = buildingData.constructionCost;
                    residentialAI.m_homeCount        = buildingData.homeCount;

                    // Determine AI class string according to subservice.
                    switch (buildingData.subService)
                    {
                    case "low eco":
                        // Apply eco service if GC installed, otherwise use normal low residential.
                        if (Util.isGCinstalled())
                        {
                            aiClass = "Low Residential Eco - Level";
                        }
                        else
                        {
                            aiClass = "Low Residential - Level";
                        }
                        break;

                    case "high eco":
                        // Apply eco service if GC installed, otherwise use normal high residential.
                        if (Util.isGCinstalled())
                        {
                            aiClass = "High Residential Eco - Level";
                        }
                        else
                        {
                            aiClass = "High Residential - Level";
                        }
                        break;

                    case "high":
                        // Stock standard high commercial.
                        aiClass = "High Residential - Level";
                        break;

                    default:
                        // Fall back to low residential as default.
                        aiClass = "Low Residential - Level";

                        // If invalid subservice, report.
                        if (buildingData.subService != "low")
                        {
                            Debugging.ErrorBuffer.AppendLine("Residential building " + buildingData.name + " has invalid subservice " + buildingData.subService + "; reverting to low residential.");
                        }
                        break;
                    }

                    // Initialize the prefab.
                    InitializePrefab(prefab, residentialAI, aiClass + buildingData.level, buildingData.growable);

                    break;

                // Office AI.
                case "office":

                    // Get AI.
                    GrowableOfficeAI officeAI = buildingData.growable ? prefab.gameObject.AddComponent <GrowableOfficeAI>() : prefab.gameObject.AddComponent <PloppableOfficeAI>();
                    if (officeAI == null)
                    {
                        throw new Exception("Ploppable RICO Office AI not found.");
                    }

                    // Assign basic parameters.
                    officeAI.m_ricoData         = buildingData;
                    officeAI.m_workplaceCount   = buildingData.workplaceCount;
                    officeAI.m_constructionCost = buildingData.constructionCost;

                    // Check if this is an IT Cluster specialisation.

                    // Determine AI class string according to subservice.
                    if (buildingData.subService == "high tech")
                    {
                        // Apply IT cluster if GC installed, otherwise use Level 3 office.
                        if (Util.isGCinstalled())
                        {
                            aiClass = "Office - Hightech";
                        }
                        else
                        {
                            aiClass = "Office - Level3";
                        }
                    }
                    else
                    {
                        // Not IT cluster - boring old ordinary office.
                        aiClass = "Office - Level" + buildingData.level;
                    }

                    // Initialize the prefab.
                    InitializePrefab(prefab, officeAI, aiClass, buildingData.growable);

                    break;

                // Industrial AI.
                case "industrial":
                    // Get AI.
                    GrowableIndustrialAI industrialAI = buildingData.growable ? prefab.gameObject.AddComponent <GrowableIndustrialAI>() : prefab.gameObject.AddComponent <PloppableIndustrialAI>();
                    if (industrialAI == null)
                    {
                        throw new Exception("Ploppable RICO Industrial AI not found.");
                    }

                    // Assign basic parameters.
                    industrialAI.m_ricoData         = buildingData;
                    industrialAI.m_workplaceCount   = buildingData.workplaceCount;
                    industrialAI.m_constructionCost = buildingData.constructionCost;
                    industrialAI.m_pollutionEnabled = buildingData.pollutionEnabled;

                    // Determine AI class string according to subservice.
                    // Check for valid subservice.
                    if (IsValidIndSubServ(buildingData.subService))
                    {
                        // Specialised industry.
                        aiClass = ServiceName(buildingData.subService) + " - Processing";
                    }
                    else
                    {
                        // Generic industry.
                        aiClass = "Industrial - Level" + buildingData.level;
                    }

                    // Initialize the prefab.
                    InitializePrefab(prefab, industrialAI, aiClass, buildingData.growable);

                    break;

                // Extractor AI.
                case "extractor":
                    // Get AI.
                    GrowableExtractorAI extractorAI = buildingData.growable ? prefab.gameObject.AddComponent <GrowableExtractorAI>() : prefab.gameObject.AddComponent <PloppableExtractorAI>();
                    if (extractorAI == null)
                    {
                        throw new Exception("Ploppable RICO Extractor AI not found.");
                    }

                    // Assign basic parameters.
                    extractorAI.m_ricoData         = buildingData;
                    extractorAI.m_workplaceCount   = buildingData.workplaceCount;
                    extractorAI.m_constructionCost = buildingData.constructionCost;
                    extractorAI.m_pollutionEnabled = buildingData.pollutionEnabled;

                    // Check that we have a valid industry subservice.
                    if (IsValidIndSubServ(buildingData.subService))
                    {
                        // Initialise the prefab.
                        InitializePrefab(prefab, extractorAI, ServiceName(buildingData.subService) + " - Extractor", buildingData.growable);
                    }
                    else
                    {
                        Debugging.Message("invalid industry subservice " + buildingData.subService + " for extractor " + buildingData.name);
                    }

                    break;

                // Commercial AI.
                case "commercial":
                    // Get AI.
                    GrowableCommercialAI commercialAI = buildingData.growable ? prefab.gameObject.AddComponent <GrowableCommercialAI>() : prefab.gameObject.AddComponent <PloppableCommercialAI>();
                    if (commercialAI == null)
                    {
                        throw new Exception("Ploppable RICO Commercial AI not found.");
                    }

                    // Assign basic parameters.
                    commercialAI.m_ricoData         = buildingData;
                    commercialAI.m_workplaceCount   = buildingData.workplaceCount;
                    commercialAI.m_constructionCost = buildingData.constructionCost;

                    // Determine AI class string according to subservice.
                    switch (buildingData.subService)
                    {
                    // Organic and Local Produce.
                    case "eco":
                        // Apply eco specialisation if GC installed, otherwise use Level 1 low commercial.
                        if (Util.isGCinstalled())
                        {
                            // Eco commercial buildings only import food goods.
                            commercialAI.m_incomingResource = TransferManager.TransferReason.Food;
                            aiClass = "Eco Commercial";
                        }
                        else
                        {
                            aiClass = "Low Commercial - Level1";
                        }
                        break;

                    // Tourism.
                    case "tourist":
                        // Apply tourist specialisation if AD installed, otherwise use Level 1 low commercial.
                        if (Util.isADinstalled())
                        {
                            aiClass = "Tourist Commercial - Land";
                        }
                        else
                        {
                            aiClass = "Low Commercial - Level1";
                        }
                        break;

                    // Leisure.
                    case "leisure":
                        // Apply leisure specialisation if AD installed, otherwise use Level 1 low commercial.
                        if (Util.isADinstalled())
                        {
                            aiClass = "Leisure Commercial";
                        }
                        else
                        {
                            aiClass = "Low Commercial - Level1";
                        }
                        break;

                    // Bog standard high commercial.
                    case "high":
                        aiClass = "High Commercial - Level" + buildingData.level;
                        break;

                    // Fall back to low commercial as default.
                    default:
                        aiClass = "Low Commercial - Level" + buildingData.level;

                        // If invalid subservice, report.
                        if (buildingData.subService != "low")
                        {
                            Debugging.ErrorBuffer.AppendLine("Commercial building " + buildingData.name + " has invalid subService " + buildingData.subService + "; reverting to low commercial.");
                        }
                        break;
                    }

                    // Initialize the prefab.
                    InitializePrefab(prefab, commercialAI, aiClass, buildingData.growable);

                    break;
                }
            }
        }
        /// <summary>
        /// Interpret and apply RICO settings to a building prefab.
        /// </summary>
        /// <param name="buildingData">RICO building data to apply</param>
        /// <param name="prefab">The building prefab to be changed</param>
        internal void ConvertPrefab(RICOBuilding buildingData, BuildingInfo prefab)
        {
            if (prefab != null)
            {
                // Check eligibility for any growable assets.
                if (buildingData.growable)
                {
                    // Growables can't have any dimension greater than 4.
                    if (prefab.GetWidth() > 4 || prefab.GetLength() > 4)
                    {
                        buildingData.growable = false;
                        Debugging.Message("building '" + prefab.name + "' can't be growable because it is too big");
                    }

                    // Growables can't have net structures.
                    if (prefab.m_paths != null && prefab.m_paths.Length != 0)
                    {
                        buildingData.growable = false;
                        Debugging.Message("building '" + prefab.name + "' can't be growable because it contains network assets");
                    }
                }

                if (buildingData.service == "dummy")
                {
                    var ai = prefab.gameObject.AddComponent <DummyBuildingAI>();

                    // Use beautification ItemClass to avoid issues, and never make growable.
                    InitializePrefab(prefab, ai, "Beautification Item", false);

                    // Final circular reference.
                    prefab.m_buildingAI.m_info = prefab;
                }
                else if (buildingData.service == "residential")
                {
                    var ai = buildingData.growable ? prefab.gameObject.AddComponent <GrowableResidentialAI>() : prefab.gameObject.AddComponent <PloppableResidentialAI>();
                    if (ai == null)
                    {
                        throw (new Exception("Residential-AI not found."));
                    }

                    ai.m_ricoData         = buildingData;
                    ai.m_constructionCost = buildingData.constructionCost;
                    ai.m_homeCount        = buildingData.homeCount;

                    if (buildingData.subService == "low eco")
                    {
                        // Apply eco service if GC installed, otherwise use normal low residential.
                        if (Util.isGCinstalled())
                        {
                            InitializePrefab(prefab, ai, "Low Residential Eco - Level" + buildingData.level, buildingData.growable);
                        }
                        else
                        {
                            InitializePrefab(prefab, ai, "Low Residential - Level" + buildingData.level, buildingData.growable);
                        }
                    }
                    else if (buildingData.subService == "high eco")
                    {
                        // Apply eco service if GC installed, otherwise use normal high residential.
                        if (Util.isGCinstalled())
                        {
                            InitializePrefab(prefab, ai, "High Residential Eco - Level" + buildingData.level, buildingData.growable);
                        }
                        else
                        {
                            InitializePrefab(prefab, ai, "High Residential - Level" + buildingData.level, buildingData.growable);
                        }
                    }
                    else if (buildingData.subService == "high")
                    {
                        // Stock standard high commercial.
                        InitializePrefab(prefab, ai, "High Residential - Level" + buildingData.level, buildingData.growable);
                    }
                    else
                    {
                        // Fall back to low residential as default.
                        InitializePrefab(prefab, ai, "Low Residential - Level" + buildingData.level, buildingData.growable);

                        // If invalid subservice, report.
                        if (buildingData.subService != "low")
                        {
                            Debugging.ErrorBuffer.AppendLine("Residential building " + buildingData.name + " has invalid subservice " + buildingData.subService + "; reverting to low residential.");
                        }
                    }
                }
                else if (buildingData.service == "office")
                {
                    var ai = buildingData.growable ? prefab.gameObject.AddComponent <GrowableOfficeAI>() : prefab.gameObject.AddComponent <PloppableOfficeAI>();
                    if (ai == null)
                    {
                        throw (new Exception("Office-AI not found."));
                    }

                    ai.m_ricoData         = buildingData;
                    ai.m_workplaceCount   = buildingData.workplaceCount;
                    ai.m_constructionCost = buildingData.constructionCost;

                    if (buildingData.subService == "high tech")
                    {
                        // Apply IT cluster if GC installed, otherwise use Level 3 office.
                        if (Util.isGCinstalled())
                        {
                            InitializePrefab(prefab, ai, "Office - Hightech", buildingData.growable);
                        }
                        else
                        {
                            InitializePrefab(prefab, ai, "Office - Level3", buildingData.growable);
                        }
                    }
                    else
                    {
                        // Not IT cluster - boring old ordinary office.
                        InitializePrefab(prefab, ai, "Office - Level" + buildingData.level, buildingData.growable);
                    }
                }
                else if (buildingData.service == "industrial")
                {
                    var ai = buildingData.growable ? prefab.gameObject.AddComponent <GrowableIndustrialAI>() : prefab.gameObject.AddComponent <PloppableIndustrialAI>();
                    if (ai == null)
                    {
                        throw (new Exception("Industrial-AI not found."));
                    }

                    ai.m_ricoData         = buildingData;
                    ai.m_workplaceCount   = buildingData.workplaceCount;
                    ai.m_constructionCost = buildingData.constructionCost;
                    ai.m_pollutionEnabled = buildingData.pollutionEnabled;

                    if (Util.industryServices.Contains(buildingData.subService))
                    {
                        InitializePrefab(prefab, ai, Util.ucFirst(buildingData.subService) + " - Processing", buildingData.growable);
                    }
                    else
                    {
                        InitializePrefab(prefab, ai, "Industrial - Level" + buildingData.level, buildingData.growable);
                    }
                }
                else if (buildingData.service == "extractor")
                {
                    var ai = buildingData.growable ? prefab.gameObject.AddComponent <GrowableExtractorAI>() : prefab.gameObject.AddComponent <PloppableExtractorAI>();
                    if (ai == null)
                    {
                        throw (new Exception("Extractor-AI not found."));
                    }

                    ai.m_ricoData         = buildingData;
                    ai.m_workplaceCount   = buildingData.workplaceCount;
                    ai.m_constructionCost = buildingData.constructionCost;
                    ai.m_pollutionEnabled = buildingData.pollutionEnabled;

                    if (Util.industryServices.Contains(buildingData.subService))
                    {
                        InitializePrefab(prefab, ai, Util.ucFirst(buildingData.subService) + " - Extractor", buildingData.growable);
                    }
                }

                else if (buildingData.service == "commercial")
                {
                    var ai = buildingData.growable ? prefab.gameObject.AddComponent <GrowableCommercialAI>() : prefab.gameObject.AddComponent <PloppableCommercialAI>();
                    if (ai == null)
                    {
                        throw (new Exception("Commercial-AI not found."));
                    }

                    ai.m_ricoData         = buildingData;
                    ai.m_workplaceCount   = buildingData.workplaceCount;
                    ai.m_constructionCost = buildingData.constructionCost;

                    if (buildingData.subService == "eco")
                    {
                        // Apply eco specialisation if GC installed, otherwise use Level 1 low commercial.
                        if (Util.isGCinstalled())
                        {
                            // Eco commercial buildings only import food goods.
                            ai.m_incomingResource = TransferManager.TransferReason.Food;
                            InitializePrefab(prefab, ai, "Eco Commercial", buildingData.growable);
                        }
                        else
                        {
                            InitializePrefab(prefab, ai, "Low Commercial - Level1", buildingData.growable);
                        }
                    }
                    else if (buildingData.subService == "tourist")
                    {
                        // Apply tourist specialisation if AD installed, otherwise use Level 1 low commercial.
                        if (Util.isADinstalled())
                        {
                            InitializePrefab(prefab, ai, "Tourist Commercial - Land", buildingData.growable);
                        }
                        else
                        {
                            InitializePrefab(prefab, ai, "Low Commercial - Level1", buildingData.growable);
                        }
                    }
                    else if (buildingData.subService == "leisure")
                    {
                        // Apply leisure specialisation if AD installed, otherwise use Level 1 low commercial.
                        if (Util.isADinstalled())
                        {
                            InitializePrefab(prefab, ai, "Leisure Commercial", buildingData.growable);
                        }
                        else
                        {
                            InitializePrefab(prefab, ai, "Low Commercial - Level1", buildingData.growable);
                        }
                    }
                    else if (buildingData.subService == "high")
                    {
                        // Bog standard high commercial.
                        InitializePrefab(prefab, ai, "High Commercial - Level" + buildingData.level, buildingData.growable);
                    }
                    else
                    {
                        // Fall back to low commercial as default.
                        InitializePrefab(prefab, ai, "Low Commercial - Level" + buildingData.level, buildingData.growable);

                        // If invalid subservice, report.
                        if (buildingData.subService != "low")
                        {
                            Debugging.ErrorBuffer.AppendLine("Commercial building " + buildingData.name + " has invalid subService " + buildingData.subService + "; reverting to low commercial.");
                        }
                    }
                }
            }
        }