/// <summary> /// Uses reflection to find the IsRICOPopManaged method of Ploppable RICO Revisited. /// If successful, sets ricoPopManaged to /// </summary> internal static void RICOReflection() { string methodName = "IsRICOPopManaged"; // Iterate through each loaded plugin assembly. foreach (PluginManager.PluginInfo plugin in PluginManager.instance.GetPluginsInfo()) { foreach (Assembly assembly in plugin.GetAssemblies()) { if (assembly.GetName().Name.Equals("ploppablerico") && plugin.isEnabled) { // Found ploppablerico.dll that's part of an enabled plugin; try to get its ModUtils class. Type ricoModUtils = assembly.GetType("PloppableRICO.Interfaces"); if (ricoModUtils != null) { // Try to get IsRICOPopManaged method. ricoPopManaged = ricoModUtils.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static); if (ricoPopManaged != null) { // Success! We're done here. Debugging.Message("found " + methodName); return; } } } } } // If we got here, we were unsuccessful. Debugging.Message("didn't find " + methodName); }
static bool Prefix(ref int __result, CommercialBuildingAI __instance, ItemClass.Level level, Randomizer r, int width, int length) { PrefabEmployStruct visitors; // All commercial places will need visitors. CalcWorkplaces is normally called first, redirected above to include a calculation of worker visits (CalculateprefabWorkerVisit). // However, there is a problem with some converted assets that don't come through the "front door" (i.e. Ploppable RICO - see below). // Try to retrieve previously calculated value. if (!DataStore.prefabWorkerVisit.TryGetValue(__instance.m_info.gameObject.GetHashCode(), out visitors)) { // If we didn't get a value, most likely it was because the prefab wasn't properly initialised. // This can happen with Ploppable RICO when the underlying asset class isn't 'Default' (for example, where Ploppable RICO assets are originally Parks, Plazas or Monuments). // When that happens, the above line returns zero, which sets the building to 'Not Operating Properly'. // So, if the call returns false, we force a recalculation of workplace visits to make sure. // If it's still zero after this, then we'll just return a "legitimate" zero. visitors.visitors = 0; int[] array = CommercialBuildingAIMod.GetArray(__instance.m_info, (int)level); AI_Utils.CalculateprefabWorkerVisit(width, length, ref __instance.m_info, 4, ref array, out visitors); DataStore.prefabWorkerVisit.Add(__instance.m_info.gameObject.GetHashCode(), visitors); // Log this to help identify specific issues. Should only occur once per prefab. Debugging.Message("CalculateprefabWorkerVisit redux: " + __instance.m_info.name); } // Original method return value. __result = visitors.visitors; // Don't execute base method after this. return(false); }
/// <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> /// 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 RealPopMod) { // 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(RealPopMod.ModName + ": assembly path not found!"); }
public static int[] GetArray(BuildingInfo item, int level) { int[][] array = DataStore.office; try { switch (item.m_class.m_subService) { case ItemClass.SubService.OfficeHightech: array = DataStore.officeHighTech; break; case ItemClass.SubService.OfficeGeneric: default: break; } return(array[level]); } catch (System.Exception) { // Prevent unnecessary log spamming due to 'level-less' buildings returning level 1 instead of level 0. if (level != 1) { Debugging.Message(item.gameObject.name + " attempted to be use " + item.m_class.m_subService.ToString() + " with level " + level + ". Returning as level 0"); } return(array[0]); } }
public static int[] GetArray(BuildingInfo item, int level) { int[][] array = DataStore.residentialLow; try { switch (item.m_class.m_subService) { case ItemClass.SubService.ResidentialHighEco: array = DataStore.resEcoHigh; break; case ItemClass.SubService.ResidentialLowEco: array = DataStore.resEcoLow; break; case ItemClass.SubService.ResidentialHigh: array = DataStore.residentialHigh; break; case ItemClass.SubService.ResidentialLow: default: break; } return(array[level]); } catch (System.Exception) { Debugging.Message(item.gameObject.name + " attempted to be use " + item.m_class.m_subService.ToString() + " with level " + level + ". Returning as level 0"); return(array[0]); } }
/// <param name="name"></param> /// <param name="level"></param> /// <param name="callingFunction">For debug purposes</param> /// <returns></returns> private static int[] getArray(string name, int level, string callingFunction) { int[] array = new int[14]; switch (name) { case "ResidentialLow": array = DataStore.residentialLow[level]; break; case "ResidentialHigh": array = DataStore.residentialHigh[level]; break; case "CommercialLow": array = DataStore.commercialLow[level]; break; case "CommercialHigh": array = DataStore.commercialHigh[level]; break; case "CommercialTourist": array = DataStore.commercialTourist[level]; break; case "CommercialLeisure": array = DataStore.commercialLeisure[level]; break; case "Office": array = DataStore.office[level]; break; case "Industry": array = DataStore.industry[level]; break; case "IndustryOre": array = DataStore.industry_ore[level]; break; case "IndustryOil": array = DataStore.industry_oil[level]; break; case "IndustryForest": array = DataStore.industry_forest[level]; break; case "IndustryFarm": array = DataStore.industry_farm[level]; break; default: Debugging.Message("callingFunction " + callingFunction + ". unknown element name: " + name); break; } return(array); } // end getArray
/// <summary> /// Checks to see if another mod is installed, based on a provided assembly name. /// Case-sensitive! PloppableRICO is not the same as ploppablerico! /// </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) { // 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.Equals(assemblyName)) { 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> /// 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. }
// Output buffer public static void releaseBuffer() { if (sb.Length > 0) { Debugging.Message(sb.ToString()); sb.Remove(0, sb.Length); } }
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"); return; } // Check for original WG Realistic Population and Consumption Mod; if it's enabled, flag and don't activate this mod. if (IsModEnabled(426163185ul)) { conflictingMod = true; Debugging.Message("Realistic Population and Consumption Mod detected, skipping activation"); } else if (!isModEnabled) { isModEnabled = true; // Harmony patches. Debugging.Message("version v" + RealPopMod.Version + " loading"); _harmony.PatchAll(GetType().Assembly); Debugging.Message("patching complete"); MergeDefaultBonus(); // Remove bonus names from over rides foreach (string name in DataStore.bonusHouseholdCache.Keys) { DataStore.householdCache.Remove(name); } foreach (string name in DataStore.bonusWorkerCache.Keys) { DataStore.workerCache.Remove(name); } DataStore.seedToId.Clear(); for (int i = 0; i <= ushort.MaxValue; ++i) // Up to 1M buildings apparently is ok { // This creates a unique number try { Randomizer number = new Randomizer(i); DataStore.seedToId.Add(number.seed, (ushort)i); } catch (Exception) { //Debugging.writeDebugToFile("Seed collision at number: " + i); } } // Check for Ploppable RICO Revisited. ModUtils.RICOReflection(); } }
/// <summary> /// Loads the configuration XML file and sets the datastore. /// </summary> internal static void ReadFromXML() { // Check the exe directory first DataStore.currentFileLocation = ColossalFramework.IO.DataLocation.executableDirectory + Path.DirectorySeparatorChar + XML_FILE; bool fileAvailable = File.Exists(DataStore.currentFileLocation); if (!fileAvailable) { // Switch to default which is the cities skylines in the application data area. DataStore.currentFileLocation = ColossalFramework.IO.DataLocation.localApplicationData + Path.DirectorySeparatorChar + XML_FILE; fileAvailable = File.Exists(DataStore.currentFileLocation); } if (fileAvailable) { Debugging.Message("loading configuration file " + DataStore.currentFileLocation); // Load in from XML - Designed to be flat file for ease WG_XMLBaseVersion reader = new XML_VersionSix(); XmlDocument doc = new XmlDocument(); try { doc.Load(DataStore.currentFileLocation); int version = Convert.ToInt32(doc.DocumentElement.Attributes["version"].InnerText); if (version > 3 && version <= 5) { // Use version 5 reader = new XML_VersionFive(); // Make a back up copy of the old system to be safe File.Copy(DataStore.currentFileLocation, DataStore.currentFileLocation + ".ver5", true); string error = "Detected an old version of the XML (v5). " + DataStore.currentFileLocation + ".ver5 has been created for future reference and will be upgraded to the new version."; Debugging.bufferWarning(error); } else if (version <= 3) // Uh oh... version 4 was a while back.. { string error = "Detected an unsupported version of the XML (v4 or less). Backing up for a new configuration as :" + DataStore.currentFileLocation + ".ver4"; Debugging.bufferWarning(error); File.Copy(DataStore.currentFileLocation, DataStore.currentFileLocation + ".ver4", true); return; } reader.readXML(doc); } catch (Exception e) { // Game will now use defaults Debugging.bufferWarning("The following exception(s) were detected while loading the XML file. Some (or all) values may not be loaded."); Debugging.bufferWarning(e.Message); } } else { Debugging.Message("configuration file not found. Will output new file to: " + DataStore.currentFileLocation); } }
public override void OnReleased() { if (isModEnabled) { isModEnabled = false; // Unapply Harmony patches. _harmony.UnpatchAll(HarmonyID); Debugging.Message("patches unapplied"); } }
/// <summary> /// Updates (or creates a new) XML configuration file with current DataStore settings. /// </summary> internal static void WriteToXML() { try { WG_XMLBaseVersion xml = new XML_VersionSix(); xml.writeXML(DataStore.currentFileLocation); } catch (Exception e) { Debugging.Message("XML writing exception:\r\n" + e.Message); } }
/// <param name="doc"></param> public override void readXML(XmlDocument doc) { XmlElement root = doc.DocumentElement; try { //DataStore.enableExperimental = Convert.ToBoolean(root.Attributes["experimental"].InnerText); //DataStore.timeBasedRealism = Convert.ToBoolean(root.Attributes["enableTimeVariation"].InnerText); } catch (Exception) { DataStore.enableExperimental = false; } foreach (XmlNode node in root.ChildNodes) { try { if (node.Name.Equals(popNodeName)) { readPopulationNode(node); } else if (node.Name.Equals(consumeNodeName)) { readConsumptionNode(node); } else if (node.Name.Equals(visitNodeName)) { readVisitNode(node); } else if (node.Name.Equals(pollutionNodeName)) { readPollutionNode(node); } else if (node.Name.Equals(productionNodeName)) { readProductionNode(node); } else if (node.Name.Equals(bonusHouseName)) { readBonusHouseNode(node); } else if (node.Name.Equals(bonusWorkName)) { readBonusWorkers(node); } } catch (Exception e) { Debugging.Message("XML readNodes exception:\r\n" + e.ToString()); } } } // end readXML
public override void OnLevelLoaded(LoadMode mode) { // Check to see if a conflicting mod has been detected - if so, alert the user. if (conflictingMod) { UIView.library.ShowModal <ExceptionPanel>("ExceptionPanel").SetMessage("Realistic Population Revisited", "Original Realistic Population and Consumption Mod mod detected - Realistic Population Revisited is shutting down to protect your game. Only ONE of these mods can be enabled at the same time; please unsubscribe from the old Realistic Population and Consumption Mod, which is now deprecated!", true); } // Don't do anything further if mod hasn't activated (conflicting mod detected, or loading into editor instead of game). if (!isModEnabled) { return; } else if (mode == LoadMode.LoadGame || mode == LoadMode.NewGame) { if (!isLevelLoaded) { isLevelLoaded = true; // Now we can remove people DataStore.allowRemovalOfCitizens = true; Debugging.releaseBuffer(); Debugging.Message("successfully applied"); } } // Create new XML if one doesn't already exist. if (!File.Exists(DataStore.currentFileLocation)) { XMLUtilsWG.WriteToXML(); } // Add button to building info panels. BuildingDetailsPanel.AddInfoPanelButton(); // Check if we need to display update notification. if (UpdateNotification.notificationVersion != 2) { // No update notification "Don't show again" flag found; show the notification. UpdateNotification notification = new UpdateNotification(); notification.Create(); notification.Show(); } // Set up options panel event handler. OptionsPanel.OptionsEventHook(); }
/// <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); }
public static int[] GetArray(BuildingInfo item, int level) { int tempLevel = 0; int[][] array = DataStore.industry; try { switch (item.m_class.m_subService) { case ItemClass.SubService.IndustrialOre: array = DataStore.industry_ore; tempLevel = level + 1; break; case ItemClass.SubService.IndustrialForestry: array = DataStore.industry_forest; tempLevel = level + 1; break; case ItemClass.SubService.IndustrialFarming: array = DataStore.industry_farm; tempLevel = level + 1; break; case ItemClass.SubService.IndustrialOil: array = DataStore.industry_oil; tempLevel = level + 1; break; case ItemClass.SubService.IndustrialGeneric: // Deliberate fall through default: tempLevel = level; break; } return(array[tempLevel]); } catch (System.Exception) { Debugging.Message(item.gameObject.name + " attempted to be use " + item.m_class.m_subService.ToString() + " with level " + level + ". Returning as level 0"); return(array[0]); } }
/// <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"); } }
public static int[] GetArray(BuildingInfo item, int level) { int[][] array = DataStore.industry; try { switch (item.m_class.m_subService) { case ItemClass.SubService.IndustrialOre: array = DataStore.industry_ore; break; case ItemClass.SubService.IndustrialForestry: array = DataStore.industry_forest; break; case ItemClass.SubService.IndustrialFarming: array = DataStore.industry_farm; break; case ItemClass.SubService.IndustrialOil: array = DataStore.industry_oil; break; case ItemClass.SubService.IndustrialGeneric: // Deliberate fall through default: break; } return(array[level]); } catch (System.Exception) { // Prevent unnecessary log spamming due to 'level-less' buildings returning level 1 instead of level 0. if (level != 1) { Debugging.Message(item.gameObject.name + " attempted to be use " + item.m_class.m_subService.ToString() + " with level " + level + ". Returning as level 0"); } return(array[0]); } } // end getArray
/// <summary> /// Creates the panel object in-game. /// </summary> public void Create() { try { // Destroy existing (if any) instances. uiGameObject = GameObject.Find("RealPopUpgradeNotification"); if (uiGameObject != null) { Debugging.Message("found existing upgrade notification instance"); GameObject.Destroy(uiGameObject); } // Create new instance. // Give it a unique name for easy finding with ModTools. uiGameObject = new GameObject("RealPopUpgradeNotification"); uiGameObject.transform.parent = UIView.GetAView().transform; _instance = uiGameObject.AddComponent <UpdateNotification>(); } catch (Exception e) { Debugging.LogException(e); } }
/// <summary> /// Prints an exception message to the Unity output log. /// </summary> /// <param name="message">Message to log</param> internal static void LogException(Exception exception) { // Use StringBuilder for efficiency since we're doing a lot of manipulation here. StringBuilder message = new StringBuilder(); message.AppendLine("caught exception!"); message.AppendLine("Exception:"); message.AppendLine(exception.Message); message.AppendLine(exception.Source); message.AppendLine(exception.StackTrace); // Log inner exception as well, if there is one. if (exception.InnerException != null) { message.AppendLine("Inner exception:"); message.AppendLine(exception.InnerException.Message); message.AppendLine(exception.InnerException.Source); message.AppendLine(exception.InnerException.StackTrace); } // Write to log. Debugging.Message(message.ToString()); }
/// <summary> /// Called whenever the currently selected building is changed to update the panel display. /// </summary> /// <param name="building"></param> public void SelectionChanged(BuildingInfo building) { if ((building == null) || (building.name == null)) { // If no valid building selected, then hide the calculations panel. detailsPanel.height = 0; detailsPanel.isVisible = false; return; } // Variables to compare actual counts vs. mod count, to see if there's another mod overriding counts. int appliedCount; int modCount; // Building model size, not plot size. Vector3 buildingSize = building.m_size; int floorCount; // Array used for calculations depending on building service/subservice (via DataStore). int[] array; // Default minimum number of homes or jobs is one; different service types will override this. int minHomesJobs = 1; int customHomeJobs; // Check for valid building AI. if (!(building.GetAI() is PrivateBuildingAI buildingAI)) { Debugging.Message("invalid building AI type in building details"); return; } // Residential vs. workplace AI. if (buildingAI is ResidentialBuildingAI) { // Get appropriate calculation array. array = ResidentialBuildingAIMod.GetArray(building, (int)building.GetClassLevel()); // Set calculated homes label. homesJobsCalcLabel.text = Translations.Translate("RPR_CAL_HOM_CALC"); // Set customised homes label and get value (if any). homesJobsCustomLabel.text = Translations.Translate("RPR_CAL_HOM_CUST"); customHomeJobs = ExternalCalls.GetResidential(building); // Applied homes is what's actually being returned by the CaclulateHomeCount call to this building AI. // It differs from calculated homes if there's an override value for that building with this mod, or if another mod is overriding. appliedCount = buildingAI.CalculateHomeCount(building.GetClassLevel(), new Randomizer(0), building.GetWidth(), building.GetLength()); homesJobsActualLabel.text = Translations.Translate("RPR_CAL_HOM_APPL") + appliedCount; } else { // Workplace AI. // Default minimum number of jobs is 4. minHomesJobs = 4; // Find the correct array for the relevant building AI. switch (building.GetService()) { case ItemClass.Service.Commercial: array = CommercialBuildingAIMod.GetArray(building, (int)building.GetClassLevel()); break; case ItemClass.Service.Office: array = OfficeBuildingAIMod.GetArray(building, (int)building.GetClassLevel()); break; case ItemClass.Service.Industrial: if (buildingAI is IndustrialExtractorAI) { array = IndustrialExtractorAIMod.GetArray(building, (int)building.GetClassLevel()); } else { array = IndustrialBuildingAIMod.GetArray(building, (int)building.GetClassLevel()); } break; default: Debugging.Message("invalid building service in building details"); return; } // Set calculated jobs label. homesJobsCalcLabel.text = Translations.Translate("RPR_CAL_JOB_CALC") + " "; // Set customised jobs label and get value (if any). homesJobsCustomLabel.text = Translations.Translate("RPR_CAL_JOB_CUST") + " "; customHomeJobs = ExternalCalls.GetWorker(building); // Applied jobs is what's actually being returned by the CalculateWorkplaceCount call to this building AI. // It differs from calculated jobs if there's an override value for that building with this mod, or if another mod is overriding. int[] jobs = new int[4]; buildingAI.CalculateWorkplaceCount(building.GetClassLevel(), new Randomizer(0), building.GetWidth(), building.GetLength(), out jobs[0], out jobs[1], out jobs[2], out jobs[3]); appliedCount = jobs[0] + jobs[1] + jobs[2] + jobs[3]; homesJobsActualLabel.text = Translations.Translate("RPR_CAL_JOB_APPL") + " " + appliedCount; } // Reproduce CalcBase calculations to get building area. int calcWidth = building.GetWidth(); int calcLength = building.GetLength(); floorCount = Mathf.Max(1, Mathf.FloorToInt(buildingSize.y / array[DataStore.LEVEL_HEIGHT])); // If CALC_METHOD is zero, then calculations are based on building model size, not plot size. if (array[DataStore.CALC_METHOD] == 0) { // If asset has small x dimension, then use plot width in squares x 6m (75% of standard width) instead. if (buildingSize.x <= 1) { calcWidth *= 6; } else { calcWidth = (int)buildingSize.x; } // If asset has small z dimension, then use plot length in squares x 6m (75% of standard length) instead. if (buildingSize.z <= 1) { calcLength *= 6; } else { calcLength = (int)buildingSize.z; } } else { // If CALC_METHOD is nonzero, then caluclations are based on plot size, not building size. // Plot size is 8 metres per square. calcWidth *= 8; calcLength *= 8; } // Display calculated (and retrieved) details. detailLabels[(int)Details.width].text = Translations.Translate("RPR_CAL_BLD_X") + " " + calcWidth; detailLabels[(int)Details.length].text = Translations.Translate("RPR_CAL_BLD_Z") + " " + calcLength; detailLabels[(int)Details.height].text = Translations.Translate("RPR_CAL_BLD_Y") + " " + (int)buildingSize.y; detailLabels[(int)Details.personArea].text = Translations.Translate("RPR_CAL_BLD_M2") + " " + array[DataStore.PEOPLE]; detailLabels[(int)Details.floorHeight].text = Translations.Translate("RPR_CAL_FLR_Y") + " " + array[DataStore.LEVEL_HEIGHT]; detailLabels[(int)Details.floors].text = Translations.Translate("RPR_CAL_FLR") + " " + floorCount; // Area calculation - will need this later. int calculatedArea = calcWidth * calcLength; detailLabels[(int)Details.area].text = Translations.Translate("RPR_CAL_M2") + " " + calculatedArea; // Show or hide extra floor modifier as appropriate (hide for zero or less, otherwise show). if (array[DataStore.DENSIFICATION] > 0) { detailLabels[(int)Details.extraFloors].text = Translations.Translate("RPR_CAL_FLR_M") + " " + array[DataStore.DENSIFICATION]; detailLabels[(int)Details.extraFloors].isVisible = true; } else { detailLabels[(int)Details.extraFloors].isVisible = false; } // Set minimum residences for high density. if ((building.GetSubService() == ItemClass.SubService.ResidentialHigh) || (building.GetSubService() == ItemClass.SubService.ResidentialHighEco)) { // Minimum of 2, or 90% number of floors, whichever is greater. This helps the 1x1 high density. minHomesJobs = Mathf.Max(2, Mathf.CeilToInt(0.9f * floorCount)); } // Perform actual household or workplace calculation. modCount = Mathf.Max(minHomesJobs, (calculatedArea * (floorCount + Mathf.Max(0, array[DataStore.DENSIFICATION]))) / array[DataStore.PEOPLE]); homesJobsCalcLabel.text += modCount; // Set customised homes/jobs label (leave blank if no custom setting retrieved). if (customHomeJobs > 0) { homesJobsCustomLabel.text += customHomeJobs.ToString(); // Update modCount to reflect the custom figures. modCount = customHomeJobs; } // Check to see if Ploppable RICO Revisited is controlling this building's population. if (ModUtils.CheckRICO(building)) { messageLabel.text = Translations.Translate("RPR_CAL_RICO"); messageLabel.Show(); } else { // Hide message text by default. messageLabel.Hide(); } // We've got a valid building and results, so show panel. detailsPanel.height = 270; detailsPanel.isVisible = true; }
/// <summary> /// Create the panel; we no longer use Start() as that's not sufficiently reliable (race conditions), and is no longer needed, with the new create/destroy process. /// </summary> public void Setup() { const int marginPadding = 10; // Generic setup. isVisible = true; canFocus = true; isInteractive = true; backgroundSprite = "UnlockingPanel"; autoLayout = false; autoLayoutDirection = LayoutDirection.Vertical; autoLayoutPadding.top = 5; autoLayoutPadding.right = 5; builtinKeyNavigation = true; clipChildren = true; // Panel title. UILabel title = this.AddUIComponent <UILabel>(); title.relativePosition = new Vector3(0, 5); title.textAlignment = UIHorizontalAlignment.Center; title.text = Translations.Translate("RPR_CUS_TITLE"); title.textScale = 1.2f; title.autoSize = false; title.width = this.width; title.height = 30; // Text field label. homeJobLabel = this.AddUIComponent <UILabel>(); homeJobLabel.relativePosition = new Vector3(marginPadding, 40); homeJobLabel.textAlignment = UIHorizontalAlignment.Left; homeJobLabel.text = Translations.Translate("RPR_LBL_HOM"); // Home or jobs count text field. homeJobsCount = UIUtils.CreateTextField(this, this.width - (marginPadding * 3) - homeJobLabel.width, 20); homeJobsCount.relativePosition = new Vector3(marginPadding + homeJobLabel.width + marginPadding, 40); // Save button. saveButton = UIUtils.CreateButton(this, 200); saveButton.relativePosition = new Vector3(marginPadding, 70); saveButton.text = Translations.Translate("RPR_CUS_ADD"); saveButton.tooltip = Translations.Translate("RPR_CUS_ADD_TIP"); saveButton.Disable(); // Delete button. deleteButton = UIUtils.CreateButton(this, 200); deleteButton.relativePosition = new Vector3(marginPadding, 110); deleteButton.text = Translations.Translate("RPR_CUS_DEL"); deleteButton.tooltip = Translations.Translate("RPR_CUS_DEL_TIP"); deleteButton.Disable(); // Save button event handler. saveButton.eventClick += (component, clickEvent) => { // Hide message. messageLabel.isVisible = false; // Don't do anything with invalid entries. if (currentSelection == null || currentSelection.name == null) { return; } // Read textfield if possible. if (int.TryParse(homeJobsCount.text, out int homesJobs)) { // Minimum value of 1. if (homesJobs < 1) { // Print warning message in red. messageLabel.textColor = new Color32(255, 0, 0, 255); messageLabel.text = Translations.Translate("RPR_ERR_ZERO"); messageLabel.isVisible = true; } else { // Homes or jobs? if (currentSelection.GetService() == ItemClass.Service.Residential) { Debugging.Message("adding custom household count of " + homesJobs + " for " + currentSelection.name); // Residential building. ExternalCalls.SetResidential(currentSelection, homesJobs); // Update household counts for existing instances of this building - only needed for residential buildings. // Workplace counts will update automatically with next call to CalculateWorkplaceCount; households require more work (tied to CitizenUnits). UpdateHouseholds(currentSelection.name); } else { Debugging.Message("adding custom workplace count of " + homesJobs + " for " + currentSelection.name); // Employment building. ExternalCalls.SetWorker(currentSelection, homesJobs); } // Refresh the display so that all panels reflect the updated settings. BuildingDetailsPanel.Panel.UpdateSelectedBuilding(currentSelection); BuildingDetailsPanel.Panel.Refresh(); } } else { // TryParse couldn't parse the data; print warning message in red. messageLabel.textColor = new Color32(255, 0, 0, 255); messageLabel.text = Translations.Translate("RPR_ERR_INV"); messageLabel.isVisible = true; } }; // Delete button event handler. deleteButton.eventClick += (component, clickEvent) => { // Hide message. messageLabel.isVisible = false; // Don't do anything with invalid entries. if (currentSelection == null || currentSelection.name == null) { return; } Debugging.Message("deleting custom entry for " + currentSelection.name); // Homes or jobs? Remove custom entry as appropriate. if (currentSelection.GetService() == ItemClass.Service.Residential) { // Residential building. ExternalCalls.RemoveResidential(currentSelection); // Update household counts for existing instances of this building - only needed for residential buildings. // Workplace counts will update automatically with next call to CalculateWorkplaceCount; households require more work (tied to CitizenUnits). UpdateHouseholds(currentSelection.name); } else { // Employment building. ExternalCalls.RemoveWorker(currentSelection); } // Refresh the display so that all panels reflect the updated settings. BuildingDetailsPanel.Panel.Refresh(); homeJobsCount.text = string.Empty; }; // Message label (initially hidden). messageLabel = this.AddUIComponent <UILabel>(); messageLabel.relativePosition = new Vector3(marginPadding, 160); messageLabel.textAlignment = UIHorizontalAlignment.Left; messageLabel.autoSize = false; messageLabel.autoHeight = true; messageLabel.wordWrap = true; messageLabel.width = this.width - (marginPadding * 2); messageLabel.isVisible = false; messageLabel.text = "No message to display"; }
} // end readXML /// <param name="fullPathFileName"></param> /// <returns></returns> public override bool writeXML(string fullPathFileName) { XmlDocument xmlDoc = new XmlDocument(); XmlNode rootNode = xmlDoc.CreateElement("WG_CityMod"); XmlAttribute attribute = xmlDoc.CreateAttribute("version"); attribute.Value = "6"; rootNode.Attributes.Append(attribute); /* * attribute = xmlDoc.CreateAttribute("experimental"); * attribute.Value = DataStore.enableExperimental ? "true" : "false"; * rootNode.Attributes.Append(attribute); */ xmlDoc.AppendChild(rootNode); XmlNode popNode = xmlDoc.CreateElement(popNodeName); attribute = xmlDoc.CreateAttribute("strictCapacity"); attribute.Value = DataStore.strictCapacity ? "true" : "false"; popNode.Attributes.Append(attribute); XmlNode consumeNode = xmlDoc.CreateElement(consumeNodeName); XmlNode visitNode = xmlDoc.CreateElement(visitNodeName); XmlNode pollutionNode = xmlDoc.CreateElement(pollutionNodeName); XmlNode productionNode = xmlDoc.CreateElement(productionNodeName); try { MakeNodes(xmlDoc, "ResidentialLow", DataStore.residentialLow, popNode, consumeNode, visitNode, pollutionNode, productionNode); MakeNodes(xmlDoc, "ResidentialHigh", DataStore.residentialHigh, popNode, consumeNode, visitNode, pollutionNode, productionNode); MakeNodes(xmlDoc, "ResEcoLow", DataStore.resEcoLow, popNode, consumeNode, visitNode, pollutionNode, productionNode); MakeNodes(xmlDoc, "ResEcoHigh", DataStore.resEcoHigh, popNode, consumeNode, visitNode, pollutionNode, productionNode); MakeNodes(xmlDoc, "CommercialLow", DataStore.commercialLow, popNode, consumeNode, visitNode, pollutionNode, productionNode); MakeNodes(xmlDoc, "CommercialHigh", DataStore.commercialHigh, popNode, consumeNode, visitNode, pollutionNode, productionNode); MakeNodes(xmlDoc, "CommercialEco", DataStore.commercialEco, popNode, consumeNode, visitNode, pollutionNode, productionNode); MakeNodes(xmlDoc, "CommercialTourist", DataStore.commercialTourist, popNode, consumeNode, visitNode, pollutionNode, productionNode); MakeNodes(xmlDoc, "CommercialLeisure", DataStore.commercialLeisure, popNode, consumeNode, visitNode, pollutionNode, productionNode); MakeNodes(xmlDoc, "Office", DataStore.office, popNode, consumeNode, visitNode, pollutionNode, productionNode); MakeNodes(xmlDoc, "OfficeHighTech", DataStore.officeHighTech, popNode, consumeNode, visitNode, pollutionNode, productionNode); MakeNodes(xmlDoc, "Industry", DataStore.industry, popNode, consumeNode, visitNode, pollutionNode, productionNode); MakeNodes(xmlDoc, "IndustryFarm", DataStore.industry_farm, popNode, consumeNode, visitNode, pollutionNode, productionNode); MakeNodes(xmlDoc, "IndustryForest", DataStore.industry_forest, popNode, consumeNode, visitNode, pollutionNode, productionNode); MakeNodes(xmlDoc, "IndustryOre", DataStore.industry_ore, popNode, consumeNode, visitNode, pollutionNode, productionNode); MakeNodes(xmlDoc, "IndustryOil", DataStore.industry_oil, popNode, consumeNode, visitNode, pollutionNode, productionNode); } catch (Exception e) { Debugging.Message("XML MakeNodes exception:\r\n" + e.ToString()); } // First segment CreatePopulationNodeComment(xmlDoc, rootNode); rootNode.AppendChild(popNode); CreateConsumptionNodeComment(xmlDoc, rootNode); rootNode.AppendChild(consumeNode); CreateVisitNodeComment(xmlDoc, rootNode); rootNode.AppendChild(visitNode); CreateProductionNodeComment(xmlDoc, rootNode); rootNode.AppendChild(productionNode); CreatePollutionNodeComment(xmlDoc, rootNode); rootNode.AppendChild(pollutionNode); // Add mesh names to XML for house holds XmlComment comment = xmlDoc.CreateComment(" ******* House hold data ******* "); rootNode.AppendChild(comment); XmlNode overrideHouseholdNode = xmlDoc.CreateElement(overrideHouseName); attribute = xmlDoc.CreateAttribute("printResNames"); attribute.Value = DataStore.printResidentialNames ? "true" : "false"; overrideHouseholdNode.Attributes.Append(attribute); attribute = xmlDoc.CreateAttribute("mergeResNames"); attribute.Value = DataStore.mergeResidentialNames ? "true" : "false"; overrideHouseholdNode.Attributes.Append(attribute); SortedList <string, int> list = new SortedList <string, int>(DataStore.householdCache); foreach (string name in list.Keys) { XmlNode meshNameNode = xmlDoc.CreateElement(meshName); meshNameNode.InnerXml = SecurityElement.Escape(name); attribute = xmlDoc.CreateAttribute("value"); int value = 1; DataStore.householdCache.TryGetValue(name, out value); attribute.Value = Convert.ToString(value); meshNameNode.Attributes.Append(attribute); overrideHouseholdNode.AppendChild(meshNameNode); } rootNode.AppendChild(overrideHouseholdNode); // Append the overrideHousehold to root // Add mesh names to XML comment = xmlDoc.CreateComment(" ******* Printed out house hold data. To activate the value, move the line into the override segment ******* "); rootNode.AppendChild(comment); XmlNode printHouseholdNode = xmlDoc.CreateElement(printHouseName); list = new SortedList <string, int>(DataStore.housePrintOutCache); foreach (string data in list.Keys) { XmlNode meshNameNode = xmlDoc.CreateElement(meshName); meshNameNode.InnerXml = data; attribute = xmlDoc.CreateAttribute("value"); int value = 1; DataStore.housePrintOutCache.TryGetValue(data, out value); attribute.Value = Convert.ToString(value); meshNameNode.Attributes.Append(attribute); printHouseholdNode.AppendChild(meshNameNode); } rootNode.AppendChild(printHouseholdNode); // Append the printHousehold to root // Add mesh names to XML list = new SortedList <string, int>(DataStore.bonusHouseholdCache); if (list.Keys.Count != 0) { XmlNode bonusHouseholdNode = xmlDoc.CreateElement(bonusHouseName); foreach (string data in list.Keys) { XmlNode meshNameNode = xmlDoc.CreateElement(meshName); meshNameNode.InnerXml = data; attribute = xmlDoc.CreateAttribute("value"); DataStore.bonusHouseholdCache.TryGetValue(data, out int value); attribute.Value = Convert.ToString(value); meshNameNode.Attributes.Append(attribute); bonusHouseholdNode.AppendChild(meshNameNode); } rootNode.AppendChild(bonusHouseholdNode); // Append the bonusHousehold to root } // Add mesh names to XML for workers comment = xmlDoc.CreateComment(" ******* Worker data ******* "); rootNode.AppendChild(comment); XmlNode overrideWorkNode = xmlDoc.CreateElement(overrideWorkName); attribute = xmlDoc.CreateAttribute("printWorkNames"); attribute.Value = DataStore.printEmploymentNames ? "true" : "false"; overrideWorkNode.Attributes.Append(attribute); attribute = xmlDoc.CreateAttribute("mergeWorkNames"); attribute.Value = DataStore.mergeEmploymentNames ? "true" : "false"; overrideWorkNode.Attributes.Append(attribute); SortedList <string, int> wList = new SortedList <string, int>(DataStore.workerCache); foreach (string name in wList.Keys) { XmlNode meshNameNode = xmlDoc.CreateElement(meshName); meshNameNode.InnerXml = SecurityElement.Escape(name); int value = 1; DataStore.workerCache.TryGetValue(name, out value); attribute = xmlDoc.CreateAttribute("value"); attribute.Value = Convert.ToString(value); meshNameNode.Attributes.Append(attribute); overrideWorkNode.AppendChild(meshNameNode); } rootNode.AppendChild(overrideWorkNode); // Append the overrideWorkers to root // Add mesh names to dictionary comment = xmlDoc.CreateComment(" ******* Printed out worker data. To activate the value, move the line into the override segment ******* "); rootNode.AppendChild(comment); XmlNode printWorkNode = xmlDoc.CreateElement(printWorkName); wList = new SortedList <string, int>(DataStore.workerPrintOutCache); foreach (string data in wList.Keys) { if (!DataStore.workerCache.ContainsKey(data)) { XmlNode meshNameNode = xmlDoc.CreateElement(meshName); meshNameNode.InnerXml = data; DataStore.workerPrintOutCache.TryGetValue(data, out int value); attribute = xmlDoc.CreateAttribute("value"); attribute.Value = Convert.ToString(value); meshNameNode.Attributes.Append(attribute); printWorkNode.AppendChild(meshNameNode); } } rootNode.AppendChild(printWorkNode); // Append the printWorkers to root // Add mesh names to dictionary wList = new SortedList <string, int>(DataStore.bonusWorkerCache); if (wList.Keys.Count != 0) { XmlNode bonusWorkNode = xmlDoc.CreateElement(bonusWorkName); foreach (string data in wList.Keys) { XmlNode meshNameNode = xmlDoc.CreateElement(meshName); meshNameNode.InnerXml = data; DataStore.bonusWorkerCache.TryGetValue(data, out int value); attribute = xmlDoc.CreateAttribute("value"); attribute.Value = Convert.ToString(value); meshNameNode.Attributes.Append(attribute); bonusWorkNode.AppendChild(meshNameNode); } rootNode.AppendChild(bonusWorkNode); // Append the bonusWorkers to root } try { if (File.Exists(fullPathFileName)) { if (File.Exists(fullPathFileName + ".bak")) { File.Delete(fullPathFileName + ".bak"); } File.Move(fullPathFileName, fullPathFileName + ".bak"); } } catch (Exception e) { Debugging.Message("PathFileName exception:\r\n" + e.ToString()); } try { xmlDoc.Save(fullPathFileName); } catch (Exception e) { Debugging.Message("XML save exception:\r\n" + e.ToString()); return(false); // Only time when we say there's an error } return(true); } // end writeXML