static bool Prefix(CommercialBuildingAI __instance, ItemClass.Level level, Randomizer r, int productionRate, out int electricityConsumption, out int waterConsumption, out int sewageAccumulation, out int garbageAccumulation, out int incomeAccumulation, out int mailAccumulation) { ItemClass item = __instance.m_info.m_class; int[] array = CommercialBuildingAIMod.GetArray(__instance.m_info, (int)level); electricityConsumption = array[DataStore.POWER]; waterConsumption = array[DataStore.WATER]; sewageAccumulation = array[DataStore.SEWAGE]; garbageAccumulation = array[DataStore.GARBAGE]; mailAccumulation = array[DataStore.MAIL]; int landVal = AI_Utils.GetLandValueIncomeComponent(r.seed); incomeAccumulation = array[DataStore.INCOME] + landVal; electricityConsumption = Mathf.Max(100, productionRate * electricityConsumption) / 100; waterConsumption = Mathf.Max(100, productionRate * waterConsumption) / 100; sewageAccumulation = Mathf.Max(100, productionRate * sewageAccumulation) / 100; garbageAccumulation = Mathf.Max(100, productionRate * garbageAccumulation) / 100; incomeAccumulation = productionRate * incomeAccumulation; mailAccumulation = Mathf.Max(100, productionRate * mailAccumulation) / 100; // Don't execute base method after this. return(false); }
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); }
static bool Prefix(ref int __result, CommercialBuildingAI __instance, ItemClass.Level level, Randomizer r, int width, int length) { ItemClass @class = __instance.m_info.m_class; int[] array = CommercialBuildingAIMod.GetArray(__instance.m_info, (int)level); // Original method return value. __result = Mathf.Max(100, width * length * array[DataStore.PRODUCTION]) / 100; // Don't execute base method after this. return(false); }
static bool Prefix(CommercialBuildingAI __instance, ItemClass.Level level, int productionRate, DistrictPolicies.CityPlanning cityPlanningPolicies, out int groundPollution, out int noisePollution) { ItemClass item = __instance.m_info.m_class; int[] array = CommercialBuildingAIMod.GetArray(__instance.m_info, (int)level); groundPollution = array[DataStore.GROUND_POLLUTION]; noisePollution = (productionRate * array[DataStore.NOISE_POLLUTION]) / 100; if (item.m_subService == ItemClass.SubService.CommercialLeisure) { if ((cityPlanningPolicies & DistrictPolicies.CityPlanning.NoLoudNoises) != DistrictPolicies.CityPlanning.None) { noisePollution /= 2; } } // Don't execute base method after this. return(false); }
/// <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); } }
static bool Prefix(CommercialBuildingAI __instance, ItemClass.Level level, Randomizer r, int width, int length, out int level0, out int level1, out int level2, out int level3) { ulong seed = r.seed; BuildingInfo item = __instance.m_info; PrefabEmployStruct output; // If not seen prefab, calculate if (!DataStore.prefabWorkerVisit.TryGetValue(item.gameObject.GetHashCode(), out output)) { int[] array = CommercialBuildingAIMod.GetArray(__instance.m_info, (int)level); AI_Utils.CalculateprefabWorkerVisit(width, length, ref item, 4, ref array, out output); DataStore.prefabWorkerVisit.Add(item.gameObject.GetHashCode(), output); } level0 = output.level0; level1 = output.level1; level2 = output.level2; level3 = output.level3; // Don't execute base method after this. return(false); }
/// <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; }