/// <summary> /// Calculates the construction cost of a workplace, depending on current settings (overrides or default). /// </summary> /// <param name="thisAI">AI reference to calculate for</param> /// <returns>Final construction cost</returns> internal static int WorkplaceConstructionCost(PrivateBuildingAI thisAI, int fixedCost) { int baseCost; // Local references. BuildingInfo thisInfo = thisAI.m_info; ItemClass.Level thisLevel = thisInfo.GetClassLevel(); // Are we overriding cost? if (ModSettings.overrideCost) { // Yes - calculate based on workplaces by level multiplied by appropriate cost-per-job setting. thisAI.CalculateWorkplaceCount(thisLevel, new Randomizer(), thisInfo.GetWidth(), thisInfo.GetLength(), out int jobs0, out int jobs1, out int jobs2, out int jobs3); baseCost = (ModSettings.costPerJob0 * jobs0) + (ModSettings.costPerJob1 * jobs1) + (ModSettings.costPerJob2 * jobs2) + (ModSettings.costPerJob3 * jobs3); } else { // No - just use the base cost provided. baseCost = fixedCost; } // Multiply base cost by 100 before feeding to EconomyManager for nomalization to game conditions prior to return. baseCost *= 100; Singleton <EconomyManager> .instance.m_EconomyWrapper.OnGetConstructionCost(ref baseCost, thisInfo.GetService(), thisInfo.GetSubService(), thisLevel); return(baseCost); }
/// <summary> /// Harmony Prefix patch to IndustrialBuildingAI.CalculateProductionCapacity to implement mod production calculations. /// </summary> /// <param name="__result">Original method result</param> /// <param name="__instance">Original AI instance reference</param> /// <param name="level">Building level</param> /// <returns>False (don't execute base game method after this)</returns> public static bool Prefix(ref int __result, IndustrialBuildingAI __instance, ItemClass.Level level, int width, int length) { // Get builidng info. BuildingInfo info = __instance.m_info; ItemClass.SubService subService = info.GetSubService(); // Array index. int arrayIndex = GetIndex(subService); // New or old method? if (prodModes[arrayIndex] == (int)ProdModes.popCalcs) { // New settings, based on population. float multiplier; switch (info.GetClassLevel()) { case ItemClass.Level.Level1: multiplier = 1f; break; case ItemClass.Level.Level2: multiplier = 0.933333f; break; default: multiplier = 0.8f; break; } // Get cached workplace count and calculate total workplaces. int[] workplaces = PopData.instance.WorkplaceCache(info, (int)level); float totalWorkers = workplaces[0] + workplaces[1] + workplaces[2] + workplaces[3]; // Multiply total workers by multipler and overall multiplier (from settings) to get result. __result = (int)((totalWorkers * multiplier * prodMults[arrayIndex]) / 100f); } else { // Legacy calcs. int[] array = LegacyAIUtils.GetIndustryArray(__instance.m_info, (int)level); // Original method return value. __result = Mathf.Max(100, width * length * array[DataStore.PRODUCTION]) / 100; } // Always set at least one. if (__result < 1) { Logging.Error("invalid production result ", __result.ToString(), " for ", __instance.m_info.name, "; setting to 1"); __result = 1; } // Don't execute base method after this. return(false); }
/// <summary> /// Returns the calculated visitplace count according to current settings for the given prefab and workforce total (e.g. for previewing effects of changes to workforces). /// </summary> /// <param name="prefab">Prefab to check</param> /// <param name="workplaces">Number of workplaces to apply</param> /// <returns>Calculated visitplaces</returns> internal static int PreviewVisitCount(BuildingInfo prefab, int workplaces) { // Get builidng info. ItemClass.SubService subService = prefab.GetSubService(); // Array index. int arrayIndex = GetIndex(subService); // New or old calculations? if (comVisitModes[arrayIndex] == (int)ComVisitModes.popCalcs) { // New calcs. return(NewVisitCount(subService, prefab.GetClassLevel(), workplaces)); } else { // Old calcs. return(LegacyVisitCount(prefab, prefab.GetClassLevel())); } }
/// <summary> /// Returns a list of calculation packs available for the given prefab. /// </summary> /// <param name="prefab">BuildingInfo prefab</param> /// <returns>Array of available calculation packs</returns> internal SchoolDataPack[] GetPacks(BuildingInfo prefab) { // Return list. List <SchoolDataPack> list = new List <SchoolDataPack>(); ItemClass.Level level = prefab.GetClassLevel(); // Iterate through each floor pack and see if it applies. foreach (SchoolDataPack pack in calcPacks) { // Check for matching service. if (pack.level == level) { // Service matches; add pack. list.Add(pack); } } return(list.ToArray()); }
/// <summary> /// Sets the customised number of workers for a given prefab. /// If a record doesn't already exist, a new one will be created. /// </summary> /// <param name="prefab">The prefab (BuildingInfo) to set</param> /// <param name="workers">The updated worker count</param> public static void SetWorker(BuildingInfo prefab, int workers) { // Update or add entry to configuration file cache. if (DataStore.workerCache.ContainsKey(prefab.name)) { // Prefab already has a record; update. DataStore.workerCache[prefab.name] = workers; } else { // Prefab doesn't already have a record; create. DataStore.workerCache.Add(prefab.name, workers); } // Save the updated configuration file. XMLUtilsWG.WriteToXML(); // Get current building hash (for updating prefab dictionary). var prefabHash = prefab.gameObject.GetHashCode(); // Calculate employment breakdown. int[] array = CommercialBuildingAIMod.GetArray(prefab, (int)prefab.GetClassLevel()); PrefabEmployStruct output = new PrefabEmployStruct(); AI_Utils.CalculateprefabWorkerVisit(prefab.GetWidth(), prefab.GetLength(), ref prefab, 4, ref array, out output); // Update entry in 'live' settings. if (DataStore.prefabWorkerVisit.ContainsKey(prefabHash)) { // Prefab already has a record; update. DataStore.prefabWorkerVisit[prefabHash] = output; } else { // Prefab doesn't already have a record; create. DataStore.prefabWorkerVisit.Add(prefabHash, output); } }
/// <summary> /// Updates the state of the service panel Realistic Population button - should only be visible and enabled when looking at elementary and high schools. /// </summary> internal static void UpdateServicePanelButton() { // Get current building instance. ushort building = WorldInfoPanel.GetCurrentInstanceID().Building; // Ensure valid building before proceeding. if (building > 0) { // Check for eduction service and level 1 or 2. BuildingInfo info = Singleton <BuildingManager> .instance.m_buildings.m_buffer[building].Info; if (info.GetService() == ItemClass.Service.Education && info.GetClassLevel() < ItemClass.Level.Level3) { // It's a school! Enable and show the button, and return. serviceButton.Enable(); serviceButton.Show(); return; } } // If we got here, it's not a valid school building; disable and hide the button. serviceButton.Disable(); serviceButton.Hide(); }
/// <summary> /// Updates all school prefabs (e.g. when the global multiplier has changed). /// </summary> internal void UpdateSchools() { // Iterate through all loaded building prefabs. for (uint i = 0; i < PrefabCollection <BuildingInfo> .LoadedCount(); ++i) { BuildingInfo building = PrefabCollection <BuildingInfo> .GetLoaded(i); // Check for schools. if (building?.name != null && building.GetAI() is SchoolAI schoolAI && (building.GetClassLevel() == ItemClass.Level.Level1 || building.GetClassLevel() == ItemClass.Level.Level2)) { // Found a school; update school record and tooltip. UpdateSchoolPrefab(building, schoolAI); } } }
/// <summary> /// Performs task on completion of level loading - recording of school default properties and application of our settings. /// Should be called OnLevelLoaded, after prefabs have been loaded but before gameplay commences. /// </summary> internal void OnLoad() { // Initialise original properties dictionary. originalStats = new Dictionary <string, OriginalSchoolStats>(); // Iterate through all loaded building prefabs. for (uint i = 0; i < PrefabCollection <BuildingInfo> .LoadedCount(); ++i) { BuildingInfo building = PrefabCollection <BuildingInfo> .GetLoaded(i); // Check for schools. if (building?.name != null && building.GetAI() is SchoolAI schoolAI && (building.GetClassLevel() == ItemClass.Level.Level1 || building.GetClassLevel() == ItemClass.Level.Level2)) { // Found a school; add it to our dictionary. originalStats.Add(building.name, new OriginalSchoolStats { jobs0 = schoolAI.m_workPlaceCount0, jobs1 = schoolAI.m_workPlaceCount1, jobs2 = schoolAI.m_workPlaceCount2, jobs3 = schoolAI.m_workPlaceCount3, cost = schoolAI.m_constructionCost, maintenance = schoolAI.m_maintenanceCost }); // If setting is set, get currently active pack and apply it. if (ModSettings.enableSchoolProperties) { ApplyPack(building, ActivePack(building) as SchoolDataPack); // ApplyPack includes a call to UpdateSchoolPrefab, so no need to do it again here. continue; } // Update school record and tooltip. UpdateSchoolPrefab(building, schoolAI); } } }
/// <summary> /// Returns the currently set default calculation pack for the given prefab. /// </summary> /// <param name="building">Building prefab</param> /// <returns>Default calculation data pack</returns> internal override DataPack CurrentDefaultPack(BuildingInfo building) => BaseDefaultPack(building.GetClassLevel(), building);
/// <summary> /// Upgrades/downgrades the selected building to the given level, if possible. /// </summary> /// <param name="buildingID">Building instance ID</param> /// <param name="targetLevel">Level to upgrade/downgrade to</param> internal static void ForceLevel(ushort buildingID, byte targetLevel) { // BuildingInfo to change to, if this building isn't historical. BuildingInfo targetInfo = null; // References. BuildingManager buildingManager = Singleton <BuildingManager> .instance; Building[] buildingBuffer = buildingManager.m_buildings.m_buffer; BuildingInfo buildingInfo = buildingBuffer[buildingID].Info; PrivateBuildingAI buildingAI = buildingInfo?.GetAI() as PrivateBuildingAI; if (buildingInfo == null || buildingAI == null) { // If something went wrong, abort. Logging.Error("couldn't get existing building info"); return; } // Check to see if this is historical or not, or is a RICO ploppable. bool isHistorical = buildingAI.IsHistorical(buildingID, ref Singleton <BuildingManager> .instance.m_buildings.m_buffer[buildingID], out bool _) || ModUtils.CheckRICOPloppable(buildingInfo); // Get target prefab (if needed, i.e. not historical or RICO ploppable). if (!isHistorical) { // Get upgrade/downgrade building target. targetInfo = GetTargetInfo(buildingID, targetLevel); if (targetInfo == null) { // If we failed, don't do anything more. return; } } // If we have a valid upgrade/downgrade target, proceed. if (isHistorical || targetInfo != null) { // Apply target level to our building and cancel all level-up progress. buildingBuffer[buildingID].m_level = targetLevel; buildingBuffer[buildingID].m_levelUpProgress = 0; // Apply our upgrade/downgrade target if not historical if (!isHistorical) { buildingManager.UpdateBuildingInfo(buildingID, targetInfo); } // Post-downgrade processing to update instance values - call game method if new level is equal to or greater than info base level, otherwise use custom method. BuildingInfo newInfo = targetInfo ?? buildingInfo; if (newInfo.GetAI() is PrivateBuildingAI newAI) { if (targetLevel < (byte)newInfo.GetClassLevel()) { // New level is less than info base level; call custom method. CustomBuildingUpgraded(newAI, buildingID, ref buildingBuffer[buildingID]); } else { // New level is equal to or greater than info base level; call game method. newAI.BuildingUpgraded(buildingID, ref buildingBuffer[buildingID]); } } } }
/// <summary> /// Render and show a preview of a building. /// </summary> /// <param name="building">The building to render</param> public void Show(BuildingInfo building) { // Update current selection to the new building. currentSelection = building; // Generate render if there's a selection with a mesh. if (currentSelection != null && currentSelection.m_mesh != null) { // Set default values. previewRender.CameraRotation = 210f; previewRender.Zoom = 4f; // Set mesh and material for render. previewRender.SetTarget(currentSelection); // Set background. previewSprite.texture = previewRender.Texture; noPreviewSprite.isVisible = false; // Render at next update. RenderPreview(); } else { // No valid current selection with a mesh; reset background. previewSprite.texture = null; noPreviewSprite.isVisible = true; } // Hide any empty building names. if (building == null) { buildingName.isVisible = false; buildingLevel.isVisible = false; buildingSize.isVisible = false; } else { // Set and show building name. buildingName.isVisible = true; buildingName.text = UIBuildingDetails.GetDisplayName(currentSelection.name); UIUtils.TruncateLabel(buildingName, width - 45); buildingName.autoHeight = true; // Set and show building level. buildingLevel.isVisible = true; buildingLevel.text = Translations.Translate("RPR_OPT_LVL") + " " + Mathf.Min((int)currentSelection.GetClassLevel() + 1, MaxLevelOf(currentSelection.GetSubService())); UIUtils.TruncateLabel(buildingLevel, width - 45); buildingLevel.autoHeight = true; // Set and show building size. buildingSize.isVisible = true; buildingSize.text = currentSelection.GetWidth() + "x" + currentSelection.GetLength(); UIUtils.TruncateLabel(buildingSize, width - 45); buildingSize.autoHeight = true; } }
/// <summary> /// Perform and display volumetric calculations for the currently selected building. /// </summary> /// <param name="building">Selected building prefab</param> /// <param name="levelData">Population (level) calculation data to apply to calculations</param> /// <param name="floorData">Floor calculation data to apply to calculations</param> /// <param name="schoolData">School calculation data to apply to calculations</param> /// <param name="schoolData">Multiplier to apply to calculations</param> internal void CalculateVolumetric(BuildingInfo building, LevelData levelData, FloorDataPack floorData, SchoolDataPack schoolData, float multiplier) { // Safety first! if (building == null) { return; } // Reset message label. messageLabel.text = string.Empty; // Perform calculations. // Get floors and allocate area an number of floor labels. SortedList <int, float> floors = PopData.instance.VolumetricFloors(building.m_generatedInfo, floorData, out float totalArea); floorAreaLabel.text = totalArea.ToString("N0", LocaleManager.cultureInfo); numFloorsLabel.text = floors.Count.ToString(); // Get total units. int totalUnits = PopData.instance.VolumetricPopulation(building.m_generatedInfo, levelData, floorData, multiplier, floors, totalArea); // Floor labels list. List <string> floorLabels = new List <string>(); // What we call our units for this building. string unitName; switch (building.GetService()) { case ItemClass.Service.Residential: // Residential - households. unitName = Translations.Translate("RPR_CAL_UNI_HOU"); break; case ItemClass.Service.Education: // Education - students. unitName = Translations.Translate("RPR_CAL_UNI_STU"); break; default: // Default - workplaces. unitName = Translations.Translate("RPR_CAL_UNI_WOR"); break; } // See if we're using area calculations for numbers of units, i.e. areaPer is at least one. if (levelData.areaPer > 0) { // Determine area percentage to use for calculations (inverse of empty area percentage). float areaPercent = 1 - (levelData.emptyPercent / 100f); // Create new floor area labels by iterating through each floor. for (int i = 0; i < floors.Count; ++i) { // StringBuilder, because we're doing a fair bit of manipulation here. StringBuilder floorString = new StringBuilder("Floor "); // Floor number floorString.Append(i + 1); floorString.Append(" " + Translations.Translate("RPR_CAL_VOL_ARA") + " "); floorString.Append(floors[i].ToString("N0")); // See if we're calculating units per individual floor. if (!levelData.multiFloorUnits) { // Number of units on this floor - always rounded down. int floorUnits = (int)((floors[i] * areaPercent) / levelData.areaPer); // Adjust by multiplier (after rounded calculation above). floorUnits = (int)(floorUnits * multiplier); // Add extra info to label. floorString.Append(" ("); floorString.Append(floorUnits.ToString("N0")); floorString.Append(" "); floorString.Append(unitName); floorString.Append(")"); } // Add new floor label item with results for this calculation. floorLabels.Add(floorString.ToString()); } } // Do we have a current school selection, and are we using school property overrides? if (schoolData != null && ModSettings.enableSchoolProperties) { // Yes - calculate and display school worker breakdown. int[] workers = SchoolData.instance.CalcWorkers(schoolData, totalUnits); schoolWorkerLabel.Show(); schoolWorkerLabel.text = workers[0] + " / " + workers[1] + " / " + workers[2] + " / " + workers[3]; // Calculate construction cost to display. int cost = SchoolData.instance.CalcCost(schoolData, totalUnits); ColossalFramework.Singleton <EconomyManager> .instance.m_EconomyWrapper.OnGetConstructionCost(ref cost, building.m_class.m_service, building.m_class.m_subService, building.m_class.m_level); // Calculate maintenance cost to display. int maintenance = SchoolData.instance.CalcMaint(schoolData, totalUnits) * 100; ColossalFramework.Singleton <EconomyManager> .instance.m_EconomyWrapper.OnGetMaintenanceCost(ref maintenance, building.m_class.m_service, building.m_class.m_subService, building.m_class.m_level); float displayMaint = Mathf.Abs(maintenance * 0.0016f); // And display school cost breakdown. costLabel.Show(); costLabel.text = cost.ToString((!(displayMaint >= 10f)) ? Settings.moneyFormat : Settings.moneyFormatNoCents, LocaleManager.cultureInfo) + " / " + displayMaint.ToString((!(displayMaint >= 10f)) ? Settings.moneyFormat : Settings.moneyFormatNoCents, LocaleManager.cultureInfo); // Enforce school floors list position. ResetFloorListPosition(); } else { // No - hide school worker breakdown and cost labels. schoolWorkerLabel.Hide(); costLabel.Hide(); // Enforce default floors list position. ResetFloorListPosition(); } // Allocate our new list of labels to the floors list (via an interim fastlist to avoid race conditions if we 'build' manually directly into floorsList). FastList <object> fastList = new FastList <object>() { m_buffer = floorLabels.ToArray(), m_size = floorLabels.Count }; floorsList.rowsData = fastList; // Display total unit calculation result. switch (building.GetService()) { case ItemClass.Service.Residential: // Residential building. totalJobsLabel.Hide(); totalStudentsLabel.Hide(); totalHomesLabel.Show(); totalHomesLabel.text = totalUnits.ToString("N0", LocaleManager.cultureInfo); break; case ItemClass.Service.Education: // School building. totalHomesLabel.Hide(); totalJobsLabel.Hide(); totalStudentsLabel.Show(); totalStudentsLabel.text = totalUnits.ToString("N0", LocaleManager.cultureInfo); break; default: // Workplace building. totalHomesLabel.Hide(); totalStudentsLabel.Hide(); totalJobsLabel.Show(); totalJobsLabel.text = totalUnits.ToString("N0", LocaleManager.cultureInfo); break; } // Display commercial visit count, or hide the label if not commercial. if (building.GetAI() is CommercialBuildingAI) { visitCountLabel.Show(); visitCountLabel.text = RealisticVisitplaceCount.PreviewVisitCount(building, totalUnits).ToString(); } else { visitCountLabel.Hide(); } // Display production count, or hide the label if not a production building. if (building.GetAI() is PrivateBuildingAI privateAI && (privateAI is OfficeBuildingAI || privateAI is IndustrialBuildingAI || privateAI is IndustrialExtractorAI)) { productionLabel.Show(); productionLabel.text = privateAI.CalculateProductionCapacity(building.GetClassLevel(), new ColossalFramework.Math.Randomizer(), building.GetWidth(), building.GetLength()).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> /// Called whenever the currently selected building is changed to update the panel display. /// </summary> /// <param name="building">Newly selected building</param> public void SelectionChanged(BuildingInfo building) { // Make sure we have a valid selection before proceeding. if (building?.name == null) { 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)) { Logging.Error("invalid building AI type in building details for building ", building.name); return; } // Residential vs. workplace AI. if (buildingAI is ResidentialBuildingAI) { // Get appropriate calculation array. array = LegacyAIUtils.GetResidentialArray(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 = OverrideUtils.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 = LegacyAIUtils.GetCommercialArray(building, (int)building.GetClassLevel()); break; case ItemClass.Service.Office: array = LegacyAIUtils.GetOfficeArray(building, (int)building.GetClassLevel()); break; case ItemClass.Service.Industrial: if (buildingAI is IndustrialExtractorAI) { array = LegacyAIUtils.GetExtractorArray(building); } else { array = LegacyAIUtils.GetIndustryArray(building, (int)building.GetClassLevel()); } break; default: Logging.Error("invalid building service in building details for building ", building.name); 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 = OverrideUtils.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; // Show visitor count for commercial buildings. if (buildingAI is CommercialBuildingAI commercialAI) { visitCountLabel.Show(); visitCountLabel.text = Translations.Translate("RPR_CAL_VOL_VIS") + " " + commercialAI.CalculateVisitplaceCount(building.GetClassLevel(), new Randomizer(), building.GetWidth(), building.GetLength()); } else { visitCountLabel.Hide(); } // Display production count, or hide the label if not a production building. if (building.GetAI() is PrivateBuildingAI privateAI && (privateAI is OfficeBuildingAI || privateAI is IndustrialBuildingAI || privateAI is IndustrialExtractorAI)) { productionLabel.Show(); productionLabel.text = Translations.Translate("RPR_CAL_VOL_PRD") + " " + privateAI.CalculateProductionCapacity(building.GetClassLevel(), new ColossalFramework.Math.Randomizer(), building.GetWidth(), building.GetLength()).ToString(); }