/// <summary> /// Load settings from XML file. /// </summary> internal static void LoadSettings() { try { // Check to see if configuration file exists. if (File.Exists(ConfigFileName)) { // Read it. using (StreamReader reader = new StreamReader(ConfigFileName)) { XmlSerializer xmlSerializer = new XmlSerializer(typeof(XMLConfigurationFile)); if (!(xmlSerializer.Deserialize(reader) is XMLConfigurationFile configFile)) { Logging.Error("couldn't deserialize configuration file"); } else { // Deserialise population calculation packs. foreach (PopPackXML xmlPack in configFile.popPacks) { // Convert to volumetric pack. VolumetricPopPack volPack = new VolumetricPopPack() { name = xmlPack.name, displayName = xmlPack.name, service = xmlPack.service, version = (int)DataVersion.customOne, levels = new LevelData[xmlPack.calculationLevels.Count] }; // Iterate through each level in the xml and add to our volumetric pack. foreach (PopLevel calculationLevel in xmlPack.calculationLevels) { volPack.levels[calculationLevel.level] = new LevelData() { //floorHeight = calculationLevel.floorHeight, emptyArea = calculationLevel.emptyArea, emptyPercent = calculationLevel.emptyPercent, areaPer = calculationLevel.areaPer, multiFloorUnits = calculationLevel.multiLevel }; } // Add new pack to our dictionary. PopData.instance.AddCalculationPack(volPack); } // Deserialise floor calculation packs. foreach (FloorPackXML xmlPack in configFile.floorPacks) { // Convert to floor pack. FloorDataPack floorPack = new FloorDataPack() { name = xmlPack.name, displayName = xmlPack.name, version = (int)DataVersion.customOne, floorHeight = xmlPack.floorHeight, firstFloorMin = xmlPack.firstMin, firstFloorExtra = xmlPack.firstExtra, firstFloorEmpty = xmlPack.firstEmpty }; // Add new pack to our dictionary. FloorData.instance.AddCalculationPack(floorPack); } // Deserialise consumption records. DataMapping mapper = new DataMapping(); foreach (ConsumptionRecord consumptionRecord in configFile.consumption) { // Get relevant DataStore array for this record. int[][] dataArray = mapper.GetArray(consumptionRecord.service, consumptionRecord.subService); // Iterate through each consumption line and populate relevant DataStore fields. foreach (ConsumptionLine consumptionLine in consumptionRecord.levels) { int level = (int)consumptionLine.level; dataArray[level][DataStore.POWER] = consumptionLine.power; dataArray[level][DataStore.WATER] = consumptionLine.water; dataArray[level][DataStore.SEWAGE] = consumptionLine.sewage; dataArray[level][DataStore.GARBAGE] = consumptionLine.garbage; dataArray[level][DataStore.GROUND_POLLUTION] = consumptionLine.pollution; dataArray[level][DataStore.NOISE_POLLUTION] = consumptionLine.noise; dataArray[level][DataStore.MAIL] = consumptionLine.mail; dataArray[level][DataStore.INCOME] = consumptionLine.income; } } // Deserialise default pack lists. PopData.instance.DeserializeDefaults(configFile.popDefaults); FloorData.instance.DeserializeDefaults(configFile.floorDefaults); // Deserialise building pack lists. PopData.instance.DeserializeBuildings(configFile.buildings); FloorData.instance.DeserializeBuildings(configFile.buildings); SchoolData.instance.DeserializeBuildings(configFile.buildings); Multipliers.instance.DeserializeBuildings(configFile.buildings); // Deserialise building population overrides. PopData.instance.DeserializeOverrides(configFile.popOverrides); // Deserialize floor overrides. foreach (FloorCalcOverride floorOverride in configFile.floors) { FloorData.instance.SetOverride(floorOverride.prefab, new FloorDataPack { firstFloorMin = floorOverride.firstHeight, floorHeight = floorOverride.floorHeight }); } // Deserialise visit modes. RealisticVisitplaceCount.DeserializeVisits(configFile.visitorModes); // Deserialise commercial sales multipliers. GoodsUtils.DeserializeSalesMults(configFile.salesMults); // Deserialise office production multipliers. RealisticOfficeProduction.DeserializeProdMults(configFile.offProdMults); // Deserialize industrial production calculation modes. RealisticIndustrialProduction.DeserializeProds(configFile.indProdModes); RealisticExtractorProduction.DeserializeProds(configFile.extProdModes); // Deserialise commercial inventory caps. GoodsUtils.DeserializeInvCaps(configFile.comIndCaps); } } }
/// <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(); }