/// <summary>
        /// Postfix to attempt to catch issues where homecounts seem to be reset to zero for specific buildings.
        /// </summary>
        /// <param name="__instance">Original object instance reference</param>
        /// <param name="buildingID">Building instance ID</param>
        /// <param name="data">Building data</param>
        /// <param name="version">Version</param>
        private static void Postfix(PrivateBuildingAI __instance, ushort buildingID, ref Building data, uint version)
        {
            // Check to see if this is one of ours.
            if (__instance is GrowableResidentialAI)
            {
                // Check to see if no citizen units are set.
                if (data.m_citizenUnits == 0)
                {
                    // Uh oh...
                    Debugging.Message("no citizenUnits for building " + buildingID + " : " + data.Info.name + "; attempting reset");

                    // Backup resetOnLoad setting and force to true for reset attempt.
                    bool oldReset = ModSettings.resetOnLoad;
                    ModSettings.resetOnLoad = true;

                    // Attempt reset for this building.
                    Prefix(__instance, buildingID, ref data, version);

                    // Restore original resetOnLoad seeting.
                    ModSettings.resetOnLoad = oldReset;
                }
                else
                {
                    Debugging.Message("building " + buildingID + " : " + data.Info.name + " passed CitizenUnits check");
                }
            }
        }
        /// <summary>
        /// Forces a building to upgrade.
        /// </summary>
        /// <param name="buildingID">Building instance ID</param>
        internal static void TriggerLevelUp(ushort buildingID)
        {
            // Don't force upgrade if building is already upgrading.
            if (Singleton <BuildingManager> .instance.m_buildings.m_buffer[buildingID].m_flags.IsFlagSet(Building.Flags.Upgrading))
            {
                return;
            }

            // Don't force upgrade if building is already at maximum level.
            // Note GetMaxLevel is 1-based, m_level is 0-based, so +1 for difference.
            if (Singleton <BuildingManager> .instance.m_buildings.m_buffer[buildingID].m_level + 1 >= GetMaxLevel(buildingID))
            {
                return;
            }

            // Get building AI and force upgrade.
            PrivateBuildingAI buildingAI = Singleton <BuildingManager> .instance.m_buildings.m_buffer[buildingID].Info?.GetAI() as PrivateBuildingAI;

            if (buildingAI == null)
            {
                Logging.Error("couldn't get AI for building ", buildingID.ToString());
            }
            else
            {
                // Only upgrade if we've got a valid upgrade target.
                if (buildingAI.GetUpgradeInfo(buildingID, ref Singleton <BuildingManager> .instance.m_buildings.m_buffer[buildingID]) != null)
                {
                    buildingAI.StartUpgrading(buildingID, ref Singleton <BuildingManager> .instance.m_buildings.m_buffer[buildingID]);
                }
            }
        }
Exemple #3
0
        /// <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);
        }
 private void InitializePrefab(BuildingInfo prefab, PrivateBuildingAI ai, String aiClass)
 {
     prefab.m_buildingAI        = ai;
     ai.m_constructionTime      = 0;
     prefab.m_buildingAI.m_info = prefab;
     prefab.InitializePrefab();
     prefab.m_class = ItemClassCollection.FindClass(aiClass);
 }
        public static int HandleWorkers(PrivateBuildingAI thisAI, ushort buildingID, ref Building buildingData, ref Citizen.BehaviourData behaviour, ref int aliveWorkerCount, ref int totalWorkerCount, ref int workPlaceCount)
        {
            //Not messed with this code too much yet. Still requires cleaning up.

            int b = 0;
            int level0, level1, level2, level3;

            //Fix for crashing? Modification added for this statement
            if (thisAI != null)
            {
                GetWorkBehaviour(thisAI, buildingID, ref buildingData, ref behaviour, ref aliveWorkerCount, ref totalWorkerCount);
                thisAI.CalculateWorkplaceCount(new Randomizer((int)buildingID), buildingData.Width, buildingData.Length, out level0, out level1, out level2, out level3);
                workPlaceCount = level0 + level1 + level2 + level3;

                if ((int)buildingData.m_fireIntensity == 0)
                {
                    HandleWorkPlaces(thisAI, buildingID, ref buildingData, level0, level1, level2, level3, ref behaviour, aliveWorkerCount, totalWorkerCount);
                    if (aliveWorkerCount != 0 && workPlaceCount != 0)
                    {
                        int num = (behaviour.m_efficiencyAccumulation + aliveWorkerCount - 1) / aliveWorkerCount;
                        b = 2 * num - 200 * num / ((100 * aliveWorkerCount + workPlaceCount - 1) / workPlaceCount + 100);
                    }
                }

                Notification.Problem problems1 = Notification.RemoveProblems(buildingData.m_problems, Notification.Problem.NoWorkers | Notification.Problem.NoEducatedWorkers);
                int num1 = (level3 * 300 + level2 * 200 + level1 * 100) / (workPlaceCount + 1);
                int num2 = (behaviour.m_educated3Count * 300 + behaviour.m_educated2Count * 200 + behaviour.m_educated1Count * 100) / (aliveWorkerCount + 1);

                //Start of modification

                if (Chances.WorkHour())
                {
                    if (aliveWorkerCount < workPlaceCount >> 1)
                    {
                        buildingData.m_workerProblemTimer = (byte)Mathf.Min((int)byte.MaxValue, (int)buildingData.m_workerProblemTimer + 1);
                        if ((int)buildingData.m_workerProblemTimer >= 128)
                            problems1 = Notification.AddProblems(problems1, Notification.Problem.NoWorkers | Notification.Problem.MajorProblem);
                        else if ((int)buildingData.m_workerProblemTimer >= 64)
                            problems1 = Notification.AddProblems(problems1, Notification.Problem.NoWorkers);
                    }
                    else if (num2 < num1 - 50)
                    {
                        buildingData.m_workerProblemTimer = (byte)Mathf.Min((int)byte.MaxValue, (int)buildingData.m_workerProblemTimer + 1);
                        if ((int)buildingData.m_workerProblemTimer >= 128)
                            problems1 = Notification.AddProblems(problems1, Notification.Problem.NoEducatedWorkers | Notification.Problem.MajorProblem);
                        else if ((int)buildingData.m_workerProblemTimer >= 64)
                            problems1 = Notification.AddProblems(problems1, Notification.Problem.NoEducatedWorkers);
                    }
                    else
                        buildingData.m_workerProblemTimer = (byte)0;
                }

                //End of modification

                buildingData.m_problems = problems1;
            }
            return Mathf.Max(1, b);
        }
 /// <summary>
 /// Harmony Postfix patch to stop historical buildings from abandoning.
 /// </summary>
 /// <param name="__instance">Instance reference</param>
 /// <param name="buildingID">Building instance ID</param
 /// <param name="buildingData">Building instance data reference</param>
 public static void Postfix(PrivateBuildingAI __instance, ushort buildingID, ref Building buildingData)
 {
     // Check to see if we have no abandonement for any building set, or no abandonment historical and this is an historical building.
     if (ModSettings.noAbandonAny || (ModSettings.noAbandonHistorical && __instance.IsHistorical(buildingID, ref buildingData, out bool _)))
     {
         // It is - simply reset the major problem timer to avoid the 'abandonment' timeout.
         buildingData.m_majorProblemTimer = 0;
     }
 }
        /// <summary>
        /// Custom implementation of PrivateBuildingAI.BuildingUpgraded that takes into account that our levels can be upgraded OR downgraded; for use when current building level is below the set prefb leve.
        /// </summary>
        /// <param name="buildingAI">Building AI instance</param>
        /// <param name="buildingID">Building instance ID</param>
        /// <param name="data">Building data record</param>
        private static void CustomBuildingUpgraded(PrivateBuildingAI buildingAI, ushort buildingID, ref Building data)
        {
            buildingAI.CalculateWorkplaceCount((ItemClass.Level)data.m_level, new Randomizer(buildingID), data.Width, data.Length, out int level, out int level2, out int level3, out int level4);
            buildingAI.AdjustWorkplaceCount(buildingID, ref data, ref level, ref level2, ref level3, ref level4);
            int workCount  = level + level2 + level3 + level4;
            int homeCount  = buildingAI.CalculateHomeCount((ItemClass.Level)data.m_level, new Randomizer(buildingID), data.Width, data.Length);
            int visitCount = buildingAI.CalculateVisitplaceCount((ItemClass.Level)data.m_level, new Randomizer(buildingID), data.Width, data.Length);

            ReversePatches.EnsureCitizenUnits(buildingAI, buildingID, ref data, homeCount, workCount, visitCount, 0);
        }
        /// <summary>
        /// Harmony Prefix patch to catch any buildings trying to upgrade beyond their maximum permitted level.
        /// Note that this won't in itself solve any broken prefabs with illegal levels, as the game's BuildingUpgraded method will set their level to the maximum of the actual or prefab level.
        /// </summary>
        /// <param name="__instance">Instance reference</param>
        /// <param name="buildingID">Building instance ID</param>
        /// <param name="data">Building data</param>
        public static void Prefix(PrivateBuildingAI __instance, ushort buildingID, ref Building data)
        {
            byte maxLevel = LevelUtils.GetMaxLevel(buildingID);

            // Check against maxLevel (m_level is zero-based, maxLevel is 1-based, so >= to catch overflows).
            if (data.m_level >= maxLevel)
            {
                Logging.Error("prevented building ", buildingID.ToString(), " (", __instance.m_info.name, ") from upgrading to illegal level ", (data.m_level + 1).ToString(), "; setting to ", maxLevel.ToString());
                data.m_level = (byte)(maxLevel - 1);
            }
        }
        /// <summary>
        /// Updates household counts for all buildings in scene with the given prefab name.
        /// Can also remove all housholds, setting the total to zero.
        /// </summary>
        /// <param name="prefabName">Prefab name</param>
        /// <param name="removeAll">If true, all households will be removed (count set to 0)</param>
        private void UpdateHouseholds(string prefabName, int level = 0, bool removeAll = false)
        {
            int homeCount        = 0;
            int visitCount       = 0;
            int homeCountChanged = 0;

            // Get building manager instance.
            var instance = Singleton <BuildingManager> .instance;


            // Iterate through each building in the scene.
            for (ushort i = 0; i < instance.m_buildings.m_buffer.Length; i++)
            {
                // Check for matching name.
                if (instance.m_buildings.m_buffer[i].Info != null && instance.m_buildings.m_buffer[i].Info.name != null && instance.m_buildings.m_buffer[i].Info.name.Equals(prefabName))
                {
                    // Got a match!  Check level if applicable.
                    if (level > 0)
                    {
                        // m_level is one less than building.level.
                        byte newLevel = (byte)(level - 1);

                        if (instance.m_buildings.m_buffer[i].m_level != newLevel)
                        {
                            Debugging.Message("found building '" + prefabName + "' with level " + (instance.m_buildings.m_buffer[i].m_level + 1) + ", overriding to level " + level);
                            instance.m_buildings.m_buffer[i].m_level = newLevel;
                        }
                    }

                    // Update homecounts for any residential buildings.
                    PrivateBuildingAI thisAI = instance.m_buildings.m_buffer[i].Info.GetAI() as ResidentialBuildingAI;
                    if (thisAI != null)
                    {
                        // This is residential! If we're not removing all households, recalculate home and visit counts using AI method.
                        if (!removeAll)
                        {
                            homeCount  = thisAI.CalculateHomeCount((ItemClass.Level)instance.m_buildings.m_buffer[i].m_level, new Randomizer(i), instance.m_buildings.m_buffer[i].Width, instance.m_buildings.m_buffer[i].Length);
                            visitCount = thisAI.CalculateVisitplaceCount((ItemClass.Level)instance.m_buildings.m_buffer[i].m_level, new Randomizer(i), instance.m_buildings.m_buffer[i].Width, instance.m_buildings.m_buffer[i].Length);
                        }

                        // Apply changes via direct call to EnsureCitizenUnits prefix patch from this mod and increment counter.
                        RealisticCitizenUnits.EnsureCitizenUnits(ref thisAI, i, ref instance.m_buildings.m_buffer[i], homeCount, 0, visitCount, 0);
                        homeCountChanged++;
                    }

                    // Clear any problems.
                    instance.m_buildings.m_buffer[i].m_problems = 0;
                }
            }

            Debugging.Message("set household counts to " + homeCount + " for " + homeCountChanged + " '" + prefabName + "' buildings");
        }
