/// <summary> /// Called whenever the currently selected building is changed to update the panel display. /// </summary> /// <param name="building">Newly selected building</param> internal void SelectionChanged(BuildingInfo building) { // Set current building. currentBuilding = building; // Safety first! if (currentBuilding != null) { string buildingName = building.name; // Get available calculation packs for this building. popPacks = PopData.instance.GetPacks(building); floorPacks = FloorData.instance.Packs; // Get current and default packs for this item. currentPopPack = (PopDataPack)PopData.instance.ActivePack(building); currentFloorPack = (FloorDataPack)FloorData.instance.ActivePack(building); PopDataPack defaultPopPack = (PopDataPack)PopData.instance.CurrentDefaultPack(building); FloorDataPack defaultFloorPack = (FloorDataPack)FloorData.instance.CurrentDefaultPack(building); // Update multiplier before we do any other calcs. multCheck.isChecked = Multipliers.instance.HasOverride(buildingName); currentMult = Multipliers.instance.ActiveMultiplier(building); // Build pop pack menu. popMenu.items = new string[popPacks.Length]; for (int i = 0; i < popMenu.items.Length; ++i) { popMenu.items[i] = popPacks[i].displayName; // Check for default name match, if (popPacks[i].name.Equals(defaultPopPack.name)) { popMenu.items[i] += Translations.Translate("RPR_PCK_DEF"); } // Set menu selection to current pack if it matches. if (popPacks[i].name.Equals(currentPopPack.name)) { popMenu.selectedIndex = i; } } // Set population pack to current pack. UpdatePopSelection(currentPopPack); // Build floor pack menu. floorMenu.items = new string[floorPacks.Length]; for (int i = 0; i < floorPacks.Length; ++i) { floorMenu.items[i] = floorPacks[i].displayName; // Check for default name match, if (floorPacks[i].name.Equals(defaultFloorPack.name)) { floorMenu.items[i] += Translations.Translate("RPR_PCK_DEF"); } // Set menu selection to current pack if it matches. if (floorPacks[i].name.Equals(currentFloorPack.name)) { floorMenu.selectedIndex = i; // Force pack selection update. UpdateFloorSelection(i); } } // Update legacy panel for private building AIs (volumetric panel is updated by menu selection change above). if (building.GetAI() is PrivateBuildingAI) { legacyPanel.SelectionChanged(building); } // Is this a school building (need to do school building after pop and floor packs are updated)? if (building.GetAI() is SchoolAI) { // Yes - school building. Set current pack. currentSchoolPack = (SchoolDataPack)SchoolData.instance.ActivePack(building); // Are we using custom school settings? if (ModSettings.enableSchoolProperties) { // Yes - extend panel height and show school panel. volumetricPanel.relativePosition = new Vector2(0f, SchoolCalcY); applyButton.relativePosition = new Vector2(ApplyX, SchoolSaveY); // Get available school packs for this building. schoolPacks = SchoolData.instance.GetPacks(building); // Get current and default packs for this item. currentSchoolPack = (SchoolDataPack)SchoolData.instance.ActivePack(building); SchoolDataPack defaultSchoolPack = (SchoolDataPack)SchoolData.instance.CurrentDefaultPack(building); // Build school pack menu. schoolMenu.items = new string[schoolPacks.Length]; for (int i = 0; i < schoolMenu.items.Length; ++i) { schoolMenu.items[i] = schoolPacks[i].displayName; // Check for default name match, if (schoolPacks[i].name.Equals(defaultSchoolPack.name)) { schoolMenu.items[i] += Translations.Translate("RPR_PCK_DEF"); } // Set menu selection to current pack if it matches. if (schoolPacks[i].name.Equals(currentSchoolPack.name)) { schoolMenu.selectedIndex = i; // Force pack selection update. UpdateSchoolSelection(i); } } // Set multiplier value. multSlider.value = currentMult; schoolPanel.Show(); } else { // It's a school, but we're not using custom school settings, so use the non-school layout. volumetricPanel.relativePosition = new Vector2(0f, BaseCalcY); applyButton.relativePosition = new Vector2(ApplyX, BaseSaveY); schoolPanel.Hide(); } } else { // Not a school building - use non-school layout. currentSchoolPack = null; volumetricPanel.relativePosition = new Vector2(0f, BaseCalcY); applyButton.relativePosition = new Vector2(ApplyX, BaseSaveY); schoolPanel.Hide(); } } }
/// <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(); }