public static void RenderCloneGeometryImplementation(InstanceState instanceState, ref Matrix4x4 matrix4x, Vector3 deltaPosition, float deltaAngle, Vector3 center, bool followTerrain, RenderManager.CameraInfo cameraInfo) { BuildingInfo info = instanceState.Info.Prefab as BuildingInfo; Color color = GetColor(instanceState.instance.id.Building, info); Vector3 newPosition = matrix4x.MultiplyPoint(instanceState.position - center); newPosition.y = instanceState.position.y + deltaPosition.y; if (followTerrain) { newPosition.y = newPosition.y - instanceState.terrainHeight + TerrainManager.instance.SampleOriginalRawHeightSmooth(newPosition); } float newAngle = instanceState.angle + deltaAngle; info.m_buildingAI.RenderBuildGeometry(cameraInfo, newPosition, newAngle, 0); BuildingTool.RenderGeometry(cameraInfo, info, info.GetLength(), newPosition, newAngle, false, color); if (info.m_subBuildings != null && info.m_subBuildings.Length != 0) { Matrix4x4 subMatrix4x = default; subMatrix4x.SetTRS(newPosition, Quaternion.AngleAxis(newAngle * Mathf.Rad2Deg, Vector3.down), Vector3.one); for (int i = 0; i < info.m_subBuildings.Length; i++) { BuildingInfo buildingInfo2 = info.m_subBuildings[i].m_buildingInfo; Vector3 position = subMatrix4x.MultiplyPoint(info.m_subBuildings[i].m_position); float angle = info.m_subBuildings[i].m_angle * Mathf.Deg2Rad + newAngle; buildingInfo2.m_buildingAI.RenderBuildGeometry(cameraInfo, position, angle, 0); BuildingTool.RenderGeometry(cameraInfo, buildingInfo2, 0, position, angle, true, color); } } }
/// <summary> /// Returns the workplace breakdowns and visitor count for the given building prefab and level. /// </summary> /// <param name="buildingPrefab">Building prefab record</param> /// <param name="level">Building level</param> /// <returns>Workplace breakdowns and visitor count </returns> public override int[] Workplaces(BuildingInfo buildingPrefab, int level) { int[] array = LegacyAIUtils.GetOfficeArray(buildingPrefab, level); LegacyAIUtils.CalculateprefabWorkerVisit(buildingPrefab.GetWidth(), buildingPrefab.GetLength(), ref buildingPrefab, 10, ref array, out int[] output); return(output); }
/// <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> /// Returns the workplace breakdowns and visitor count for the given building prefab and level. /// </summary> /// <param name="buildingPrefab">Building prefab record</param> /// <param name="level">Building level</param> /// <returns>Workplace breakdowns and visitor count </returns> public override int[] Workplaces(BuildingInfo buildingPrefab, int level) { int[] array; int minWorkers; // Need to test if we're an extractor or not for this one. if (buildingPrefab.GetAI() is IndustrialExtractorAI) { array = LegacyAIUtils.GetExtractorArray(buildingPrefab); minWorkers = 3; } else { array = LegacyAIUtils.GetIndustryArray(buildingPrefab, level); minWorkers = 4; } LegacyAIUtils.CalculateprefabWorkerVisit(buildingPrefab.GetWidth(), buildingPrefab.GetLength(), ref buildingPrefab, minWorkers, ref array, out int[] output); return(output); }
/// <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> /// Interpret and apply RICO settings to a building prefab. /// </summary> /// <param name="buildingData">RICO building data to apply</param> /// <param name="prefab">The building prefab to be changed</param> internal void ConvertPrefab(RICOBuilding buildingData, BuildingInfo prefab) { // AI class for prefab init. string aiClass; if (prefab != null) { // Check eligibility for any growable assets. if (buildingData.growable) { // Growables can't have any dimension greater than 4. if (prefab.GetWidth() > 4 || prefab.GetLength() > 4) { buildingData.growable = false; Logging.Error("building '", prefab.name, "' can't be growable because it is too big"); } // Growables can't have net structures. if (prefab.m_paths != null && prefab.m_paths.Length != 0) { buildingData.growable = false; Logging.Error("building '", prefab.name, "' can't be growable because it contains network assets"); } } // Apply AI based on service. switch (buildingData.service) { // Dummy AI. case "dummy": // Get AI. DummyBuildingAI dummyAI = prefab.gameObject.AddComponent <DummyBuildingAI>(); // Use beautification ItemClass to avoid issues, and never make growable. InitializePrefab(prefab, dummyAI, "Beautification Item", false); // Final circular reference. prefab.m_buildingAI.m_info = prefab; // Dummy is a special case, and we're done here. return; // Residential AI. case "residential": // Get AI. GrowableResidentialAI residentialAI = buildingData.growable ? prefab.gameObject.AddComponent <GrowableResidentialAI>() : prefab.gameObject.AddComponent <PloppableResidentialAI>(); if (residentialAI == null) { throw new Exception("Ploppable RICO residential AI not found."); } // Assign basic parameters. residentialAI.m_ricoData = buildingData; residentialAI.m_constructionCost = buildingData.ConstructionCost; residentialAI.m_homeCount = buildingData.homeCount; // Determine AI class string according to subservice. switch (buildingData.subService) { case "low eco": // Apply eco service if GC installed, otherwise use normal low residential. if (Util.IsGCinstalled()) { aiClass = "Low Residential Eco - Level"; } else { aiClass = "Low Residential - Level"; } break; case "high eco": // Apply eco service if GC installed, otherwise use normal high residential. if (Util.IsGCinstalled()) { aiClass = "High Residential Eco - Level"; } else { aiClass = "High Residential - Level"; } break; case "high": // Stock standard high commercial. aiClass = "High Residential - Level"; break; default: // Fall back to low residential as default. aiClass = "Low Residential - Level"; // If invalid subservice, report. if (buildingData.subService != "low") { Logging.Message("Residential building ", buildingData.Name, " has invalid subservice ", buildingData.subService, "; reverting to low residential"); } break; } // Initialize the prefab. InitializePrefab(prefab, residentialAI, aiClass + buildingData.level, buildingData.growable); break; // Office AI. case "office": // Get AI. GrowableOfficeAI officeAI = buildingData.growable ? prefab.gameObject.AddComponent <GrowableOfficeAI>() : prefab.gameObject.AddComponent <PloppableOfficeAI>(); if (officeAI == null) { throw new Exception("Ploppable RICO Office AI not found."); } // Assign basic parameters. officeAI.m_ricoData = buildingData; officeAI.m_workplaceCount = buildingData.WorkplaceCount; officeAI.m_constructionCost = buildingData.ConstructionCost; // Check if this is an IT Cluster specialisation. // Determine AI class string according to subservice. if (buildingData.subService == "high tech") { // Apply IT cluster if GC installed, otherwise use Level 3 office. if (Util.IsGCinstalled()) { aiClass = "Office - Hightech"; } else { aiClass = "Office - Level3"; } } else { // Not IT cluster - boring old ordinary office. aiClass = "Office - Level" + buildingData.level; } // Initialize the prefab. InitializePrefab(prefab, officeAI, aiClass, buildingData.growable); break; // Industrial AI. case "industrial": // Get AI. GrowableIndustrialAI industrialAI = buildingData.growable ? prefab.gameObject.AddComponent <GrowableIndustrialAI>() : prefab.gameObject.AddComponent <PloppableIndustrialAI>(); if (industrialAI == null) { throw new Exception("Ploppable RICO Industrial AI not found."); } // Assign basic parameters. industrialAI.m_ricoData = buildingData; industrialAI.m_workplaceCount = buildingData.WorkplaceCount; industrialAI.m_constructionCost = buildingData.ConstructionCost; industrialAI.m_pollutionEnabled = buildingData.pollutionEnabled; // Determine AI class string according to subservice. // Check for valid subservice. if (IsValidIndSubServ(buildingData.subService)) { // Specialised industry. aiClass = ServiceName(buildingData.subService) + " - Processing"; } else { // Generic industry. aiClass = "Industrial - Level" + buildingData.level; } // Initialize the prefab. InitializePrefab(prefab, industrialAI, aiClass, buildingData.growable); break; // Extractor AI. case "extractor": // Get AI. GrowableExtractorAI extractorAI = buildingData.growable ? prefab.gameObject.AddComponent <GrowableExtractorAI>() : prefab.gameObject.AddComponent <PloppableExtractorAI>(); if (extractorAI == null) { throw new Exception("Ploppable RICO Extractor AI not found."); } // Assign basic parameters. extractorAI.m_ricoData = buildingData; extractorAI.m_workplaceCount = buildingData.WorkplaceCount; extractorAI.m_constructionCost = buildingData.ConstructionCost; extractorAI.m_pollutionEnabled = buildingData.pollutionEnabled; // Check that we have a valid industry subservice. if (IsValidIndSubServ(buildingData.subService)) { // Initialise the prefab. InitializePrefab(prefab, extractorAI, ServiceName(buildingData.subService) + " - Extractor", buildingData.growable); } else { Logging.Error("invalid industry subservice ", buildingData.subService, " for extractor ", buildingData.Name); } break; // Commercial AI. case "commercial": // Get AI. GrowableCommercialAI commercialAI = buildingData.growable ? prefab.gameObject.AddComponent <GrowableCommercialAI>() : prefab.gameObject.AddComponent <PloppableCommercialAI>(); if (commercialAI == null) { throw new Exception("Ploppable RICO Commercial AI not found."); } // Assign basic parameters. commercialAI.m_ricoData = buildingData; commercialAI.m_workplaceCount = buildingData.WorkplaceCount; commercialAI.m_constructionCost = buildingData.ConstructionCost; // Determine AI class string according to subservice. switch (buildingData.subService) { // Organic and Local Produce. case "eco": // Apply eco specialisation if GC installed, otherwise use Level 1 low commercial. if (Util.IsGCinstalled()) { // Eco commercial buildings only import food goods. commercialAI.m_incomingResource = TransferManager.TransferReason.Food; aiClass = "Eco Commercial"; } else { aiClass = "Low Commercial - Level1"; } break; // Tourism. case "tourist": // Apply tourist specialisation if AD installed, otherwise use Level 1 low commercial. if (Util.IsADinstalled()) { aiClass = "Tourist Commercial - Land"; } else { aiClass = "Low Commercial - Level1"; } break; // Leisure. case "leisure": // Apply leisure specialisation if AD installed, otherwise use Level 1 low commercial. if (Util.IsADinstalled()) { aiClass = "Leisure Commercial"; } else { aiClass = "Low Commercial - Level1"; } break; // Bog standard high commercial. case "high": aiClass = "High Commercial - Level" + buildingData.level; break; // Fall back to low commercial as default. default: aiClass = "Low Commercial - Level" + buildingData.level; // If invalid subservice, report. if (buildingData.subService != "low") { Logging.Message("Commercial building ", buildingData.Name, " has invalid subService ", buildingData.subService, "; reverting to low commercial."); } break; } // Initialize the prefab. InitializePrefab(prefab, commercialAI, aiClass, buildingData.growable); break; } } }
/// <summary> /// Interpret and apply RICO settings to a building prefab. /// </summary> /// <param name="buildingData">RICO building data to apply</param> /// <param name="prefab">The building prefab to be changed</param> internal void ConvertPrefab(RICOBuilding buildingData, BuildingInfo prefab) { if (prefab != null) { // Check eligibility for any growable assets. if (buildingData.growable) { // Growables can't have any dimension greater than 4. if (prefab.GetWidth() > 4 || prefab.GetLength() > 4) { buildingData.growable = false; Debugging.Message("building '" + prefab.name + "' can't be growable because it is too big"); } // Growables can't have net structures. if (prefab.m_paths != null && prefab.m_paths.Length != 0) { buildingData.growable = false; Debugging.Message("building '" + prefab.name + "' can't be growable because it contains network assets"); } } if (buildingData.service == "dummy") { var ai = prefab.gameObject.AddComponent <DummyBuildingAI>(); // Use beautification ItemClass to avoid issues, and never make growable. InitializePrefab(prefab, ai, "Beautification Item", false); // Final circular reference. prefab.m_buildingAI.m_info = prefab; } else if (buildingData.service == "residential") { var ai = buildingData.growable ? prefab.gameObject.AddComponent <GrowableResidentialAI>() : prefab.gameObject.AddComponent <PloppableResidentialAI>(); if (ai == null) { throw (new Exception("Residential-AI not found.")); } ai.m_ricoData = buildingData; ai.m_constructionCost = buildingData.constructionCost; ai.m_homeCount = buildingData.homeCount; if (buildingData.subService == "low eco") { // Apply eco service if GC installed, otherwise use normal low residential. if (Util.isGCinstalled()) { InitializePrefab(prefab, ai, "Low Residential Eco - Level" + buildingData.level, buildingData.growable); } else { InitializePrefab(prefab, ai, "Low Residential - Level" + buildingData.level, buildingData.growable); } } else if (buildingData.subService == "high eco") { // Apply eco service if GC installed, otherwise use normal high residential. if (Util.isGCinstalled()) { InitializePrefab(prefab, ai, "High Residential Eco - Level" + buildingData.level, buildingData.growable); } else { InitializePrefab(prefab, ai, "High Residential - Level" + buildingData.level, buildingData.growable); } } else if (buildingData.subService == "high") { // Stock standard high commercial. InitializePrefab(prefab, ai, "High Residential - Level" + buildingData.level, buildingData.growable); } else { // Fall back to low residential as default. InitializePrefab(prefab, ai, "Low Residential - Level" + buildingData.level, buildingData.growable); // If invalid subservice, report. if (buildingData.subService != "low") { Debugging.ErrorBuffer.AppendLine("Residential building " + buildingData.name + " has invalid subservice " + buildingData.subService + "; reverting to low residential."); } } } else if (buildingData.service == "office") { var ai = buildingData.growable ? prefab.gameObject.AddComponent <GrowableOfficeAI>() : prefab.gameObject.AddComponent <PloppableOfficeAI>(); if (ai == null) { throw (new Exception("Office-AI not found.")); } ai.m_ricoData = buildingData; ai.m_workplaceCount = buildingData.workplaceCount; ai.m_constructionCost = buildingData.constructionCost; if (buildingData.subService == "high tech") { // Apply IT cluster if GC installed, otherwise use Level 3 office. if (Util.isGCinstalled()) { InitializePrefab(prefab, ai, "Office - Hightech", buildingData.growable); } else { InitializePrefab(prefab, ai, "Office - Level3", buildingData.growable); } } else { // Not IT cluster - boring old ordinary office. InitializePrefab(prefab, ai, "Office - Level" + buildingData.level, buildingData.growable); } } else if (buildingData.service == "industrial") { var ai = buildingData.growable ? prefab.gameObject.AddComponent <GrowableIndustrialAI>() : prefab.gameObject.AddComponent <PloppableIndustrialAI>(); if (ai == null) { throw (new Exception("Industrial-AI not found.")); } ai.m_ricoData = buildingData; ai.m_workplaceCount = buildingData.workplaceCount; ai.m_constructionCost = buildingData.constructionCost; ai.m_pollutionEnabled = buildingData.pollutionEnabled; if (Util.industryServices.Contains(buildingData.subService)) { InitializePrefab(prefab, ai, Util.ucFirst(buildingData.subService) + " - Processing", buildingData.growable); } else { InitializePrefab(prefab, ai, "Industrial - Level" + buildingData.level, buildingData.growable); } } else if (buildingData.service == "extractor") { var ai = buildingData.growable ? prefab.gameObject.AddComponent <GrowableExtractorAI>() : prefab.gameObject.AddComponent <PloppableExtractorAI>(); if (ai == null) { throw (new Exception("Extractor-AI not found.")); } ai.m_ricoData = buildingData; ai.m_workplaceCount = buildingData.workplaceCount; ai.m_constructionCost = buildingData.constructionCost; ai.m_pollutionEnabled = buildingData.pollutionEnabled; if (Util.industryServices.Contains(buildingData.subService)) { InitializePrefab(prefab, ai, Util.ucFirst(buildingData.subService) + " - Extractor", buildingData.growable); } } else if (buildingData.service == "commercial") { var ai = buildingData.growable ? prefab.gameObject.AddComponent <GrowableCommercialAI>() : prefab.gameObject.AddComponent <PloppableCommercialAI>(); if (ai == null) { throw (new Exception("Commercial-AI not found.")); } ai.m_ricoData = buildingData; ai.m_workplaceCount = buildingData.workplaceCount; ai.m_constructionCost = buildingData.constructionCost; if (buildingData.subService == "eco") { // Apply eco specialisation if GC installed, otherwise use Level 1 low commercial. if (Util.isGCinstalled()) { // Eco commercial buildings only import food goods. ai.m_incomingResource = TransferManager.TransferReason.Food; InitializePrefab(prefab, ai, "Eco Commercial", buildingData.growable); } else { InitializePrefab(prefab, ai, "Low Commercial - Level1", buildingData.growable); } } else if (buildingData.subService == "tourist") { // Apply tourist specialisation if AD installed, otherwise use Level 1 low commercial. if (Util.isADinstalled()) { InitializePrefab(prefab, ai, "Tourist Commercial - Land", buildingData.growable); } else { InitializePrefab(prefab, ai, "Low Commercial - Level1", buildingData.growable); } } else if (buildingData.subService == "leisure") { // Apply leisure specialisation if AD installed, otherwise use Level 1 low commercial. if (Util.isADinstalled()) { InitializePrefab(prefab, ai, "Leisure Commercial", buildingData.growable); } else { InitializePrefab(prefab, ai, "Low Commercial - Level1", buildingData.growable); } } else if (buildingData.subService == "high") { // Bog standard high commercial. InitializePrefab(prefab, ai, "High Commercial - Level" + buildingData.level, buildingData.growable); } else { // Fall back to low commercial as default. InitializePrefab(prefab, ai, "Low Commercial - Level" + buildingData.level, buildingData.growable); // If invalid subservice, report. if (buildingData.subService != "low") { Debugging.ErrorBuffer.AppendLine("Commercial building " + buildingData.name + " has invalid subService " + buildingData.subService + "; reverting to low commercial."); } } } } }
// Detours public static void SimulationStep(ref ZoneBlock zoneBlock, ushort blockID) { // This is the decompiled ZoneBlock.SimulationStep() method // Segments which were changed are marked with "begin mod" and "end mod" if (Debugger.Enabled && debugCount < 10) { debugCount++; Debugger.LogFormat("Building Themes: Detoured ZoneBlock.SimulationStep was called. blockID: {0}, position: {1}.", blockID, zoneBlock.m_position); } ZoneManager zoneManager = Singleton <ZoneManager> .instance; int rowCount = zoneBlock.RowCount; float m_angle = zoneBlock.m_angle; Vector2 xDirection = new Vector2(Mathf.Cos(m_angle), Mathf.Sin(m_angle)) * 8f; Vector2 zDirection = new Vector2(xDirection.y, -xDirection.x); ulong num = zoneBlock.m_valid & ~(zoneBlock.m_occupied1 | zoneBlock.m_occupied2); int spawnpointRow = 0; ItemClass.Zone zone = ItemClass.Zone.Unzoned; int num3 = 0; while (num3 < 4 && zone == ItemClass.Zone.Unzoned) { spawnpointRow = Singleton <SimulationManager> .instance.m_randomizer.Int32((uint)rowCount); if ((num & 1uL << (spawnpointRow << 3)) != 0uL) { zone = zoneBlock.GetZone(0, spawnpointRow); } num3++; } DistrictManager instance2 = Singleton <DistrictManager> .instance; Vector3 m_position = (Vector3)zoneBlock.m_position; byte district = instance2.GetDistrict(m_position); int num4; switch (zone) { case ItemClass.Zone.ResidentialLow: num4 = zoneManager.m_actualResidentialDemand; num4 += instance2.m_districts.m_buffer[(int)district].CalculateResidentialLowDemandOffset(); break; case ItemClass.Zone.ResidentialHigh: num4 = zoneManager.m_actualResidentialDemand; num4 += instance2.m_districts.m_buffer[(int)district].CalculateResidentialHighDemandOffset(); break; case ItemClass.Zone.CommercialLow: num4 = zoneManager.m_actualCommercialDemand; num4 += instance2.m_districts.m_buffer[(int)district].CalculateCommercialLowDemandOffset(); break; case ItemClass.Zone.CommercialHigh: num4 = zoneManager.m_actualCommercialDemand; num4 += instance2.m_districts.m_buffer[(int)district].CalculateCommercialHighDemandOffset(); break; case ItemClass.Zone.Industrial: num4 = zoneManager.m_actualWorkplaceDemand; num4 += instance2.m_districts.m_buffer[(int)district].CalculateIndustrialDemandOffset(); break; case ItemClass.Zone.Office: num4 = zoneManager.m_actualWorkplaceDemand; num4 += instance2.m_districts.m_buffer[(int)district].CalculateOfficeDemandOffset(); break; default: return; } Vector2 a = VectorUtils.XZ(m_position); Vector2 vector3 = a - 3.5f * xDirection + ((float)spawnpointRow - 3.5f) * zDirection; int[] tmpXBuffer = zoneManager.m_tmpXBuffer; for (int i = 0; i < 13; i++) { tmpXBuffer[i] = 0; } Quad2 quad = default(Quad2); quad.a = a - 4f * xDirection + ((float)spawnpointRow - 10f) * zDirection; quad.b = a + 3f * xDirection + ((float)spawnpointRow - 10f) * zDirection; quad.c = a + 3f * xDirection + ((float)spawnpointRow + 2f) * zDirection; quad.d = a - 4f * xDirection + ((float)spawnpointRow + 2f) * zDirection; Vector2 vector4 = quad.Min(); Vector2 vector5 = quad.Max(); //begin mod int num5 = Mathf.Max((int)((vector4.x - 46f) / 64f + _zoneGridHalfResolution), 0); int num6 = Mathf.Max((int)((vector4.y - 46f) / 64f + _zoneGridHalfResolution), 0); int num7 = Mathf.Min((int)((vector5.x + 46f) / 64f + _zoneGridHalfResolution), _zoneGridResolution - 1); int num8 = Mathf.Min((int)((vector5.y + 46f) / 64f + _zoneGridHalfResolution), _zoneGridResolution - 1); //end mod for (int j = num6; j <= num8; j++) { for (int k = num5; k <= num7; k++) { //begin mod ushort num9 = zoneManager.m_zoneGrid[j * _zoneGridResolution + k]; //end mod int num10 = 0; while (num9 != 0) { Vector3 positionVar = zoneManager.m_blocks.m_buffer[(int)num9].m_position; float num11 = Mathf.Max(Mathf.Max(vector4.x - 46f - positionVar.x, vector4.y - 46f - positionVar.z), Mathf.Max(positionVar.x - vector5.x - 46f, positionVar.z - vector5.y - 46f)); if (num11 < 0f) { _CheckBlock.Invoke(zoneBlock, new object[] { zoneManager.m_blocks.m_buffer[(int)num9], tmpXBuffer, zone, vector3, xDirection, zDirection, quad }); } num9 = zoneManager.m_blocks.m_buffer[(int)num9].m_nextGridBlock; if (++num10 >= 49152) { CODebugBase <LogChannel> .Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace); break; } } } } for (int l = 0; l < 13; l++) { uint num12 = (uint)tmpXBuffer[l]; int num13 = 0; bool flag = (num12 & 196608u) == 196608u; bool flag2 = false; while ((num12 & 1u) != 0u) { num13++; flag2 = ((num12 & 65536u) != 0u); num12 >>= 1; } if (num13 == 5 || num13 == 6) { if (flag2) { num13 -= Singleton <SimulationManager> .instance.m_randomizer.Int32(2u) + 2; } else { num13 = 4; } num13 |= 131072; } else if (num13 == 7) { num13 = 4; num13 |= 131072; } if (flag) { num13 |= 65536; } tmpXBuffer[l] = num13; } int num14 = tmpXBuffer[6] & 65535; if (num14 == 0) { return; } bool flag3 = (bool)_IsGoodPlace.Invoke(zoneBlock, new object[] { vector3 }); if (Singleton <SimulationManager> .instance.m_randomizer.Int32(100u) >= num4) { if (flag3) { zoneManager.m_goodAreaFound[(int)zone] = 1024; } return; } if (!flag3 && zoneManager.m_goodAreaFound[(int)zone] > -1024) { if (zoneManager.m_goodAreaFound[(int)zone] == 0) { zoneManager.m_goodAreaFound[(int)zone] = -1; } return; } int num15 = 6; int num16 = 6; bool flag4 = true; while (true) { if (flag4) { while (num15 != 0) { if ((tmpXBuffer[num15 - 1] & 65535) != num14) { break; } num15--; } while (num16 != 12) { if ((tmpXBuffer[num16 + 1] & 65535) != num14) { break; } num16++; } } else { while (num15 != 0) { if ((tmpXBuffer[num15 - 1] & 65535) < num14) { break; } num15--; } while (num16 != 12) { if ((tmpXBuffer[num16 + 1] & 65535) < num14) { break; } num16++; } } int num17 = num15; int num18 = num16; while (num17 != 0) { if ((tmpXBuffer[num17 - 1] & 65535) < 2) { break; } num17--; } while (num18 != 12) { if ((tmpXBuffer[num18 + 1] & 65535) < 2) { break; } num18++; } bool flag5 = num17 != 0 && num17 == num15 - 1; bool flag6 = num18 != 12 && num18 == num16 + 1; if (flag5 && flag6) { if (num16 - num15 > 2) { break; } if (num14 <= 2) { if (!flag4) { goto Block_34; } } else { num14--; } } else if (flag5) { if (num16 - num15 > 1) { goto Block_36; } if (num14 <= 2) { if (!flag4) { goto Block_38; } } else { num14--; } } else if (flag6) { if (num16 - num15 > 1) { goto Block_40; } if (num14 <= 2) { if (!flag4) { goto Block_42; } } else { num14--; } } else { if (num15 != num16) { goto IL_884; } if (num14 <= 2) { if (!flag4) { goto Block_45; } } else { num14--; } } flag4 = false; } num15++; num16--; Block_34: goto IL_891; Block_36: num15++; Block_38: goto IL_891; Block_40: num16--; Block_42: Block_45: IL_884: IL_891: int num19; int num20; if (num14 == 1 && num16 - num15 >= 1) { num15 += Singleton <SimulationManager> .instance.m_randomizer.Int32((uint)(num16 - num15)); num16 = num15 + 1; num19 = num15 + Singleton <SimulationManager> .instance.m_randomizer.Int32(2u); num20 = num19; } else { do { num19 = num15; num20 = num16; if (num16 - num15 == 2) { if (Singleton <SimulationManager> .instance.m_randomizer.Int32(2u) == 0) { num20--; } else { num19++; } } else if (num16 - num15 == 3) { if (Singleton <SimulationManager> .instance.m_randomizer.Int32(2u) == 0) { num20 -= 2; } else { num19 += 2; } } else if (num16 - num15 == 4) { if (Singleton <SimulationManager> .instance.m_randomizer.Int32(2u) == 0) { num16 -= 2; num20 -= 3; } else { num15 += 2; num19 += 3; } } else if (num16 - num15 == 5) { if (Singleton <SimulationManager> .instance.m_randomizer.Int32(2u) == 0) { num16 -= 3; num20 -= 2; } else { num15 += 3; num19 += 2; } } else if (num16 - num15 >= 6) { if (num15 == 0 || num16 == 12) { if (num15 == 0) { num15 = 3; num19 = 2; } if (num16 == 12) { num16 = 9; num20 = 10; } } else if (Singleton <SimulationManager> .instance.m_randomizer.Int32(2u) == 0) { num16 = num15 + 3; num20 = num19 + 2; } else { num15 = num16 - 3; num19 = num20 - 2; } } }while (num16 - num15 > 3 || num20 - num19 > 3); } int depth_A = 4; int width_A = num16 - num15 + 1; BuildingInfo.ZoningMode zoningMode = BuildingInfo.ZoningMode.Straight; bool flag7 = true; for (int m = num15; m <= num16; m++) { depth_A = Mathf.Min(depth_A, tmpXBuffer[m] & 65535); if ((tmpXBuffer[m] & 131072) == 0) { flag7 = false; } } if (num16 > num15) { if ((tmpXBuffer[num15] & 65536) != 0) { zoningMode = BuildingInfo.ZoningMode.CornerLeft; num20 = num15 + num20 - num19; num19 = num15; } if ((tmpXBuffer[num16] & 65536) != 0 && (zoningMode != BuildingInfo.ZoningMode.CornerLeft || Singleton <SimulationManager> .instance.m_randomizer.Int32(2u) == 0)) { zoningMode = BuildingInfo.ZoningMode.CornerRight; num19 = num16 + num19 - num20; num20 = num16; } } int depth_B = 4; int width_B = num20 - num19 + 1; BuildingInfo.ZoningMode zoningMode2 = BuildingInfo.ZoningMode.Straight; bool flag8 = true; for (int n = num19; n <= num20; n++) { depth_B = Mathf.Min(depth_B, tmpXBuffer[n] & 65535); if ((tmpXBuffer[n] & 131072) == 0) { flag8 = false; } } if (num20 > num19) { if ((tmpXBuffer[num19] & 65536) != 0) { zoningMode2 = BuildingInfo.ZoningMode.CornerLeft; } if ((tmpXBuffer[num20] & 65536) != 0 && (zoningMode2 != BuildingInfo.ZoningMode.CornerLeft || Singleton <SimulationManager> .instance.m_randomizer.Int32(2u) == 0)) { zoningMode2 = BuildingInfo.ZoningMode.CornerRight; } } ItemClass.SubService subService = ItemClass.SubService.None; ItemClass.Level level = ItemClass.Level.Level1; ItemClass.Service service; switch (zone) { case ItemClass.Zone.ResidentialLow: service = ItemClass.Service.Residential; subService = ItemClass.SubService.ResidentialLow; break; case ItemClass.Zone.ResidentialHigh: service = ItemClass.Service.Residential; subService = ItemClass.SubService.ResidentialHigh; break; case ItemClass.Zone.CommercialLow: service = ItemClass.Service.Commercial; subService = ItemClass.SubService.CommercialLow; break; case ItemClass.Zone.CommercialHigh: service = ItemClass.Service.Commercial; subService = ItemClass.SubService.CommercialHigh; break; case ItemClass.Zone.Industrial: service = ItemClass.Service.Industrial; break; case ItemClass.Zone.Office: service = ItemClass.Service.Office; subService = ItemClass.SubService.None; break; default: return; } BuildingInfo buildingInfo = null; Vector3 vector6 = Vector3.zero; int num25_row = 0; int length = 0; int width = 0; BuildingInfo.ZoningMode zoningMode3 = BuildingInfo.ZoningMode.Straight; int num28 = 0; // begin mod int depth_alt = Mathf.Min(depth_A, 4); int width_alt = width_A; // end mod while (num28 < 8) // while (num28 < 6) { switch (num28) { // Corner cases case 0: if (zoningMode != BuildingInfo.ZoningMode.Straight) { num25_row = num15 + num16 + 1; length = depth_A; width = width_A; zoningMode3 = zoningMode; goto IL_D6A; } break; case 1: if (zoningMode2 != BuildingInfo.ZoningMode.Straight) { num25_row = num19 + num20 + 1; length = depth_B; width = width_B; zoningMode3 = zoningMode2; goto IL_D6A; } break; case 2: if (zoningMode != BuildingInfo.ZoningMode.Straight) { if (depth_A >= 4) { num25_row = num15 + num16 + 1; length = ((!flag7) ? 2 : 3); width = width_A; zoningMode3 = zoningMode; goto IL_D6A; } } break; case 3: if (zoningMode2 != BuildingInfo.ZoningMode.Straight) { if (depth_B >= 4) { num25_row = num19 + num20 + 1; length = ((!flag8) ? 2 : 3); width = width_B; zoningMode3 = zoningMode2; goto IL_D6A; } } break; // begin mod case 4: if (zoningMode != BuildingInfo.ZoningMode.Straight) { if (width_alt > 1) { width_alt--; } else if (depth_alt > 1) { depth_alt--; width_alt = width_A; } else { break; } if (width_alt == width_A) { num25_row = num15 + num16 + 1; } else { if (zoningMode == BuildingInfo.ZoningMode.CornerLeft) { num25_row = num15 + num16 + 1 - (width_A - width_alt); } else { num25_row = num15 + num16 + 1 + (width_A - width_alt); } } length = depth_alt; width = width_alt; zoningMode3 = zoningMode; num28--; goto IL_D6A; } break; // end mod // Straight cases case 5: num25_row = num15 + num16 + 1; length = depth_A; width = width_A; zoningMode3 = BuildingInfo.ZoningMode.Straight; goto IL_D6A; case 6: // begin mod // reset variables depth_alt = Mathf.Min(depth_A, 4); width_alt = width_A; // end mod //int width_B = num20 - num19 + 1; num25_row = num19 + num20 + 1; length = depth_B; width = width_B; zoningMode3 = BuildingInfo.ZoningMode.Straight; goto IL_D6A; // begin mod case 7: if (width_alt > 1) { width_alt--; } else { break; } if (width_alt == width_A) { num25_row = num15 + num16 + 1; } else if (width_A % 2 != width_alt % 2) { num25_row = num15 + num16; } else { num25_row = num15 + num16 + 1; } length = depth_alt; width = width_alt; zoningMode3 = BuildingInfo.ZoningMode.Straight; num28--; goto IL_D6A; // end mod default: goto IL_D6A; } IL_DF0: num28++; continue; IL_D6A: vector6 = m_position + VectorUtils.X_Y(((float)length * 0.5f - 4f) * xDirection + ((float)num25_row * 0.5f + (float)spawnpointRow - 10f) * zDirection); if (zone == ItemClass.Zone.Industrial) { ZoneBlock.GetIndustryType(vector6, out subService, out level); } else if (zone == ItemClass.Zone.CommercialLow || zone == ItemClass.Zone.CommercialHigh) { ZoneBlock.GetCommercialType(vector6, zone, width, length, out subService, out level); } byte district2 = instance2.GetDistrict(vector6); ushort style = instance2.m_districts.m_buffer[(int)district2].m_Style; // begin mod // Here we are calling a custom getRandomBuildingInfo method buildingInfo = BuildingManagerDetour.GetRandomBuildingInfo_Spawn(vector6, ref Singleton <SimulationManager> .instance.m_randomizer, service, subService, level, width, length, zoningMode3, style); // end mod if (buildingInfo != null) { // begin mod // If the depth of the found prefab is smaller than the one we were looking for, recalculate the size // This is done by checking the position of every prop // Plots only get shrinked when no assets are placed on the extra space // This is needed for themes which only contain small buildings (e.g. 1x2) // because those buildings would occupy more space than needed! if (buildingInfo.GetWidth() == width && buildingInfo.GetLength() != length) { // Calculate the z position of the furthest away prop float biggestPropPosZ = 0; if (buildingInfo.m_props != null) { foreach (var prop in buildingInfo.m_props) { if (prop == null) { continue; } biggestPropPosZ = Mathf.Max(biggestPropPosZ, buildingInfo.m_expandFrontYard ? prop.m_position.z : -prop.m_position.z); } } // Check if the furthest away prop is outside of the bounds of the prefab float occupiedExtraSpace = biggestPropPosZ - buildingInfo.GetLength() * 4; if (occupiedExtraSpace <= 0) { // No? Then shrink the plot to the prefab length so no space is wasted! length = buildingInfo.GetLength(); } else { // Yes? Shrink the plot so all props are in the bounds int newLength = buildingInfo.GetLength() + Mathf.CeilToInt(occupiedExtraSpace / 8); length = Mathf.Min(length, newLength); } vector6 = m_position + VectorUtils.X_Y(((float)length * 0.5f - 4f) * xDirection + ((float)num25_row * 0.5f + (float)spawnpointRow - 10f) * zDirection); } // This block handles Corner buildings. We always shrink them else if (buildingInfo.GetLength() == width && buildingInfo.GetWidth() != length) { length = buildingInfo.GetWidth(); vector6 = m_position + VectorUtils.X_Y(((float)length * 0.5f - 4f) * xDirection + ((float)num25_row * 0.5f + (float)spawnpointRow - 10f) * zDirection); } // end mod if (Debugger.Enabled) { Debugger.LogFormat("Found prefab: {5} - {0}, {1}, {2}, {3} x {4}", service, subService, level, width, length, buildingInfo.name); } break; } if (Debugger.Enabled) { } goto IL_DF0; } if (buildingInfo == null) { if (Debugger.Enabled) { Debugger.LogFormat("No prefab found: {0}, {1}, {2}, {3} x {4}", service, subService, level, width, length); } return; } float num29 = Singleton <TerrainManager> .instance.WaterLevel(VectorUtils.XZ(vector6)); if (num29 > vector6.y) { return; } float num30 = m_angle + 1.57079637f; if (zoningMode3 == BuildingInfo.ZoningMode.CornerLeft && buildingInfo.m_zoningMode == BuildingInfo.ZoningMode.CornerRight) { num30 -= 1.57079637f; length = width; } else if (zoningMode3 == BuildingInfo.ZoningMode.CornerRight && buildingInfo.m_zoningMode == BuildingInfo.ZoningMode.CornerLeft) { num30 += 1.57079637f; length = width; } ushort num31; if (Singleton <BuildingManager> .instance.CreateBuilding(out num31, ref Singleton <SimulationManager> .instance.m_randomizer, buildingInfo, vector6, num30, length, Singleton <SimulationManager> .instance.m_currentBuildIndex)) { Singleton <SimulationManager> .instance.m_currentBuildIndex += 1u; switch (service) { case ItemClass.Service.Residential: zoneManager.m_actualResidentialDemand = Mathf.Max(0, zoneManager.m_actualResidentialDemand - 5); break; case ItemClass.Service.Commercial: zoneManager.m_actualCommercialDemand = Mathf.Max(0, zoneManager.m_actualCommercialDemand - 5); break; case ItemClass.Service.Industrial: zoneManager.m_actualWorkplaceDemand = Mathf.Max(0, zoneManager.m_actualWorkplaceDemand - 5); break; case ItemClass.Service.Office: zoneManager.m_actualWorkplaceDemand = Mathf.Max(0, zoneManager.m_actualWorkplaceDemand - 5); break; } switch (zone) { case ItemClass.Zone.ResidentialHigh: case ItemClass.Zone.CommercialHigh: { Building[] expr_FD7_cp_0 = Singleton <BuildingManager> .instance.m_buildings.m_buffer; ushort expr_FD7_cp_1 = num31; expr_FD7_cp_0[(int)expr_FD7_cp_1].m_flags = (expr_FD7_cp_0[(int)expr_FD7_cp_1].m_flags | Building.Flags.HighDensity); break; } } } zoneManager.m_goodAreaFound[(int)zone] = 1024; }
/// <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(); }
/// <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); }