Exemple #10
0
        /// <summary>
        /// Harmony pre-emptive Prefix patch to replace game workplace calculations with the mod's.
        /// </summary>
        /// <param name="__instance">Original AI instance reference</param>
        /// <param name="level">Building level</param>
        /// <param name="level0">Uneducated worker count output</param>
        /// <param name="level1">Educated worker count output</param>
        /// <param name="level2">Well-educated worker count output</param>
        /// <param name="level3">Highly-educated worker count output</param>
        /// <returns>Always false (never execute original method)</returns>
        public static bool Prefix(PrivateBuildingAI __instance, ItemClass.Level level, out int level0, out int level1, out int level2, out int level3)
        {
            // Get cached workplace count.
            int[] workplaces = PopData.instance.WorkplaceCache(__instance.m_info, (int)level);

            // Set return values.
            level0 = workplaces[0];
            level1 = workplaces[1];
            level2 = workplaces[2];
            level3 = workplaces[3];

            // Don't execute base method after this.
            return(false);
        }
Exemple #11
0
        /// <summary>
        /// Harmony Postfix patch to handle *reductions* in homecounts when a building upgrades.
        /// </summary>
        /// <param name="__instance">Instance reference</param>
        /// <param name="buildingID">Building instance ID</param>
        /// <param name="data">Building data</param>
        public static void Postfix(PrivateBuildingAI __instance, ushort buildingID, ref Building data)
        {
            // Only interested in residential builidngs.
            if (__instance is ResidentialBuildingAI)
            {
                // Recalculate homecount.
                int homeCount = (__instance.CalculateHomeCount((ItemClass.Level)data.m_level, new Randomizer(buildingID), data.Width, data.Length));

                Logging.Message("residential building ", buildingID.ToString(), " (", data.Info.name, ") upgraded to level ", (data.m_level + 1).ToString(), "; calculated homecount is ", homeCount.ToString());

                // Remove any extra households.
                CitizenUnitUtils.RemoveCitizenUnits(ref data, homeCount, 0, 0, false);
            }
        }
        /// <summary>
        /// Harmony Prefix patch to catch any buildings being loaded with levels above their maximum permitted level.
        /// Note that this won't in itself solve any broken prefabs with illegal levels, as the game's BuildingUpgraded method will set their level to the maximum of the actual or prefab level.
        /// </summary>
        /// <param name="__instance">Instance reference</param>
        /// <param name="buildingID">Building instance ID</param>
        /// <param name="data">Building data</param>
        /// <param name="version">Data version</param>
        public static void Prefix(PrivateBuildingAI __instance, ushort buildingID, ref Building data)
        {
            // Only do this if settings permit.
            if (ModSettings.loadLevelCheck)
            {
                byte maxLevel = LevelUtils.GetMaxLevel(buildingID);

                // Check against maxLevel (m_level is zero-based, maxLevel is 1-based, so >= to catch overflows).
                if (data.m_level >= maxLevel)
                {
                    Logging.Error("building ", buildingID.ToString(), " (", __instance.m_info.name, ") had illegal level ", (data.m_level + 1).ToString(), "; setting to ", maxLevel.ToString());
                    data.m_level = (byte)(maxLevel - 1);
                }
            }
        }
Exemple #13
0
        /// <summary>
        /// Harmony Postfix patch to skip 'gradual construction' for plopped RICO growables, and/or to apply the 'Make Historical' and/or 'Lock level' settings on building creation, accoriding to settings.
        /// </summary>
        /// <param name="__result">Original method result (unchanged)</param>
        /// <param name="info">BuildingInfo prefab for this building (unchanged)</param>
        /// <param name="position">Building position (ignored)</param>
        /// <param name="angle">Building rotation (ignored)</param>
        /// <param name="relocating">Building relocation (ignored)</param>
        /// <param name="needMoney">Is money needed (ignored)</param>
        /// <param name="fixedHeight">Fixed height (ignored)</param>
        internal static void Postfix(ref ushort __result, ref BuildingInfo info, Vector3 position, float angle, int relocating, bool needMoney, bool fixedHeight)
        {
            // Check that we have a valid building ID.
            if (__result == 0)
            {
                return;
            }

            // Get building AI.
            PrivateBuildingAI buildingAI = info.GetAI() as PrivateBuildingAI;

            // Only interested in private building AI.
            if (buildingAI != null)
            {
                // Check if AI is a RICO custom AI type.
                bool isRICO = RICOUtils.IsRICOAI(buildingAI);

                // Check if it's a RICO custom AI type.
                // Enable 'ploppable growables' if option is set.
                if ((ModSettings.plopOther && !isRICO) || (ModSettings.plopRico && isRICO))
                {
                    // Check to see if construction time is greater than zero.
                    if (buildingAI.m_constructionTime > 0)
                    {
                        Singleton <BuildingManager> .instance.m_buildings.m_buffer[__result].m_frame0.m_constructState = byte.MaxValue;
                        BuildingCompletedRev(buildingAI, __result, ref Singleton <BuildingManager> .instance.m_buildings.m_buffer[__result]);

                        // Have to do this manually as CommonBuildingAI.BuildingCompleted won't if construction time isn't zero.
                        Singleton <BuildingManager> .instance.UpdateBuildingRenderer(__result, updateGroup : true);
                    }
                }

                // Enable 'Make Historical' if option is set.
                if ((ModSettings.historicalOther && !isRICO) || (ModSettings.historicalRico && isRICO))
                {
                    info.m_buildingAI.SetHistorical(__result, ref Singleton <BuildingManager> .instance.m_buildings.m_buffer[__result], historical: true);
                }

                // Enable ABLC level lock if option is set and ABLC is running.
                if (ModUtils.ablcLockBuildingLevel != null && ((ModSettings.lockLevelOther && !isRICO) || (ModSettings.lockLevelRico && isRICO)))
                {
                    ModUtils.ablcLockBuildingLevel.Invoke(null, new object[] { __result, Singleton <BuildingManager> .instance.m_buildings.m_buffer[__result].m_level });
                }
            }
        }
        public static void InitializePrefab(BuildingInfo prefab, PrivateBuildingAI ai, String aiClass)
        {
            prefab.m_buildingAI        = ai;
            ai.m_constructionTime      = 30;
            prefab.m_buildingAI.m_info = prefab;

            try
            {
                prefab.InitializePrefab();
            }
            catch
            {
                Debug.Log("InitPrefab Failed" + prefab.name);
            }

            prefab.m_class          = ItemClassCollection.FindClass(aiClass);
            prefab.m_placementStyle = ItemClass.Placement.Manual;
            prefab.m_autoRemove     = true;
            //prefab.m_dontSpawnNormally = false;
        }
Exemple #15
0
        /// <summary>
        /// Postfix to attempt to catch issues where homecounts seem to be reset to zero for specific buildings.
        /// </summary>
        /// <param name="__instance">Original object instance reference</param>
        /// <param name="buildingID">Building instance ID</param>
        /// <param name="data">Building data</param>
        /// <param name="version">Version</param>
        public static void Postfix(PrivateBuildingAI __instance, ushort buildingID, ref Building data, uint version)
        {
            // Check to see if this is one of ours and that's not abandoned.
            if (__instance is GrowableResidentialAI && (data.m_flags & Building.Flags.Abandoned) == 0)
            {
                CitizenUnit[] citizenBuffer = Singleton <CitizenManager> .instance.m_units.m_buffer;

                int  population         = 0;
                uint currentCitizenUnit = data.m_citizenUnits;

                while (currentCitizenUnit != 0)
                {
                    ++population;
                    currentCitizenUnit = citizenBuffer[currentCitizenUnit].m_nextUnit;
                }

                // Check to see if no citizen units are set.
                if (population == 0)
                {
                    // Uh oh...
                    Logging.Error("no citizenUnits for building ", buildingID.ToString(), " : ", data.Info.name, "; attempting reset");

                    // Backup resetOnLoad setting and force to true for reset attempt.
                    bool oldReset = ModSettings.resetOnLoad;
                    ModSettings.resetOnLoad = true;

                    // Attempt reset for this building.
                    Prefix(__instance, buildingID, ref data, version);

                    // Restore original resetOnLoad seeting.
                    ModSettings.resetOnLoad = oldReset;
                }
                else
                {
                    Logging.Message("building ", buildingID.ToString(), " : ", data.Info.name, " passed CitizenUnits check with ", population.ToString(), " households");
                }
            }
        }
Exemple #16
0
 /// <summary>
 /// Harmony Postfix patch to handle *reductions* in homecounts when a building upgrades.
 /// </summary>
 /// <param name="buildingAI">Building AI instance</param>
 /// <param name="buildingID">Building instance ID</param>
 /// <param name="data">Building data</param>
 public static void Postfix(PrivateBuildingAI buildingAI, ushort buildingID, ref Building data) => BuildingUpgradedPatch.Postfix(buildingAI, buildingID, ref data);
 public static void GetWorkBehaviour(PrivateBuildingAI thisAI, ushort buildingID, ref Building buildingData, ref Citizen.BehaviourData behaviour, ref int aliveCount, ref int totalCount)
 {
     Debug.LogWarning("GetWorkBehaviour is not overridden!");
 }
 public static void SimulationStepActive(PrivateBuildingAI baseAI, ushort buildingID, ref Building buildingData, ref Building.Frame frameData)
 {
     Debug.LogWarning("SimulationStepActive is not overridden!");
 }
 public static void HandleWorkPlaces(PrivateBuildingAI thisAI, ushort buildingID, ref Building data, int workPlaces0, int workPlaces1, int workPlaces2, int workPlaces3, ref Citizen.BehaviourData behaviour, int aliveWorkerCount, int totalWorkerCount)
 {
     Debug.LogWarning("HandleWorkPlaces is not overridden!");
 }
 private static int GetConstructionTime(PrivateBuildingAI instance)
 {
     return(RealTimeAI?.GetConstructionTime() ?? 0);
 }
 public static void HandleWorkPlaces(PrivateBuildingAI thisAI, ushort buildingID, ref Building data, int workPlaces0, int workPlaces1, int workPlaces2, int workPlaces3, ref Citizen.BehaviourData behaviour, int aliveWorkerCount, int totalWorkerCount)
 {
     Debug.LogWarning("HandleWorkPlaces is not overridden!");
 }
 public static void GetWorkBehaviour(PrivateBuildingAI thisAI, ushort buildingID, ref Building buildingData, ref Citizen.BehaviourData behaviour, ref int aliveCount, ref int totalCount)
 {
     Debug.LogWarning("GetWorkBehaviour is not overridden!");
 }
 public static void SimulationStepActive(PrivateBuildingAI baseAI, ushort buildingID, ref Building buildingData, ref Building.Frame frameData)
 {
     Debug.LogWarning("SimulationStepActive is not overridden!");
 }
 /// <summary>
 /// Checks if a builing instance has a Ploppable RICO custom AI.
 /// </summary>
 /// <param name="buildingID">Building instance ID</param>
 /// <returns>True if this building has a Ploppable RICO custom AI, false otherwise</returns>
 internal static bool IsRICOAI(PrivateBuildingAI prefabAI) => prefabAI != null && (prefabAI is GrowableResidentialAI || prefabAI is GrowableCommercialAI || prefabAI is GrowableIndustrialAI || prefabAI is GrowableOfficeAI || prefabAI is GrowableExtractorAI);
Exemple #25
0
        public static void SimulationStepActive(CommercialBuildingAI thisAI, ushort buildingID, ref Building buildingData, ref Building.Frame frameData)
        {
            //This is a mess because I pulled it directly from the decompiled code and patched it up slightly.
            //It works though, and that's all I'm bothered about for now.

            if (thisAI)
            {
                DistrictManager               instance1            = Singleton <DistrictManager> .instance;
                byte                          district             = instance1.GetDistrict(buildingData.m_position);
                DistrictPolicies.Services     policies             = instance1.m_districts.m_buffer[(int)district].m_servicePolicies;
                DistrictPolicies.Taxation     taxationPolicies     = instance1.m_districts.m_buffer[(int)district].m_taxationPolicies;
                DistrictPolicies.CityPlanning cityPlanningPolicies = instance1.m_districts.m_buffer[(int)district].m_cityPlanningPolicies;
                instance1.m_districts.m_buffer[(int)district].m_servicePoliciesEffect |= policies & (DistrictPolicies.Services.PowerSaving | DistrictPolicies.Services.WaterSaving | DistrictPolicies.Services.SmokeDetectors | DistrictPolicies.Services.Recycling | DistrictPolicies.Services.RecreationalUse | DistrictPolicies.Services.ExtraInsulation | DistrictPolicies.Services.NoElectricity | DistrictPolicies.Services.OnlyElectricity | DistrictPolicies.Services.RecyclePlastic);
                switch (thisAI.m_info.m_class.m_subService)
                {
                case ItemClass.SubService.CommercialLow:
                    if ((taxationPolicies & (DistrictPolicies.Taxation.TaxRaiseComLow | DistrictPolicies.Taxation.TaxLowerComLow)) != (DistrictPolicies.Taxation.TaxRaiseComLow | DistrictPolicies.Taxation.TaxLowerComLow))
                    {
                        instance1.m_districts.m_buffer[(int)district].m_taxationPoliciesEffect |= taxationPolicies & (DistrictPolicies.Taxation.TaxRaiseComLow | DistrictPolicies.Taxation.TaxLowerComLow);
                    }
                    instance1.m_districts.m_buffer[(int)district].m_cityPlanningPoliciesEffect |= cityPlanningPolicies & DistrictPolicies.CityPlanning.SmallBusiness;
                    break;

                case ItemClass.SubService.CommercialHigh:
                    if ((taxationPolicies & (DistrictPolicies.Taxation.TaxRaiseComHigh | DistrictPolicies.Taxation.TaxLowerComHigh)) != (DistrictPolicies.Taxation.TaxRaiseComHigh | DistrictPolicies.Taxation.TaxLowerComHigh))
                    {
                        instance1.m_districts.m_buffer[(int)district].m_taxationPoliciesEffect |= taxationPolicies & (DistrictPolicies.Taxation.TaxRaiseComHigh | DistrictPolicies.Taxation.TaxLowerComHigh);
                    }
                    instance1.m_districts.m_buffer[(int)district].m_cityPlanningPoliciesEffect |= cityPlanningPolicies & DistrictPolicies.CityPlanning.BigBusiness;
                    break;

                case ItemClass.SubService.CommercialLeisure:
                    instance1.m_districts.m_buffer[(int)district].m_taxationPoliciesEffect     |= taxationPolicies & DistrictPolicies.Taxation.DontTaxLeisure;
                    instance1.m_districts.m_buffer[(int)district].m_cityPlanningPoliciesEffect |= cityPlanningPolicies & DistrictPolicies.CityPlanning.NoLoudNoises;
                    break;

                case ItemClass.SubService.CommercialTourist:
                    instance1.m_districts.m_buffer[(int)district].m_cityPlanningPoliciesEffect |= cityPlanningPolicies & DistrictPolicies.CityPlanning.LightningRods;
                    break;

                case ItemClass.SubService.CommercialEco:
                    instance1.m_districts.m_buffer[(int)district].m_cityPlanningPoliciesEffect |= cityPlanningPolicies;
                    break;
                }
                Citizen.BehaviourData behaviour = new Citizen.BehaviourData();
                int aliveWorkerCount            = 0;
                int totalWorkerCount            = 0;
                int workPlaceCount = 0;
                int num1           = NewPrivateBuildingAI.HandleWorkers(thisAI, buildingID, ref buildingData, ref behaviour, ref aliveWorkerCount, ref totalWorkerCount, ref workPlaceCount);

                if ((buildingData.m_flags & Building.Flags.Evacuating) != Building.Flags.None)
                {
                    num1 = 0;
                }

                int width      = buildingData.Width;
                int length     = buildingData.Length;
                int num2       = MaxIncomingLoadSize(thisAI);
                int aliveCount = 0;
                int totalCount = 0;
                GetVisitBehaviour(thisAI, buildingID, ref buildingData, ref behaviour, ref aliveCount, ref totalCount);
                int visitCount = thisAI.CalculateVisitplaceCount(new Randomizer((int)buildingID), width, length);
                int num3       = Mathf.Max(0, visitCount - totalCount);
                int a1         = visitCount * 500;
                int num4       = Mathf.Max(a1, num2 * 4);
                TransferManager.TransferReason incomingTransferReason = GetIncomingTransferReason(thisAI);
                TransferManager.TransferReason outgoingTransferReason = GetOutgoingTransferReason(thisAI, buildingID);
                if (num1 != 0)
                {
                    int num5 = num4;
                    if (incomingTransferReason != TransferManager.TransferReason.None)
                    {
                        num5 = Mathf.Min(num5, (int)buildingData.m_customBuffer1);
                    }
                    if (outgoingTransferReason != TransferManager.TransferReason.None)
                    {
                        num5 = Mathf.Min(num5, num4 - (int)buildingData.m_customBuffer2);
                    }
                    int num6 = Mathf.Max(0, Mathf.Min(num1, (num5 * 200 + num4 - 1) / num4));
                    int a2   = (visitCount * num6 + 9) / 10;
                    if (Singleton <SimulationManager> .instance.m_isNightTime)
                    {
                        a2 = a2 + 1 >> 1;
                    }
                    int num7 = Mathf.Max(0, Mathf.Min(a2, num5));
                    if (incomingTransferReason != TransferManager.TransferReason.None)
                    {
                        buildingData.m_customBuffer1 -= (ushort)num7;
                    }
                    if (outgoingTransferReason != TransferManager.TransferReason.None)
                    {
                        buildingData.m_customBuffer2 += (ushort)num7;
                    }
                    num1 = (num7 + 9) / 10;
                }
                int electricityConsumption;
                int waterConsumption;
                int sewageAccumulation;
                int garbageAccumulation;
                int incomeAccumulation;
                thisAI.GetConsumptionRates(new Randomizer((int)buildingID), num1, out electricityConsumption, out waterConsumption, out sewageAccumulation, out garbageAccumulation, out incomeAccumulation);
                int heatingConsumption = 0;
                if (electricityConsumption != 0 && instance1.IsPolicyLoaded(DistrictPolicies.Policies.ExtraInsulation))
                {
                    if ((policies & DistrictPolicies.Services.ExtraInsulation) != DistrictPolicies.Services.None)
                    {
                        heatingConsumption = Mathf.Max(1, electricityConsumption * 3 + 8 >> 4);
                        incomeAccumulation = incomeAccumulation * 95 / 100;
                    }
                    else
                    {
                        heatingConsumption = Mathf.Max(1, electricityConsumption + 2 >> 2);
                    }
                }
                if (garbageAccumulation != 0 && (policies & DistrictPolicies.Services.Recycling) != DistrictPolicies.Services.None)
                {
                    garbageAccumulation = Mathf.Max(1, garbageAccumulation * 85 / 100);
                    incomeAccumulation  = incomeAccumulation * 95 / 100;
                }
                int taxRate;
                switch (thisAI.m_info.m_class.m_subService)
                {
                case ItemClass.SubService.CommercialLeisure:
                    taxRate = (buildingData.m_flags & Building.Flags.HighDensity) == Building.Flags.None ? Singleton <EconomyManager> .instance.GetTaxRate(ItemClass.Service.Commercial, ItemClass.SubService.CommercialLow, thisAI.m_info.m_class.m_level, taxationPolicies) : Singleton <EconomyManager> .instance.GetTaxRate(ItemClass.Service.Commercial, ItemClass.SubService.CommercialHigh, thisAI.m_info.m_class.m_level, taxationPolicies);

                    if ((taxationPolicies & DistrictPolicies.Taxation.DontTaxLeisure) != DistrictPolicies.Taxation.None)
                    {
                        taxRate = 0;
                    }
                    if ((cityPlanningPolicies & DistrictPolicies.CityPlanning.NoLoudNoises) != DistrictPolicies.CityPlanning.None && Singleton <SimulationManager> .instance.m_isNightTime)
                    {
                        electricityConsumption = electricityConsumption + 1 >> 1;
                        waterConsumption       = waterConsumption + 1 >> 1;
                        sewageAccumulation     = sewageAccumulation + 1 >> 1;
                        garbageAccumulation    = garbageAccumulation + 1 >> 1;
                        incomeAccumulation     = 0;
                        break;
                    }
                    break;

                case ItemClass.SubService.CommercialTourist:
                    taxRate = (buildingData.m_flags & Building.Flags.HighDensity) == Building.Flags.None ? Singleton <EconomyManager> .instance.GetTaxRate(ItemClass.Service.Commercial, ItemClass.SubService.CommercialLow, thisAI.m_info.m_class.m_level, taxationPolicies) : Singleton <EconomyManager> .instance.GetTaxRate(ItemClass.Service.Commercial, ItemClass.SubService.CommercialHigh, thisAI.m_info.m_class.m_level, taxationPolicies);

                    break;

                default:
                    taxRate = Singleton <EconomyManager> .instance.GetTaxRate(thisAI.m_info.m_class, taxationPolicies);

                    break;
                }
                if (num1 != 0)
                {
                    int num5 = HandleCommonConsumption(thisAI, buildingID, ref buildingData, ref frameData, ref electricityConsumption, ref heatingConsumption, ref waterConsumption, ref sewageAccumulation, ref garbageAccumulation, policies);
                    num1 = (num1 * num5 + 99) / 100;
                    if (num1 != 0)
                    {
                        int amount1 = incomeAccumulation;
                        if (amount1 != 0)
                        {
                            if (thisAI.m_info.m_class.m_subService == ItemClass.SubService.CommercialLow)
                            {
                                if ((cityPlanningPolicies & DistrictPolicies.CityPlanning.SmallBusiness) != DistrictPolicies.CityPlanning.None)
                                {
                                    Singleton <EconomyManager> .instance.FetchResource(EconomyManager.Resource.PolicyCost, 12, thisAI.m_info.m_class);

                                    amount1 *= 2;
                                }
                            }
                            else if ((cityPlanningPolicies & DistrictPolicies.CityPlanning.BigBusiness) != DistrictPolicies.CityPlanning.None)
                            {
                                Singleton <EconomyManager> .instance.FetchResource(EconomyManager.Resource.PolicyCost, 25, thisAI.m_info.m_class);

                                amount1 *= 3;
                            }
                            if ((policies & DistrictPolicies.Services.RecreationalUse) != DistrictPolicies.Services.None)
                            {
                                amount1 = (amount1 * 105 + 99) / 100;
                            }
                            int num6 = Singleton <EconomyManager> .instance.AddPrivateIncome(amount1, ItemClass.Service.Commercial, thisAI.m_info.m_class.m_subService, thisAI.m_info.m_class.m_level, taxRate);

                            int amount2 = (behaviour.m_touristCount * num6 + (aliveCount >> 1)) / Mathf.Max(1, aliveCount);
                            int amount3 = Mathf.Max(0, num6 - amount2);
                            if (amount3 != 0)
                            {
                                Singleton <EconomyManager> .instance.AddResource(EconomyManager.Resource.CitizenIncome, amount3, thisAI.m_info.m_class);
                            }
                            if (amount2 != 0)
                            {
                                Singleton <EconomyManager> .instance.AddResource(EconomyManager.Resource.TourismIncome, amount2, thisAI.m_info.m_class);
                            }
                        }
                        int groundPollution;
                        int noisePollution;
                        thisAI.GetPollutionRates(num1, cityPlanningPolicies, out groundPollution, out noisePollution);
                        if (groundPollution != 0 && Singleton <SimulationManager> .instance.m_randomizer.Int32(3U) == 0)
                        {
                            Singleton <NaturalResourceManager> .instance.TryDumpResource(NaturalResourceManager.Resource.Pollution, groundPollution, groundPollution, buildingData.m_position, 60f);
                        }
                        if (noisePollution != 0)
                        {
                            Singleton <ImmaterialResourceManager> .instance.AddResource(ImmaterialResourceManager.Resource.NoisePollution, noisePollution, buildingData.m_position, 60f);
                        }
                        if (num5 < 100)
                        {
                            buildingData.m_flags |= Building.Flags.RateReduced;
                        }
                        else
                        {
                            buildingData.m_flags &= ~Building.Flags.RateReduced;
                        }
                        buildingData.m_flags |= Building.Flags.Active;
                    }
                    else
                    {
                        buildingData.m_flags &= ~(Building.Flags.RateReduced | Building.Flags.Active);
                    }
                }
                else
                {
                    electricityConsumption  = 0;
                    heatingConsumption      = 0;
                    waterConsumption        = 0;
                    sewageAccumulation      = 0;
                    garbageAccumulation     = 0;
                    buildingData.m_problems = Notification.RemoveProblems(buildingData.m_problems, Notification.Problem.Electricity | Notification.Problem.Water | Notification.Problem.Sewage | Notification.Problem.Flood | Notification.Problem.Heating);
                    buildingData.m_flags   &= ~(Building.Flags.RateReduced | Building.Flags.Active);
                }
                int   health    = 0;
                int   wellbeing = 0;
                float radius    = (float)(buildingData.Width + buildingData.Length) * 2.5f;
                if (behaviour.m_healthAccumulation != 0)
                {
                    if (aliveWorkerCount + aliveCount != 0)
                    {
                        health = (behaviour.m_healthAccumulation + (aliveWorkerCount + aliveCount >> 1)) / (aliveWorkerCount + aliveCount);
                    }
                    Singleton <ImmaterialResourceManager> .instance.AddResource(ImmaterialResourceManager.Resource.Health, behaviour.m_healthAccumulation, buildingData.m_position, radius);
                }
                if (behaviour.m_wellbeingAccumulation != 0)
                {
                    if (aliveWorkerCount + aliveCount != 0)
                    {
                        wellbeing = (behaviour.m_wellbeingAccumulation + (aliveWorkerCount + aliveCount >> 1)) / (aliveWorkerCount + aliveCount);
                    }
                    Singleton <ImmaterialResourceManager> .instance.AddResource(ImmaterialResourceManager.Resource.Wellbeing, behaviour.m_wellbeingAccumulation, buildingData.m_position, radius);
                }
                int num8 = Citizen.GetHappiness(health, wellbeing) * 15 / 100;
                int a3   = aliveWorkerCount * 20 / workPlaceCount;
                if ((buildingData.m_problems & Notification.Problem.MajorProblem) == Notification.Problem.None)
                {
                    num8 += 20;
                }
                if (buildingData.m_problems == Notification.Problem.None)
                {
                    num8 += 25;
                }
                int num9  = num8 + Mathf.Min(a3, (int)buildingData.m_customBuffer1 * a3 / num4) + (a3 - Mathf.Min(a3, (int)buildingData.m_customBuffer2 * a3 / num4));
                int num10 = (int)(8 - thisAI.m_info.m_class.m_level);
                int num11 = (int)(11 - thisAI.m_info.m_class.m_level);
                if (thisAI.m_info.m_class.m_subService == ItemClass.SubService.CommercialHigh)
                {
                    ++num10;
                    ++num11;
                }
                if (taxRate < num10)
                {
                    num9 += num10 - taxRate;
                }
                if (taxRate > num11)
                {
                    num9 -= taxRate - num11;
                }
                if (taxRate >= num11 + 4)
                {
                    if ((int)buildingData.m_taxProblemTimer != 0 || Singleton <SimulationManager> .instance.m_randomizer.Int32(32U) == 0)
                    {
                        int num5 = taxRate - num11 >> 2;
                        buildingData.m_taxProblemTimer = (byte)Mathf.Min((int)byte.MaxValue, (int)buildingData.m_taxProblemTimer + num5);
                        if ((int)buildingData.m_taxProblemTimer >= 96)
                        {
                            buildingData.m_problems = Notification.AddProblems(buildingData.m_problems, Notification.Problem.TaxesTooHigh | Notification.Problem.MajorProblem);
                        }
                        else if ((int)buildingData.m_taxProblemTimer >= 32)
                        {
                            buildingData.m_problems = Notification.AddProblems(buildingData.m_problems, Notification.Problem.TaxesTooHigh);
                        }
                    }
                }
                else
                {
                    buildingData.m_taxProblemTimer = (byte)Mathf.Max(0, (int)buildingData.m_taxProblemTimer - 1);
                    buildingData.m_problems        = Notification.RemoveProblems(buildingData.m_problems, Notification.Problem.TaxesTooHigh);
                }
                int entertainment;
                int attractiveness;
                GetAccumulation(thisAI, new Randomizer((int)buildingID), num1, taxRate, cityPlanningPolicies, taxationPolicies, out entertainment, out attractiveness);
                if (entertainment != 0)
                {
                    Singleton <ImmaterialResourceManager> .instance.AddResource(ImmaterialResourceManager.Resource.Entertainment, entertainment, buildingData.m_position, radius);
                }
                if (attractiveness != 0)
                {
                    Singleton <ImmaterialResourceManager> .instance.AddResource(ImmaterialResourceManager.Resource.Attractiveness, attractiveness);
                }
                int happiness = Mathf.Clamp(num9, 0, 100);
                buildingData.m_health       = (byte)health;
                buildingData.m_happiness    = (byte)happiness;
                buildingData.m_citizenCount = (byte)(aliveWorkerCount + aliveCount);
                HandleDead(thisAI, buildingID, ref buildingData, ref behaviour, totalWorkerCount + totalCount);
                int crimeAccumulation = behaviour.m_crimeAccumulation / 10;
                if (thisAI.m_info.m_class.m_subService == ItemClass.SubService.CommercialLeisure)
                {
                    crimeAccumulation = crimeAccumulation * 5 + 3 >> 2;
                }
                if ((policies & DistrictPolicies.Services.RecreationalUse) != DistrictPolicies.Services.None)
                {
                    crimeAccumulation = crimeAccumulation * 3 + 3 >> 2;
                }
                HandleCrime(thisAI, buildingID, ref buildingData, crimeAccumulation, (int)buildingData.m_citizenCount);
                int num12 = (int)buildingData.m_crimeBuffer;
                if (aliveWorkerCount != 0)
                {
                    Singleton <ImmaterialResourceManager> .instance.AddResource(ImmaterialResourceManager.Resource.Density, aliveWorkerCount, buildingData.m_position, radius);

                    int num5 = (behaviour.m_educated0Count * 100 + behaviour.m_educated1Count * 50 + behaviour.m_educated2Count * 30) / aliveWorkerCount + 50;
                    buildingData.m_fireHazard = (byte)num5;
                }
                else
                {
                    buildingData.m_fireHazard = (byte)0;
                }
                int crimeRate = (int)buildingData.m_citizenCount == 0 ? 0 : (num12 + ((int)buildingData.m_citizenCount >> 1)) / (int)buildingData.m_citizenCount;
                int count     = 0;
                int cargo     = 0;
                int capacity  = 0;
                int outside   = 0;
                if (incomingTransferReason != TransferManager.TransferReason.None)
                {
                    CalculateGuestVehicles(thisAI, buildingID, ref buildingData, incomingTransferReason, ref count, ref cargo, ref capacity, ref outside);
                    buildingData.m_tempImport = (byte)Mathf.Clamp(outside, (int)buildingData.m_tempImport, (int)byte.MaxValue);
                }
                buildingData.m_tempExport = (byte)Mathf.Clamp(behaviour.m_touristCount, (int)buildingData.m_tempExport, (int)byte.MaxValue);
                SimulationManager _simulationManager = Singleton <SimulationManager> .instance;
                if ((long)((_simulationManager.m_currentFrameIndex & 3840U) >> 8) == (long)((int)buildingID & 15) && (thisAI.m_info.m_class.m_subService == ItemClass.SubService.CommercialLow || thisAI.m_info.m_class.m_subService == ItemClass.SubService.CommercialHigh) && ((int)Singleton <ZoneManager> .instance.m_lastBuildIndex == (int)_simulationManager.m_currentBuildIndex && (buildingData.m_flags & Building.Flags.Upgrading) == Building.Flags.None))
                {
                    CheckBuildingLevel(thisAI, buildingID, ref buildingData, ref frameData, ref behaviour, aliveCount);
                }
                if ((buildingData.m_flags & (Building.Flags.Completed | Building.Flags.Upgrading)) == Building.Flags.None)
                {
                    return;
                }
                Notification.Problem problems1 = Notification.RemoveProblems(buildingData.m_problems, Notification.Problem.NoCustomers | Notification.Problem.NoGoods);

                //Begin edited section

                if ((int)buildingData.m_customBuffer2 > num4 - (a1 >> 1) && aliveCount <= visitCount >> 1)
                {
                    if (_simulationManager.m_currentDayTimeHour > 19 && _simulationManager.m_currentDayTimeHour < 20)
                    {
                        buildingData.m_outgoingProblemTimer = (byte)Mathf.Min(byte.MaxValue, buildingData.m_outgoingProblemTimer + 1);

                        if (buildingData.m_outgoingProblemTimer >= 192)
                        {
                            problems1 = Notification.AddProblems(problems1, Notification.Problem.NoCustomers | Notification.Problem.MajorProblem);
                        }
                        else if (buildingData.m_outgoingProblemTimer >= 128)
                        {
                            problems1 = Notification.AddProblems(problems1, Notification.Problem.NoCustomers);
                        }
                    }
                    else
                    {
                        buildingData.m_outgoingProblemTimer = 0;
                    }
                }
                else
                {
                    buildingData.m_outgoingProblemTimer = (byte)0;
                }

                if (!CityEventManager.instance.EventStartsWithin(3D) && !CityEventManager.instance.EventTakingPlace() && !CityEventManager.instance.EventJustEnded())
                {
                    if ((int)buildingData.m_customBuffer1 == 0)
                    {
                        buildingData.m_incomingProblemTimer = (byte)Mathf.Min((int)byte.MaxValue, (int)buildingData.m_incomingProblemTimer + 1);
                        problems1 = (int)buildingData.m_incomingProblemTimer >= 64 ? Notification.AddProblems(problems1, Notification.Problem.NoGoods | Notification.Problem.MajorProblem) : Notification.AddProblems(problems1, Notification.Problem.NoGoods);
                    }
                    else
                    {
                        buildingData.m_incomingProblemTimer = (byte)0;
                    }

                    float currentHour = _simulationManager.m_currentDayTimeHour;

                    //Artifically shop at night to keep industry happy. Will give the effect of industry stocking up commercial over night.
                    //Note: ModifyMaterialBuffer is expensive, so if there's any performance impact with the mod now, it'll most likely be this.
                    if ((currentHour > 20f || currentHour < 4f))
                    {
                        if (_simulationManager.m_randomizer.Int32(80) < 2)
                        {
                            //Simulate 2 people buying things
                            int amount = -200;
                            thisAI.ModifyMaterialBuffer(buildingID, ref buildingData, TransferManager.TransferReason.Shopping, ref amount);
                        }
                    }
                    else if (Experiments.ExperimentsToggle.AllowActiveCommercialFix && _simulationManager.m_randomizer.Int32(40) < 5) //Added in as a potential fix to random inactive buildings. Lack of customers still shuts down commercial.
                    {
                        int amount = -50;
                        thisAI.ModifyMaterialBuffer(buildingID, ref buildingData, TransferManager.TransferReason.Shopping, ref amount);
                    }
                }
                else
                {
                    buildingData.m_incomingProblemTimer = 0;
                }

                //End edited section

                buildingData.m_problems = problems1;
                instance1.m_districts.m_buffer[(int)district].AddCommercialData(ref behaviour, health, happiness, crimeRate, workPlaceCount, aliveWorkerCount, Mathf.Max(0, workPlaceCount - totalWorkerCount), visitCount, aliveCount, num3, (int)thisAI.m_info.m_class.m_level, electricityConsumption, heatingConsumption, waterConsumption, sewageAccumulation, garbageAccumulation, incomeAccumulation, Mathf.Min(100, (int)buildingData.m_garbageBuffer / 50), (int)buildingData.m_waterPollution * 100 / (int)byte.MaxValue, (int)buildingData.m_finalImport, (int)buildingData.m_finalExport, thisAI.m_info.m_class.m_subService);
                if ((int)buildingData.m_fireIntensity == 0 && incomingTransferReason != TransferManager.TransferReason.None)
                {
                    int num5 = num4 - (int)buildingData.m_customBuffer1 - capacity - (num2 >> 1);
                    if (num5 >= 0)
                    {
                        Singleton <TransferManager> .instance.AddIncomingOffer(incomingTransferReason, new TransferManager.TransferOffer()
                        {
                            Priority = num5 * 8 / num2,
                            Building = buildingID,
                            Position = buildingData.m_position,
                            Amount   = 1,
                            Active   = false
                        });
                    }
                }
                if ((int)buildingData.m_fireIntensity == 0 && outgoingTransferReason != TransferManager.TransferReason.None)
                {
                    int num5 = (int)buildingData.m_customBuffer2 - aliveCount * 100;
                    if (num5 >= 100 && num3 > 0)
                    {
                        Singleton <TransferManager> .instance.AddOutgoingOffer(outgoingTransferReason, new TransferManager.TransferOffer()
                        {
                            Priority = Mathf.Max(1, num5 * 8 / num4),
                            Building = buildingID,
                            Position = buildingData.m_position,
                            Amount   = Mathf.Min(num5 / 100, num3),
                            Active   = false
                        });
                    }
                }

                PrivateBuildingAI baseAI = thisAI as PrivateBuildingAI; //Because we don't have access to base here.

                if (baseAI != null)
                {
                    NewPrivateBuildingAI.SimulationStepActive(baseAI, buildingID, ref buildingData, ref frameData);
                }

                HandleFire(thisAI, buildingID, ref buildingData, ref frameData, policies);
            }
            else
            {
                Debug.LogError("Commercial building " + buildingID + " has no AI! This could have been bad.");
            }
        }
        /// <summary>
        /// Upgrades/downgrades the selected building to the given level, if possible.
        /// </summary>
        /// <param name="buildingID">Building instance ID</param>
        /// <param name="targetLevel">Level to upgrade/downgrade to</param>
        internal static void ForceLevel(ushort buildingID, byte targetLevel)
        {
            // BuildingInfo to change to, if this building isn't historical.
            BuildingInfo targetInfo = null;

            // References.
            BuildingManager buildingManager = Singleton <BuildingManager> .instance;

            Building[]        buildingBuffer = buildingManager.m_buildings.m_buffer;
            BuildingInfo      buildingInfo   = buildingBuffer[buildingID].Info;
            PrivateBuildingAI buildingAI     = buildingInfo?.GetAI() as PrivateBuildingAI;

            if (buildingInfo == null || buildingAI == null)
            {
                // If something went wrong, abort.
                Logging.Error("couldn't get existing building info");
                return;
            }

            // Check to see if this is historical or not, or is a RICO ploppable.
            bool isHistorical = buildingAI.IsHistorical(buildingID, ref Singleton <BuildingManager> .instance.m_buildings.m_buffer[buildingID], out bool _) || ModUtils.CheckRICOPloppable(buildingInfo);

            // Get target prefab (if needed, i.e. not historical or RICO ploppable).
            if (!isHistorical)
            {
                // Get upgrade/downgrade building target.
                targetInfo = GetTargetInfo(buildingID, targetLevel);
                if (targetInfo == null)
                {
                    // If we failed, don't do anything more.
                    return;
                }
            }

            // If we have a valid upgrade/downgrade target, proceed.
            if (isHistorical || targetInfo != null)
            {
                // Apply target level to our building and cancel all level-up progress.
                buildingBuffer[buildingID].m_level           = targetLevel;
                buildingBuffer[buildingID].m_levelUpProgress = 0;

                // Apply our upgrade/downgrade target if not historical
                if (!isHistorical)
                {
                    buildingManager.UpdateBuildingInfo(buildingID, targetInfo);
                }

                // Post-downgrade processing to update instance values - call game method if new level is equal to or greater than info base level, otherwise use custom method.
                BuildingInfo newInfo = targetInfo ?? buildingInfo;
                if (newInfo.GetAI() is PrivateBuildingAI newAI)
                {
                    if (targetLevel < (byte)newInfo.GetClassLevel())
                    {
                        // New level is less than info base level; call custom method.
                        CustomBuildingUpgraded(newAI, buildingID, ref buildingBuffer[buildingID]);
                    }
                    else
                    {
                        // New level is equal to or greater than info base level; call game method.
                        newAI.BuildingUpgraded(buildingID, ref buildingBuffer[buildingID]);
                    }
                }
            }
        }
        public static int HandleWorkers(PrivateBuildingAI thisAI, ushort buildingID, ref Building buildingData, ref Citizen.BehaviourData behaviour, ref int aliveWorkerCount, ref int totalWorkerCount, ref int workPlaceCount)
        {
            //Not messed with this code too much yet. Still requires cleaning up.

            int b = 0;
            int level0, level1, level2, level3;

            //Fix for crashing? Modification added for this statement
            if (thisAI != null)
            {
                GetWorkBehaviour(thisAI, buildingID, ref buildingData, ref behaviour, ref aliveWorkerCount, ref totalWorkerCount);
                thisAI.CalculateWorkplaceCount(new Randomizer((int)buildingID), buildingData.Width, buildingData.Length, out level0, out level1, out level2, out level3);
                workPlaceCount = level0 + level1 + level2 + level3;

                if ((int)buildingData.m_fireIntensity == 0)
                {
                    HandleWorkPlaces(thisAI, buildingID, ref buildingData, level0, level1, level2, level3, ref behaviour, aliveWorkerCount, totalWorkerCount);
                    if (aliveWorkerCount != 0 && workPlaceCount != 0)
                    {
                        int num = (behaviour.m_efficiencyAccumulation + aliveWorkerCount - 1) / aliveWorkerCount;
                        b = 2 * num - 200 * num / ((100 * aliveWorkerCount + workPlaceCount - 1) / workPlaceCount + 100);
                    }
                }

                Notification.Problem problems1 = Notification.RemoveProblems(buildingData.m_problems, Notification.Problem.NoWorkers | Notification.Problem.NoEducatedWorkers);
                int num1 = (level3 * 300 + level2 * 200 + level1 * 100) / (workPlaceCount + 1);
                int num2 = (behaviour.m_educated3Count * 300 + behaviour.m_educated2Count * 200 + behaviour.m_educated1Count * 100) / (aliveWorkerCount + 1);

                //Start of modification

                if (Chances.WorkHour())
                {
                    if (aliveWorkerCount < workPlaceCount >> 1)
                    {
                        buildingData.m_workerProblemTimer = (byte)Mathf.Min((int)byte.MaxValue, (int)buildingData.m_workerProblemTimer + 1);
                        if ((int)buildingData.m_workerProblemTimer >= 128)
                        {
                            problems1 = Notification.AddProblems(problems1, Notification.Problem.NoWorkers | Notification.Problem.MajorProblem);
                        }
                        else if ((int)buildingData.m_workerProblemTimer >= 64)
                        {
                            problems1 = Notification.AddProblems(problems1, Notification.Problem.NoWorkers);
                        }
                    }
                    else if (num2 < num1 - 50)
                    {
                        buildingData.m_workerProblemTimer = (byte)Mathf.Min((int)byte.MaxValue, (int)buildingData.m_workerProblemTimer + 1);
                        if ((int)buildingData.m_workerProblemTimer >= 128)
                        {
                            problems1 = Notification.AddProblems(problems1, Notification.Problem.NoEducatedWorkers | Notification.Problem.MajorProblem);
                        }
                        else if ((int)buildingData.m_workerProblemTimer >= 64)
                        {
                            problems1 = Notification.AddProblems(problems1, Notification.Problem.NoEducatedWorkers);
                        }
                    }
                }

                //End of modification

                buildingData.m_problems = problems1;
            }
            return(Mathf.Max(1, b));
        }
        internal static void EnsureCitizenUnits(ref PrivateBuildingAI __instance, ushort buildingID, ref Building data, int homeCount, int workCount, int visitCount, int studentCount)
        {
            int totalWorkCount  = (workCount + 4) / 5;
            int totalVisitCount = (visitCount + 4) / 5;
            int totalHomeCount  = homeCount;

            int[] workersRequired = new int[] { 0, 0, 0, 0 };


            if ((data.m_flags & (Building.Flags.Abandoned | Building.Flags.BurnedDown)) == Building.Flags.None)
            {
                Citizen.Wealth wealthLevel = Citizen.GetWealthLevel(__instance.m_info.m_class.m_level);
                uint           num         = 0u;
                uint           num2        = data.m_citizenUnits;
                int            num3        = 0;
                while (num2 != 0u)
                {
                    CitizenUnit.Flags flags = citizenUnitArray[(int)((UIntPtr)num2)].m_flags;
                    if ((ushort)(flags & CitizenUnit.Flags.Home) != 0)
                    {
                        citizenUnitArray[(int)((UIntPtr)num2)].SetWealthLevel(wealthLevel);
                        homeCount--;
                    }
                    if ((ushort)(flags & CitizenUnit.Flags.Work) != 0)
                    {
                        workCount -= 5;
                        for (int i = 0; i < 5; i++)
                        {
                            uint citizen = citizenUnitArray[(int)((UIntPtr)num2)].GetCitizen(i);
                            if (citizen != 0u)
                            {
                                // Tick off education to see what is there
                                workersRequired[(int)citizenArray[(int)((UIntPtr)citizen)].EducationLevel]--;
                            }
                        }
                    }
                    if ((ushort)(flags & CitizenUnit.Flags.Visit) != 0)
                    {
                        visitCount -= 5;
                    }
                    if ((ushort)(flags & CitizenUnit.Flags.Student) != 0)
                    {
                        studentCount -= 5;
                    }
                    num  = num2;
                    num2 = citizenUnitArray[(int)((UIntPtr)num2)].m_nextUnit;
                    if (++num3 > 524288)
                    {
                        CODebugBase <LogChannel> .Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace);

                        break;
                    }
                } // end while

                /*
                 *              homeCount = Mathf.Max(0, homeCount);
                 *              workCount = Mathf.Max(0, workCount);
                 */
                visitCount   = Mathf.Max(0, visitCount);
                studentCount = Mathf.Max(0, studentCount);

                if (homeCount > 0 || workCount > 0 || visitCount > 0 || studentCount > 0)
                {
                    uint num4 = 0u;
                    if (citizenManager.CreateUnits(out num4, ref Singleton <SimulationManager> .instance.m_randomizer, buildingID, 0, homeCount, workCount, visitCount, 0, studentCount))
                    {
                        if (num != 0u)
                        {
                            citizenUnitArray[(int)((UIntPtr)num)].m_nextUnit = num4;
                        }
                        else
                        {
                            data.m_citizenUnits = num4;
                        }
                    }
                }


                // Stop incoming offers to get HandleWorkers() to start fresh
                TransferManager.TransferOffer offer = default(TransferManager.TransferOffer);
                offer.Building = buildingID;
                Singleton <TransferManager> .instance.RemoveIncomingOffer(TransferManager.TransferReason.Worker0, offer);

                Singleton <TransferManager> .instance.RemoveIncomingOffer(TransferManager.TransferReason.Worker1, offer);

                Singleton <TransferManager> .instance.RemoveIncomingOffer(TransferManager.TransferReason.Worker2, offer);

                Singleton <TransferManager> .instance.RemoveIncomingOffer(TransferManager.TransferReason.Worker3, offer);

                int             worker0 = 0;
                int             worker1 = 0;
                int             worker2 = 0;
                int             worker3 = 0;
                ItemClass.Level level   = ((PrivateBuildingAI)data.Info.GetAI()).m_info.m_class.m_level;
                ((PrivateBuildingAI)data.Info.GetAI()).CalculateWorkplaceCount(level, new Randomizer((int)buildingID), data.Width, data.Length,
                                                                               out worker0, out worker1, out worker2, out worker3);

                // Update the workers required once figuring out how many are needed by the new building
                workersRequired[0] += worker0;
                workersRequired[1] += worker1;
                workersRequired[2] += worker2;
                workersRequired[3] += worker3;

                if (workCount < 0)
                {
                    RemoveWorkerBuilding(buildingID, ref data, totalWorkCount);
                }
                else if (homeCount < 0)
                {
                    RemoveHouseHold(buildingID, ref data, totalHomeCount);
                }

                /*
                 *  if (visitCount < 0)
                 *  {
                 *      RemoveVisitorsBuilding(buildingID, ref data, totalVisitCount);
                 *  }
                 */
                PromoteWorkers(buildingID, ref data, ref workersRequired);
                // Do nothing for students
            } // end if good building
        }     // end EnsureCitizenUnits
        /// <summary>
        /// Prefix to force settings reset on load (if enabled) for RICO buildings (resetting to current settings).
        /// </summary>
        /// <param name="__instance">Original object instance reference</param>
        /// <param name="buildingID">Building instance ID</param>
        /// <param name="data">Building data</param>
        /// <param name="version">Version</param>
        private static bool Prefix(PrivateBuildingAI __instance, ushort buildingID, ref Building data, uint version)
        {
            // Don't do anything if the flag isn't set.
            if (!ModSettings.resetOnLoad)
            {
                // Carry on to original method.
                return(true);
            }

            // Check to see if we've preloaded a local settings file.
            if (Loading.localRicoDef != null)
            {
                // Step through each definition from the local settings file, looking for a match.
                foreach (RICOBuilding building in Loading.localRicoDef.Buildings)
                {
                    if (building.ricoEnabled && __instance.m_info.name == building.name)
                    {
                        // m_level is one less than building.level.
                        byte newLevel = (byte)(building.level - 1);

                        if (data.m_level != newLevel)
                        {
                            Debugging.Message("found building '" + building.name + "' with level " + (data.m_level + 1) + ", overriding to level " + building.level);
                            data.m_level = newLevel;
                        }

                        // Basic game code processing to continue initialisation.
                        __instance.CalculateWorkplaceCount((ItemClass.Level)data.m_level, new Randomizer(buildingID), data.Width, data.Length, out int level, out int level2, out int level3, out int level4);
                        __instance.AdjustWorkplaceCount(buildingID, ref data, ref level, ref level2, ref level3, ref level4);

                        int workCount       = level + level2 + level3 + level4;
                        int targetHomeCount = 0;

                        // Update visitor count.
                        int visitCount = __instance.CalculateVisitplaceCount((ItemClass.Level)data.m_level, new Randomizer(buildingID), data.Width, data.Length);

                        // Check to see if rsidential building homecounts differ from settings.
                        if (building.service == "residential")
                        {
                            int currentHomeCount = 0;

                            // Count currently applied citizen units (households).
                            if (data.m_citizenUnits != 0)
                            {
                                // At least one household here; get the first.
                                CitizenUnit citizenUnit = Singleton <CitizenManager> .instance.m_units.m_buffer[data.m_citizenUnits];
                                currentHomeCount = 1;

                                // Step through all applied citizen units (linked via m_nextUnit), counting as we go,
                                while (citizenUnit.m_nextUnit != 0)
                                {
                                    citizenUnit = Singleton <CitizenManager> .instance.m_units.m_buffer[citizenUnit.m_nextUnit];
                                    currentHomeCount++;
                                }
                            }

                            // Determine target household count.
                            targetHomeCount = __instance.CalculateHomeCount((ItemClass.Level)data.m_level, new Randomizer(buildingID), data.Width, data.Length);

                            // If target household count is lower than the current household count, we need to perform a forced reset.
                            // The reverse case, targets greater than current, will be caught with the base-case call to EnsureCitizenUnits below.
                            if (targetHomeCount < currentHomeCount)
                            {
                                Debugging.Message("found Residential prefab " + building.name + " with target homecount " + targetHomeCount + " and citizen units " + currentHomeCount + "; forcing homecount reset");
                                RealisticCitizenUnits.EnsureCitizenUnits(ref __instance, buildingID, ref data, targetHomeCount, workCount, visitCount, 0);
                            }
                        }

                        // Update citizen units to match new totals.
                        EnsureCitizenUnitsRev(__instance, buildingID, ref data, targetHomeCount, workCount, visitCount, 0);

                        // Clear any problems (so we don't have any residual issues from changing service types, for example (new) residential buildings showing 'not enough goods'.
                        // Any 'genuine' problems will be quickly reapplied by the game.
                        data.m_problems = 0;

                        // We've set things up here for Ploppable RICO - don't fall through to game code.
                        return(false);
                    }
                }
            }

            // If we've hit this point, then no Ploppable RICO setup has occured - fall through to game code.
            return(true);
        }