Esempio n. 1
0
        /// <summary>
        /// Helper method for dumping the contents of an offer, for debugging purposes.
        /// </summary>
        /// <param name="offer"></param>
        /// <param name="material"></param>
        /// <returns></returns>
        public static string ToString(ref TransferManager.TransferOffer offer, TransferManager.TransferReason material)
        {
            var outsideOfferText = TransferManagerInfo.IsOutsideOffer(ref offer) ? "(O)" : "";

            if (offer.NetSegment != 0)
            {
                return($"Id=S{offer.NetSegment}, (Amt,Mat,Pri,Exc,Act)=({offer.Amount},{material},{offer.Priority},{offer.Exclude},{offer.Active})");
            }

            if (offer.Vehicle != 0)
            {
                var homeBuilding = VehicleManager.instance.m_vehicles.m_buffer[offer.Vehicle].m_sourceBuilding;
                return($"Id=V{offer.Vehicle}, Home=B{homeBuilding}{outsideOfferText}, (Amt,Mat,Pri,Exc,Act)=({offer.Amount},{material},{offer.Priority},{offer.Exclude},{offer.Active})");
            }

            if (offer.Citizen != 0)
            {
                var homeBuilding = CitizenManager.instance.m_citizens.m_buffer[offer.Citizen].m_homeBuilding;
                return($"Id=C{offer.Citizen}, Home=B{homeBuilding}{outsideOfferText}, (Amt,Mat,Pri,Exc,Act)=({offer.Amount},{material},{offer.Priority},{offer.Exclude},{offer.Active})");
            }

            if (offer.Building != 0)
            {
                return($"Id=B{offer.Building}{outsideOfferText}, (Amt,Mat,Pri,Exc,Act)=({offer.Amount},{material},{offer.Priority},{offer.Exclude},{offer.Active})");
            }

            return($"Id=0, (Amt,Mat,Pri,Exc,Act)=({offer.Amount},{material},{offer.Priority},{offer.Exclude},{offer.Active})");
        }
        /// <summary>
        /// Add a supply chain link between the source and destination buildings.
        /// The supply chain link overrides all local area, all outside connections, and all district constraints.
        /// </summary>
        /// <param name="source"></param>
        /// <param name="destination"></param>
        public static void AddSupplyChainConnection(ushort source, ushort destination)
        {
            if (!TransferManagerInfo.IsSupplyChainBuilding(source) || !TransferManagerInfo.IsSupplyChainBuilding(destination))
            {
                return;
            }

            if (!TransferManagerInfo.IsValidSupplyChainLink(source, destination))
            {
                Logger.Log($"Constraints::AddSupplyChainConnection: Could not add invalid supply chain link: source={source}, destination={destination}");
                return;
            }

            bool added = false;

            if (m_supplyDestinations[source] == null)
            {
                m_supplyDestinations[source] = new List <int>();
            }

            if (!m_supplyDestinations[source].Contains(destination))
            {
                added = true;
                m_supplyDestinations[source].Add(destination);
            }

            if (added)
            {
                var sourceBuildingName      = TransferManagerInfo.GetBuildingName(source);
                var destinationBuildingName = TransferManagerInfo.GetBuildingName(destination);
                Logger.Log($"Constraints::AddSupplyChainConnection: {sourceBuildingName} ({source}) => {destinationBuildingName} ({destination}) ...");
            }
        }
Esempio n. 3
0
        protected override void OnToolUpdate()
        {
            base.OnToolUpdate();

            var building = m_hoverInstance.Building;

            if (m_toolController.IsInsideUI || !Cursor.visible || building == 0)
            {
                ShowToolInfo(false, null, Vector3.zero);
                return;
            }

            // Don't show info for dummy or sub buildings.
            if (BuildingManager.instance.m_buildings.m_buffer[building].Info.GetAI() is DummyBuildingAI)
            {
                ShowToolInfo(false, null, Vector3.zero);
                return;
            }

            if (TransferManagerInfo.IsDistrictServicesBuilding(building) || TransferManagerInfo.IsCustomVehiclesBuilding(building))
            {
                var position = BuildingManager.instance.m_buildings.m_buffer[building].m_position;
                var txt      = GetBuildingInfoText(building);
                ShowToolInfo(true, txt, position);
            }
        }
        private static bool AddDistrictParkServiced(List <DistrictPark>[] array, int buildingId, DistrictPark districtPark)
        {
            if (!TransferManagerInfo.IsDistrictServicesBuilding(buildingId))
            {
                var buildingName = TransferManagerInfo.GetBuildingName(buildingId);
                Logger.LogWarning($"Constraints::AddDistrictParkServiced: Ignoring {districtPark.Name} restriction because {buildingName} ({buildingId}) is not a district services building.");
                return(false);
            }

            if (!districtPark.Exists)
            {
                Logger.LogWarning($"Constraints::AddDistrictParkServiced: Ignoring {districtPark.Name} restriction because this district/park does not exist.");
                return(false);
            }

            if (array[buildingId] == null)
            {
                array[buildingId] = new List <DistrictPark>();
            }

            if (!array[buildingId].Contains(districtPark))
            {
                array[buildingId].Add(districtPark);
            }

            return(true);
        }
        public static void SetAllInputLocalAreas(int buildingId, bool status)
        {
            var buildingName = TransferManagerInfo.GetBuildingName(buildingId);

            Logger.LogVerbose($"Constraints::SetAllInputLocalAreas: {buildingName} ({buildingId}) => {status} ...");

            SetArrayStatus(m_inputBuildingToAllLocalAreas, buildingId, status);
        }
        public static void SetBuildingUseDefaultVehicles(int buildingId, bool status)
        {
            var buildingName = TransferManagerInfo.GetBuildingName(buildingId);

            Logger.LogVerbose($"VehicleManagerMod::SetBuildingUseDefaultVehicles: {buildingName} ({buildingId}) => {status} ...");

            BuildingUseDefaultVehicles[buildingId] = status;
        }
        public static void SetAllOutputOutsideConnections(int buildingId, bool status)
        {
            var buildingName = TransferManagerInfo.GetBuildingName(buildingId);

            Logger.LogVerbose($"Constraints::SetAllOutputOutsideConnections: {buildingName} ({buildingId}) => {status} ...");

            SetArrayStatus(m_outputBuildingToOutsideConnections, buildingId, status);
        }
        /// <summary>
        /// Sets the internal supply reserve.
        /// </summary>
        /// <param name="buildingId"></param>
        /// <param name="amount">Must bet between 0 and 100.</param>
        public static void SetInternalSupplyReserve(int buildingId, int amount)
        {
            var buildingName = TransferManagerInfo.GetBuildingName(buildingId);

            Logger.LogVerbose($"Constraints::SetInternalSupplyReserve: {buildingName} ({buildingId}) => {amount} ...");

            m_buildingToInternalSupplyBuffer[buildingId] = COMath.Clamp(amount, 0, 100);
        }
 /// <summary>
 /// Allow the specified district or park to be serviced by the specified building
 /// </summary>
 /// <param name="buildingId"></param>
 /// <param name="districtPark"></param>
 public static void AddOutputDistrictParkServiced(int buildingId, DistrictPark districtPark)
 {
     if (AddDistrictParkServiced(m_outputBuildingToDistrictParkServiced, buildingId, districtPark))
     {
         var buildingName = TransferManagerInfo.GetBuildingName(buildingId);
         Logger.Log($"Constraints::AddOutputDistrictParkServiced: {buildingName} ({buildingId}) => {districtPark.Name} ...");
     }
 }
        private static void SetArrayStatus(bool[] array, int buildingId, bool status)
        {
            if (!TransferManagerInfo.IsDistrictServicesBuilding(buildingId))
            {
                return;
            }

            array[buildingId] = status;
        }
            /// <summary>
            /// Principles:
            ///   1) max concurrent orders per building per type is capped per MAX_TTL day period
            ///   2)
            /// </summary>
            /// <param name="material"></param>
            /// <param name="requestBuilding"></param>
            /// <param name="responseBuilding"></param>
            /// <returns></returns>
            public bool IsRestricted(TransferManager.TransferReason material, ushort requestBuilding, ushort responseBuilding)
            {
                if (!Events.TryGetValue(requestBuilding, out var list))
                {
                    return(false);
                }

                var isRequestBuildingOutside  = TransferManagerInfo.IsOutsideBuilding(requestBuilding);
                var isResponseBuildingOutside = TransferManagerInfo.IsOutsideBuilding(responseBuilding);

                if (!Settings.enableDummyCargoTraffic.value && isRequestBuildingOutside && isResponseBuildingOutside)
                {
                    return(true);
                }

                var concurrentOrderCount = list.Count;

                var concurrentOrderCountToOutsideConnection = 0;
                var concurrentOrderCountToResponseBuilding  = 0;

                for (int i = 0; i < list.Count; i++)
                {
                    if (list[i].ResponseBuilding == responseBuilding)
                    {
                        concurrentOrderCountToResponseBuilding++;
                    }

                    if (TransferManagerInfo.IsOutsideBuilding(list[i].ResponseBuilding))
                    {
                        concurrentOrderCountToOutsideConnection++;
                    }
                }

                var vehicleCount = TransferManagerInfo.GetCargoVehicleCount(requestBuilding, material);

                var maxConcurrentOrderCount = Math.Ceiling(Constraints.GlobalOutsideConnectionIntensity() / 10.0);

                if (isRequestBuildingOutside && TransferManagerInfo.IsOutsideRoadConnection(requestBuilding))
                {
                    maxConcurrentOrderCount *= 4;
                }

                var maxConcurrentOrderCountToResponseBuilding  = Math.Ceiling(maxConcurrentOrderCount / 2.0);
                var maxConcurrentOrderCountToOutsideConnection = Math.Ceiling(maxConcurrentOrderCount * Constraints.GlobalOutsideToOutsidePerc() / 100.0);

                var maxVehicleCount = Math.Ceiling(maxConcurrentOrderCount / 2.0);

                bool isRestrictedConcurrent                    = concurrentOrderCount >= maxConcurrentOrderCount;
                bool isRestrictedConcurrentToBuilding          = concurrentOrderCountToResponseBuilding >= maxConcurrentOrderCountToResponseBuilding;
                bool isRestrictedConcurrentToOutsideConnection = isRequestBuildingOutside && isResponseBuildingOutside && concurrentOrderCountToOutsideConnection >= maxConcurrentOrderCountToOutsideConnection;
                bool isVehicleConstrained = vehicleCount >= maxVehicleCount;

                return(isRestrictedConcurrent || isRestrictedConcurrentToBuilding || isRestrictedConcurrentToOutsideConnection || isVehicleConstrained);
            }
        private static bool IsSameLocation(
            ref TransferManager.TransferOffer requestOffer,
            ref TransferManager.TransferOffer responseOffer)
        {
            if (requestOffer.m_object == responseOffer.m_object)
            {
                return(true);
            }

            var requestHomeBuilding  = TransferManagerInfo.GetHomeBuilding(ref requestOffer);
            var responseHomeBuilding = TransferManagerInfo.GetHomeBuilding(ref responseOffer);

            if (requestHomeBuilding == responseHomeBuilding)
            {
                return(true);
            }

            // Don't match a guest vehicle to its host building.  For instance, Taxi stands.
            if (responseOffer.Vehicle != 0 && BuildingManager.instance.m_buildings.m_buffer[requestHomeBuilding].m_guestVehicles != 0)
            {
                var vehicleID = BuildingManager.instance.m_buildings.m_buffer[requestHomeBuilding].m_guestVehicles;
                int num       = 0;
                while (vehicleID != 0)
                {
                    if (responseOffer.Vehicle == vehicleID)
                    {
                        return(true);
                    }

                    vehicleID = VehicleManager.instance.m_vehicles.m_buffer[vehicleID].m_nextGuestVehicle;
                    ++num;

                    if (++num > 16384)
                    {
                        CODebugBase <LogChannel> .Error(LogChannel.Core, "Invalid list detected!\n" + System.Environment.StackTrace);

                        break;
                    }
                }
            }

            // Don't match outside connections that are too close to each other.
            if (TransferManagerInfo.IsOutsideBuilding(requestHomeBuilding) && TransferManagerInfo.IsOutsideBuilding(responseHomeBuilding))
            {
                var requestPosition  = BuildingManager.instance.m_buildings.m_buffer[requestHomeBuilding].m_position;
                var responsePosition = BuildingManager.instance.m_buildings.m_buffer[responseHomeBuilding].m_position;

                var distanceSquared = Vector3.SqrMagnitude(responsePosition - requestPosition);
                return(distanceSquared <= 100000);
            }

            return(false);
        }
        /// <summary>
        /// Returns true if we can potentially match the two given offers.
        /// </summary>
        /// <returns></returns>
        private static bool IsValidDistrictOffer(
            TransferManager.TransferReason material,
            ref TransferManager.TransferOffer requestOffer, int requestPriority,
            ref TransferManager.TransferOffer responseOffer, int responsePriority)
        {
            var requestBuilding  = TransferManagerInfo.GetHomeBuilding(ref requestOffer);
            var responseBuilding = TransferManagerInfo.GetHomeBuilding(ref responseOffer);

            if (responseBuilding == 0)
            {
                Logger.LogMaterial(
                    $"TransferManager::IsValidDistrictOffer: {Utils.ToString(ref responseOffer, material)}, not a district services building",
                    material);
                return(false);
            }

            // Special logic if both buildings are warehouses.  Used to prevent goods from being shuffled back and forth between warehouses.
            if (BuildingManager.instance.m_buildings.m_buffer[requestBuilding].Info.GetAI() is WarehouseAI &&
                BuildingManager.instance.m_buildings.m_buffer[responseBuilding].Info.GetAI() is WarehouseAI)
            {
                return(false);
            }

            if (Constraints.OutputAllLocalAreas(responseBuilding))
            {
                Logger.LogMaterial(
                    $"TransferManager::IsValidDistrictOffer: {Utils.ToString(ref responseOffer, material)}, serves all local areas",
                    material);
                return(true);
            }

            // The call to TransferManagerInfo.GetDistrict applies to offers that are come from buildings, service
            // vehicles, citizens, AND segments.  The latter needs to be considered for road maintenance.
            var requestDistrictPark         = TransferManagerInfo.GetDistrictPark(material, ref requestOffer);
            var responseDistrictParksServed = Constraints.OutputDistrictParkServiced(responseBuilding);

            if (requestDistrictPark.IsServedBy(responseDistrictParksServed))
            {
                Logger.LogMaterial(
                    $"TransferManager::IsValidDistrictOffer: {Utils.ToString(ref responseOffer, material)}, serves district {requestDistrictPark.Name}",
                    material);
                return(true);
            }

            Logger.LogMaterial(
                $"TransferManager::IsValidDistrictOffer: {Utils.ToString(ref responseOffer, material)}, not valid",
                material);
            return(false);
        }
        public static void AddCustomVehicle(int buildingId, int prefabIndex)
        {
            if (BuildingToVehicles[buildingId] == null)
            {
                BuildingToVehicles[buildingId] = new List <int>();
            }

            if (!BuildingToVehicles[buildingId].Contains(prefabIndex))
            {
                BuildingToVehicles[buildingId].Add(prefabIndex);

                var buildingName = TransferManagerInfo.GetBuildingName(buildingId);
                var prefabName   = PrefabNames[prefabIndex];
                Logger.Log($"VehicleManagerMod::AddCustomVehicle: {buildingName} ({buildingId}) => {prefabName} ...");
            }
        }
        public static bool Prefix(TransferManager.TransferReason material, ref TransferManager.TransferOffer offer)
        {
            Logger.LogMaterial($"TransferManager::AddIncomingOffer: {Utils.ToString(ref offer, material)}!", material);

            // Inactive outside connections should not be adding offers ...
            if (OutsideConnectionInfo.IsInvalidIncomingOutsideConnection(offer.Building))
            {
                Logger.LogMaterial($"TransferManager::AddIncomingOffer: Disallowing outside connection B{offer.Building} because of missing cargo buildings!", material);
                return(false);
            }

            if (!(TransferManagerInfo.IsDistrictOffer(material) || TransferManagerInfo.IsSupplyChainOffer(material)))
            {
                // Fix for certain assets that have sub buildings that should not be making offers ...
                if (offer.Building != 0 && BuildingManager.instance.m_buildings.m_buffer[offer.Building].m_parentBuilding != 0)
                {
                    if (material == TransferManager.TransferReason.ParkMaintenance)
                    {
                        Logger.LogMaterial($"TransferManager::AddIncomingOffer: Filtering out subBuilding {Utils.ToString(ref offer, material)}!", material);
                        return(false);
                    }
                }

                return(true);
            }

            if (material == TransferManager.TransferReason.Taxi && offer.Citizen != 0)
            {
                var instance       = CitizenManager.instance.m_citizens.m_buffer[offer.Citizen].m_instance;
                var targetBuilding = CitizenManager.instance.m_instances.m_buffer[instance].m_targetBuilding;
                var targetPosition = BuildingManager.instance.m_buildings.m_buffer[targetBuilding].m_position;

                if (!TaxiMod.CanUseTaxis(offer.Position, targetPosition))
                {
                    Logger.LogMaterial($"TransferManager::AddIncomingOffer: Filtering out {Utils.ToString(ref offer, material)}!", material);
                    var instanceId = CitizenManager.instance.m_citizens.m_buffer[offer.Citizen].m_instance;
                    CitizenManager.instance.m_instances.m_buffer[instanceId].m_flags      &= ~CitizenInstance.Flags.WaitingTaxi;
                    CitizenManager.instance.m_instances.m_buffer[instanceId].m_flags      |= CitizenInstance.Flags.BoredOfWaiting;
                    CitizenManager.instance.m_instances.m_buffer[instanceId].m_flags      |= CitizenInstance.Flags.CannotUseTaxi;
                    CitizenManager.instance.m_instances.m_buffer[instanceId].m_waitCounter = byte.MaxValue;
                    return(false);
                }
            }

            TransferManagerAddOffer.ModifyOffer(material, ref offer);
            return(true);
        }
        /// <summary>
        /// Called when a building is first created.  If situated in a district or park, then automatically restricts that
        /// building to serve its home district only.
        /// </summary>
        /// <param name="buildingId"></param>
        public static void CreateBuilding(ushort buildingId)
        {
            if (!TransferManagerInfo.IsDistrictServicesBuilding(buildingId))
            {
                return;
            }

            var buildingInfo = BuildingManager.instance.m_buildings.m_buffer[buildingId].Info;
            var service      = buildingInfo.GetService();
            var subService   = buildingInfo.GetSubService();
            var ai           = buildingInfo.GetAI();

            // Do not pack the homeDistrict and homePark into a single DistrictPark struct.  Otherwise, it will make
            // removing districts/parks a lot harder!!
            var position     = BuildingManager.instance.m_buildings.m_buffer[buildingId].m_position;
            var homeDistrict = DistrictManager.instance.GetDistrict(position);
            var homePark     = DistrictManager.instance.GetPark(position);

            Logger.Log($"Constraints::CreateBuilding: buildingId={buildingId}, homeDistrict={homeDistrict}, homePark={homePark}, service={service}, subService={subService}, ai={ai}");

            // Set default input settings.
            SetAllInputLocalAreas(buildingId, true);
            SetAllInputOutsideConnections(buildingId, true);
            m_inputBuildingToDistrictParkServiced[buildingId] = null;

            // Do not set the home district for these types of buildings.
            if (ai is ChildcareAI || ai is EldercareAI)
            {
                homeDistrict = 0;
                homePark     = 0;
            }

            // Serve all areas if the building doesn't belong to any district or park.
            SetAllOutputLocalAreas(buildingId, homeDistrict == 0 && homePark == 0);
            SetAllOutputOutsideConnections(buildingId, homeDistrict == 0 && homePark == 0);
            m_outputBuildingToDistrictParkServiced[buildingId] = null;

            if (homeDistrict != 0)
            {
                AddOutputDistrictParkServiced(buildingId, DistrictPark.FromDistrict(homeDistrict));
            }

            if (homePark != 0)
            {
                AddOutputDistrictParkServiced(buildingId, DistrictPark.FromPark(homePark));
            }
        }
        public static bool Prefix(ref TransferManager.TransferReason material, ref TransferManager.TransferOffer offer)
        {
            Logger.LogMaterial($"TransferManager::AddOutgoingOffer: {Utils.ToString(ref offer, material)}!", material);

            // Inactive outside connections should not be adding offers ...
            if (OutsideConnectionInfo.IsInvalidOutgoingOutsideConnection(offer.Building))
            {
                Logger.LogMaterial($"TransferManager::AddOutgoingOffer: Disallowing outside connection B{offer.Building} because of missing cargo buildings!", material);
                return(false);
            }

            // Change these offers ... a bug in the base game.  Citizens should not offer health care services.
            if ((material == TransferManager.TransferReason.ElderCare || material == TransferManager.TransferReason.ChildCare) && offer.Citizen != 0)
            {
                offer.Active = true;
                TransferManager.instance.AddIncomingOffer(material, offer);
                return(false);
            }

            // Too many requests for helicopters ...
            if (material == TransferManager.TransferReason.Sick2)
            {
                if (Singleton <SimulationManager> .instance.m_randomizer.Int32(10U) != 0)
                {
                    material = TransferManager.TransferReason.Sick;
                }
            }

            if (!(TransferManagerInfo.IsDistrictOffer(material) || TransferManagerInfo.IsSupplyChainOffer(material)))
            {
                // Fix for certain assets that have sub buildings that should not be making offers ...
                if (offer.Building != 0 && BuildingManager.instance.m_buildings.m_buffer[offer.Building].m_parentBuilding != 0)
                {
                    if (material == TransferManager.TransferReason.ParkMaintenance)
                    {
                        Logger.LogMaterial($"TransferManager::AddOutgoingOffer: Filtering out subBuilding {Utils.ToString(ref offer, material)}!", material);
                        return(false);
                    }
                }

                return(true);
            }

            TransferManagerAddOffer.ModifyOffer(material, ref offer);
            return(true);
        }
Esempio n. 18
0
        /// <summary>
        /// Returns the input type of the building, used to determine which GUI elements are shown in the main panel.
        /// </summary>
        /// <param name="building"></param>
        /// <returns></returns>
        public static InputType GetBuildingInputType(int building)
        {
            if (building == 0)
            {
                return(InputType.NONE);
            }

            var result = InputType.NONE;

            // The only building type for which we will not show an outgoing tab is coal and heating power plants.
            var info = BuildingManager.instance.m_buildings.m_buffer[building].Info;

            if (TransferManagerInfo.IsDistrictServicesBuilding(building))
            {
                if ((info?.GetService() == ItemClass.Service.Electricity && info?.GetAI() is PowerPlantAI) ||
                    (info?.GetService() == ItemClass.Service.Water && info?.GetAI() is HeatingPlantAI) ||
                    (info?.GetService() == ItemClass.Service.Monument && info?.gameObject?.name == "ChirpX Launch Control Center"))
                {
                }
                else if (!Settings.enableIndustriesControl && info?.GetService() == ItemClass.Service.PlayerIndustry)
                {
                }
                else
                {
                    result |= InputType.OUTGOING;
                }
            }

            if (Settings.enableIndustriesControl && TransferManagerInfo.IsSupplyChainBuilding(building))
            {
                result |= InputType.SUPPLY_CHAIN;

                if (!(info?.GetAI() is ExtractingFacilityAI || info?.GetAI() is FishFarmAI || info?.GetAI() is FishingHarborAI))
                {
                    result |= InputType.INCOMING;
                }
            }

            if (TransferManagerInfo.IsCustomVehiclesBuilding(building))
            {
                result |= InputType.VEHICLES;
            }

            return(result);
        }
        /// <summary>
        /// Sets the priority of outside connection offers to 0, while ensuring that local offers have priority 1
        /// or greater.
        /// </summary>
        /// <remarks>
        /// We are a modifying the priority of offers as a way of prioritizing the local supply chain, and only
        /// resorting to outside connections if materials cannot be found locally.
        /// </remarks>
        /// <param name="material"></param>
        /// <param name="offer"></param>
        public static void ModifyOffer(TransferManager.TransferReason material, ref TransferManager.TransferOffer offer)
        {
            var isOutsideOffer = TransferManagerInfo.IsOutsideOffer(ref offer);

            if (isOutsideOffer)
            {
                offer.Priority = 0;
            }
            else
            {
                offer.Priority = Mathf.Clamp(offer.Priority + 1, 1, 7);
            }

            if (offer.Vehicle != 0)
            {
                offer.Priority = 7;
            }
        }
        private static bool IsSameLocation(
            ref TransferManager.TransferOffer requestOffer,
            ref TransferManager.TransferOffer responseOffer)
        {
            if (requestOffer.m_object == responseOffer.m_object)
            {
                return(true);
            }

            var requestHomeBuilding  = TransferManagerInfo.GetHomeBuilding(ref requestOffer);
            var responseHomeBuilding = TransferManagerInfo.GetHomeBuilding(ref responseOffer);

            if (requestHomeBuilding == responseHomeBuilding)
            {
                return(true);
            }

            // Don't match a guest vehicle to its host building.  For instance, Taxi stands.
            if (responseOffer.Vehicle != 0 && BuildingManager.instance.m_buildings.m_buffer[requestHomeBuilding].m_guestVehicles != 0)
            {
                var vehicleID = BuildingManager.instance.m_buildings.m_buffer[requestHomeBuilding].m_guestVehicles;
                int num       = 0;
                while (vehicleID != 0)
                {
                    if (responseOffer.Vehicle == vehicleID)
                    {
                        return(true);
                    }

                    vehicleID = VehicleManager.instance.m_vehicles.m_buffer[vehicleID].m_nextGuestVehicle;
                    ++num;

                    if (++num > 16384)
                    {
                        CODebugBase <LogChannel> .Error(LogChannel.Core, "Invalid list detected!\n" + System.Environment.StackTrace);

                        break;
                    }
                }
            }

            return(false);
        }
Esempio n. 21
0
        /// <summary>
        /// Lists all buildings that can be configured using ESD.
        /// </summary>
        /// <returns></returns>
        public static List <ushort> GetSupportedServiceBuildings()
        {
            var bs = new List <Building>();

            for (ushort buildingId = 0; buildingId < BuildingManager.MAX_BUILDING_COUNT; buildingId++)
            {
                if (TransferManagerInfo.IsDistrictServicesBuilding(buildingId))
                {
                    bs.Add(new Building
                    {
                        Name = TransferManagerInfo.GetBuildingName(buildingId),
                        Id   = buildingId
                    });
                }
            }

            bs.Sort();
            return(bs.Select(b => b.Id).ToList());
        }
        public static void RemoveCustomVehicle(int buildingId, int prefabIndex)
        {
            if (BuildingToVehicles[buildingId] == null)
            {
                return;
            }

            if (BuildingToVehicles[buildingId].Contains(prefabIndex))
            {
                BuildingToVehicles[buildingId].Remove(prefabIndex);

                var buildingName = TransferManagerInfo.GetBuildingName(buildingId);
                var prefabName   = PrefabNames[prefabIndex];
                Logger.Log($"VehicleManagerMod::RemoveCustomVehicle: {buildingName} ({buildingId}) => {prefabName} ...");
            }

            if (BuildingToVehicles[buildingId].Count == 0)
            {
                BuildingToVehicles[buildingId] = null;
            }
        }
Esempio n. 23
0
        /// <summary>
        /// Helper method for displaying information, including district and supply chain constraints, about the
        /// building with given building id.
        /// </summary>
        /// <param name="building"></param>
        /// <returns></returns>
        private static string GetBuildingInfoText(ushort building)
        {
            var inputType = TransferManagerInfo.GetBuildingInputType(building);

            var txtItems = new List <string>();

            txtItems.Add($"{TransferManagerInfo.GetBuildingName(building)} ({building})");
            txtItems.Add(TransferManagerInfo.GetDistrictParkText(building));

            // Early return.  Rest of info pertains to building types that we deal with in the mod.
            if (!(TransferManagerInfo.IsDistrictServicesBuilding(building) || TransferManagerInfo.IsCustomVehiclesBuilding(building)))
            {
                return(string.Join("\n", txtItems.ToArray()));
            }

            txtItems.Add(TransferManagerInfo.GetBuildingInputTypeText(building));
            txtItems.Add(TransferManagerInfo.GetServicesText(building));

            if (!TransferManagerInfo.IsSupplyChainBuilding(building))
            {
                if (TransferManagerInfo.IsDistrictServicesBuilding(building))
                {
                    txtItems.Add("");
                    txtItems.Add(TransferManagerInfo.GetOutputDistrictsServedText(building));
                }

                if (Settings.enableCustomVehicles &&
                    !VehicleManagerMod.BuildingUseDefaultVehicles[building] &&
                    VehicleManagerMod.BuildingToVehicles[building] != null &&
                    (inputType & InputType.VEHICLES) != InputType.NONE)
                {
                    txtItems.Add("");
                    txtItems.Add(TransferManagerInfo.GetCustomVehiclesText(building));
                }

                return(string.Join("\n", txtItems.ToArray()));
            }

            if (Settings.enableIndustriesControl)
            {
                // From this point forth, we know this is a supply chain building ...
                txtItems.Add($"Supply Reserve: {Constraints.InternalSupplyBuffer(building)}");

                if ((inputType & InputType.INCOMING) != InputType.NONE)
                {
                    txtItems.Add("");
                    txtItems.Add(TransferManagerInfo.GetSupplyBuildingSourcesText(building));
                }

                if ((inputType & InputType.OUTGOING) != InputType.NONE)
                {
                    txtItems.Add("");
                    txtItems.Add(TransferManagerInfo.GetSupplyBuildingDestinationsText(building));
                }

                if (Settings.enableCustomVehicles &&
                    !VehicleManagerMod.BuildingUseDefaultVehicles[building] &&
                    VehicleManagerMod.BuildingToVehicles[building] != null &&
                    (inputType & InputType.VEHICLES) != InputType.NONE)
                {
                    txtItems.Add("");
                    txtItems.Add(TransferManagerInfo.GetCustomVehiclesText(building));
                }

                var problemText = TransferManagerInfo.GetSupplyBuildingProblemsText(building);
                if (problemText != string.Empty)
                {
                    txtItems.Add("");
                    txtItems.Add($"<<WARNING: Cannot find the following materials to procure!>>");
                    txtItems.Add(problemText);
                }
            }

            return(string.Join("\n", txtItems.ToArray()));
        }
Esempio n. 24
0
        protected override void OnToolGUI(Event e)
        {
            try
            {
                var hoverInstance = this.m_hoverInstance;
                var building      = hoverInstance.Building;

                WorldInfoPanel.HideAllWorldInfoPanels();

                if (Settings.keyCopy.IsPressed(e))
                {
                    if (building == 0 ||
                        BuildingManager.instance.m_buildings.m_buffer[building].Info.GetAI() is DummyBuildingAI ||
                        !(TransferManagerInfo.IsDistrictServicesBuilding(building) || TransferManagerInfo.IsCustomVehiclesBuilding(building)))
                    {
                        Utils.DisplayMessage(
                            str1: "Enhanced District Services",
                            str2: $"Cannot copy policy from this building!",
                            str3: "IconMessage");
                        return;
                    }

                    CopyPaste.BuildingTemplate = building;
                }

                if (Settings.keyPaste.IsPressed(e))
                {
                    if (CopyPaste.BuildingTemplate == 0)
                    {
                        Utils.DisplayMessage(
                            str1: "Enhanced District Services",
                            str2: $"Please hover over a valid building and press Ctrl-C to copy its policy first!",
                            str3: "IconMessage");
                        return;
                    }

                    if (building == 0 ||
                        BuildingManager.instance.m_buildings.m_buffer[building].Info.GetAI() is DummyBuildingAI ||
                        !(TransferManagerInfo.IsDistrictServicesBuilding(building) || TransferManagerInfo.IsCustomVehiclesBuilding(building)))
                    {
                        Utils.DisplayMessage(
                            str1: "Enhanced District Services",
                            str2: $"Cannot copy policy to this unsupported building!",
                            str3: "IconMessage");
                        return;
                    }

                    var inputType1 = TransferManagerInfo.GetBuildingInputType(CopyPaste.BuildingTemplate);
                    var inputType2 = TransferManagerInfo.GetBuildingInputType(building);

                    if (inputType1 != inputType2)
                    {
                        Utils.DisplayMessage(
                            str1: "Enhanced District Services",
                            str2: $"Can only copy-paste policy between buildings of the same policy type!",
                            str3: "IconMessage");
                        return;
                    }

                    var success = CopyPaste.CopyPolicyTo(building);
                    if (!success)
                    {
                        Utils.DisplayMessage(
                            str1: "Enhanced District Services",
                            str2: $"Could not copy certain supply chain restrictions.  Please check results of copy operation!",
                            str3: "IconMessage");
                        return;
                    }

                    var position = BuildingManager.instance.m_buildings.m_buffer[building].m_position;
                    var txt      = GetBuildingInfoText(building);
                    ShowToolInfo(true, txt, position);
                }

                if (!m_toolController.IsInsideUI && e.type == UnityEngine.EventType.MouseDown && e.button == 0)
                {
                    if (!(TransferManagerInfo.IsDistrictServicesBuilding(building) || TransferManagerInfo.IsCustomVehiclesBuilding(building)))
                    {
                        return;
                    }

                    if (this.m_selectErrors == ToolBase.ToolErrors.None || this.m_selectErrors == ToolBase.ToolErrors.RaycastFailed)
                    {
                        Vector3 mousePosition = this.m_mousePosition;
                        UIInput.MouseUsed();

                        if (!Singleton <InstanceManager> .instance.SelectInstance(hoverInstance))
                        {
                            return;
                        }

                        SimulationManager.instance.AddAction(() =>
                        {
                            var panel = EnhancedDistrictServicesUIPanel.Instance;

                            if (panel != null)
                            {
                                panel.SetBuilding(hoverInstance.Building);
                                panel.UpdatePositionToBuilding(hoverInstance.Building);
                                panel.UpdatePanelToBuilding(hoverInstance.Building);
                                panel.opacity = 1f;
                            }

                            Singleton <GuideManager> .instance.m_worldInfoNotUsed.Disable();
                        });
                    }
                }
            }
            catch (Exception ex)
            {
                Logger.LogWarning($"EnhancedDistrictServicesTool::OnToolGUI: ...");
                Logger.LogException(ex);
            }
        }
        /// <summary>
        /// Matches all offers of the given material, if supported.  Returns true if this method did attempt to match
        /// offers.
        /// </summary>
        /// <param name="material"></param>
        /// <returns></returns>
        public static bool MatchOffers(TransferManager.TransferReason material)
        {
            try
            {
                if (material == TransferManager.TransferReason.None)
                {
                    return(true);
                }

                if (Settings.enableIndustriesControl == false && TransferManagerInfo.IsSupplyChainOffer(material))
                {
                    return(false);
                }

                // Park/Road maintenance, taxis, etc. are switched around ...
                if (material == TransferManager.TransferReason.ChildCare ||
                    material == TransferManager.TransferReason.ElderCare ||
                    material == TransferManager.TransferReason.ParkMaintenance ||
                    material == TransferManager.TransferReason.RoadMaintenance ||
                    material == TransferManager.TransferReason.Taxi)
                {
                    MatchOffersClosest(
                        material,
                        requestCount: m_incomingCount, requestOffers: m_incomingOffers,
                        requestPriorityMax: 7, requestPriorityMin: 1,
                        responseCount: m_outgoingCount, responseOffers: m_outgoingOffers,
                        responsePriorityMax: 7, responsePriorityMin: 1,
                        matchFilter: IsValidDistrictOffer);

                    Clear(material);
                    return(true);
                }
                else if (material == TransferManager.TransferReason.Fish)
                {
                    MatchOffersClosest(
                        material,
                        requestCount: m_incomingCount, requestOffers: m_incomingOffers,
                        requestPriorityMax: 7, requestPriorityMin: 1,
                        responseCount: m_outgoingCount, responseOffers: m_outgoingOffers,
                        responsePriorityMax: 7, responsePriorityMin: 1,
                        matchFilter: IsValidSupplyChainOffer);

                    MatchOffersClosest(
                        material,
                        requestCount: m_incomingCount, requestOffers: m_incomingOffers,
                        requestPriorityMax: 0, requestPriorityMin: 0,
                        responseCount: m_outgoingCount, responseOffers: m_outgoingOffers,
                        responsePriorityMax: 7, responsePriorityMin: 1,
                        matchFilter: IsValidLowPriorityOffer);

                    Clear(material);
                    return(true);
                }
                else if (TransferManagerInfo.IsDistrictOffer(material))
                {
                    MatchOffersClosest(
                        material,
                        requestCount: m_outgoingCount, requestOffers: m_outgoingOffers,
                        requestPriorityMax: 7, requestPriorityMin: 1,
                        responseCount: m_incomingCount, responseOffers: m_incomingOffers,
                        responsePriorityMax: 7, responsePriorityMin: 1,
                        matchFilter: IsValidDistrictOffer);

                    Clear(material);
                    return(true);
                }
                else if (TransferManagerInfo.IsSupplyChainOffer(material))
                {
                    // First try to match using supply chain rules.
                    MatchOffersClosest(
                        material,
                        requestCount: m_incomingCount, requestOffers: m_incomingOffers,
                        requestPriorityMax: 7, requestPriorityMin: 1,
                        responseCount: m_outgoingCount, responseOffers: m_outgoingOffers,
                        responsePriorityMax: 7, responsePriorityMin: 1,
                        matchFilter: IsValidSupplyChainOffer);

                    MatchOffersClosest(
                        material,
                        requestCount: m_incomingCount, requestOffers: m_incomingOffers,
                        requestPriorityMax: 7, requestPriorityMin: 1,
                        responseCount: m_outgoingCount, responseOffers: m_outgoingOffers,
                        responsePriorityMax: 7, responsePriorityMin: 0,
                        matchFilter: IsValidLowPriorityOffer);

                    // Now finally try and match to outside offers, as well as match using extra supply.
                    MatchOffersClosest(
                        material,
                        requestCount: m_incomingCount, requestOffers: m_incomingOffers,
                        requestPriorityMax: 0, requestPriorityMin: 0,
                        responseCount: m_outgoingCount, responseOffers: m_outgoingOffers,
                        responsePriorityMax: 7, responsePriorityMin: 0,
                        matchFilter: IsValidLowPriorityOffer);

                    Clear(material);
                    return(true);
                }
            }
            catch (Exception ex)
            {
                Logger.LogException(ex);
                Clear(material);
                return(true);
            }

            // Did not handle the material.
            return(false);
        }
        /// <summary>
        /// Returns true if we can potentially match the two given offers.
        /// </summary>
        /// <returns></returns>
        private static bool IsValidSupplyChainOffer(
            TransferManager.TransferReason material,
            ref TransferManager.TransferOffer requestOffer, int requestPriority,
            ref TransferManager.TransferOffer responseOffer, int responsePriority)
        {
            var requestBuilding  = TransferManagerInfo.GetHomeBuilding(ref requestOffer);
            var responseBuilding = TransferManagerInfo.GetHomeBuilding(ref responseOffer);

            if (responseBuilding == 0)
            {
                Logger.LogMaterial(
                    $"TransferManager::IsValidSupplyChainOffer: {Utils.ToString(ref responseOffer, material)}, not a district services building",
                    material);
                return(false);
            }

            // First check if a supply link exists.
            var responseSupplyDestinations = Constraints.SupplyDestinations(responseBuilding);

            if (responseSupplyDestinations?.Count > 0)
            {
                for (int i = 0; i < responseSupplyDestinations.Count; i++)
                {
                    if (responseSupplyDestinations[i] == (int)requestBuilding)
                    {
                        Logger.LogMaterial(
                            $"TransferManager::IsValidSupplyChainOffer: {Utils.ToString(ref responseOffer, material)}, supply link allowed",
                            material);
                        return(true);
                    }
                }
            }

            // Special logic if both buildings are warehouses.  Used to prevent goods from being shuffled back and forth between warehouses.
            if (BuildingManager.instance.m_buildings.m_buffer[requestBuilding].Info.GetAI() is WarehouseAI &&
                BuildingManager.instance.m_buildings.m_buffer[responseBuilding].Info.GetAI() is WarehouseAI)
            {
                return(false);
            }

            // Now match on all local areas and district restrictions, both on request an response buildings!
            var requestDistrictPark  = TransferManagerInfo.GetDistrictPark(material, ref requestOffer);
            var responseDistrictPark = TransferManagerInfo.GetDistrictPark(material, ref responseOffer);

            // If the request constrains the districts that it can accept orders from ...
            if (!Constraints.InputAllLocalAreas(requestBuilding))
            {
                var requestDistrictParksServed = Constraints.InputDistrictParkServiced(requestBuilding);
                if (!responseDistrictPark.IsServedBy(requestDistrictParksServed))
                {
                    Logger.LogMaterial(
                        $"TransferManager::IsValidSupplyChainOffer: {Utils.ToString(ref responseOffer, material)}, request is constrained to accept offers from certain districts only!",
                        material);
                    return(false);
                }
            }

            if (!Constraints.InputOutsideConnections(requestBuilding) && TransferManagerInfo.IsOutsideOffer(ref responseOffer))
            {
                Logger.LogMaterial(
                    $"TransferManager::IsValidSupplyChainOffer: {Utils.ToString(ref responseOffer, material)}, request is constrained not to accept outside offers!",
                    material);
                return(false);
            }

            if (Constraints.OutputAllLocalAreas(responseBuilding))
            {
                Logger.LogMaterial(
                    $"TransferManager::IsValidSupplyChainOffer: {Utils.ToString(ref responseOffer, material)}, serves all local areas",
                    material);
                return(true);
            }
            else
            {
                // The call to TransferManagerInfo.GetDistrict applies to offers that are come from buildings, service
                // vehicles, citizens, AND segments.  The latter needs to be considered for road maintenance.
                var responseDistrictParksServed = Constraints.OutputDistrictParkServiced(responseBuilding);
                if (requestDistrictPark.IsServedBy(responseDistrictParksServed))
                {
                    Logger.LogMaterial(
                        $"TransferManager::IsValidSupplyChainOffer: {Utils.ToString(ref responseOffer, material)}, serves district {requestDistrictPark.Name}",
                        material);
                    return(true);
                }
            }

            Logger.LogMaterial(
                $"TransferManager::IsValidSupplyChainOffer: {Utils.ToString(ref responseOffer, material)}, not valid",
                material);
            return(false);
        }
        /// <summary>
        /// Returns true if we can potentially match the two given offers.
        /// </summary>
        /// <returns></returns>
        private static bool IsValidLowPriorityOffer(
            TransferManager.TransferReason material,
            ref TransferManager.TransferOffer requestOffer, int requestPriority,
            ref TransferManager.TransferOffer responseOffer, int responsePriority)
        {
            var requestBuilding  = TransferManagerInfo.GetHomeBuilding(ref requestOffer);
            var responseBuilding = TransferManagerInfo.GetHomeBuilding(ref responseOffer);

            if (responseBuilding == 0)
            {
                Logger.LogMaterial(
                    $"TransferManager::IsValidLowPriorityOffer: {Utils.ToString(ref responseOffer, material)}, not a district services building",
                    material);
                return(false);
            }

            // Special logic if both buildings are warehouses.  Used to prevent goods from being shuffled back and forth between warehouses.
            if (BuildingManager.instance.m_buildings.m_buffer[requestBuilding].Info.GetAI() is WarehouseAI &&
                BuildingManager.instance.m_buildings.m_buffer[responseBuilding].Info.GetAI() is WarehouseAI)
            {
                return(false);
            }

            // Special logic for recycling centers, since they can produce recycled goods but the district policies
            // should not apply to these materials.
            if (responseBuilding != 0 && BuildingManager.instance.m_buildings.m_buffer[responseBuilding].Info.GetAI() is LandfillSiteAI)
            {
                if (TransferManagerInfo.IsOutsideOffer(ref requestOffer))
                {
                    Logger.LogMaterial(
                        $"TransferManager::IsValidLowPriorityOffer: {Utils.ToString(ref responseOffer, material)}, allow recycling centers",
                        material);
                    return(true);
                }

                // Only allow if there are no restrictions on the request, OR if recycling center resides in an allowed district.
                var requestDistrictParksServed = Constraints.InputDistrictParkServiced(requestBuilding);
                var responseDistrictPark       = TransferManagerInfo.GetDistrictPark(responseBuilding);
                if (Constraints.InputAllLocalAreas(requestBuilding) || responseDistrictPark.IsServedBy(requestDistrictParksServed))
                {
                    Logger.LogMaterial(
                        $"TransferManager::IsValidLowPriorityOffer: {Utils.ToString(ref responseOffer, material)}, allow recycling centers",
                        material);
                    return(true);
                }
                else
                {
                    return(false);
                }
            }

            // See if the request is from an outside connection ...
            if (TransferManagerInfo.IsOutsideOffer(ref requestOffer))
            {
                if (TransferManagerInfo.IsOutsideOffer(ref responseOffer))
                {
                    // Prevent matching roads that are too close together ...
                    var distanceSquared = Vector3.SqrMagnitude(responseOffer.Position - requestOffer.Position);
                    return(distanceSquared > 100000);
                }
                else if (TransferManagerInfo.GetSupplyBuildingAmount(responseBuilding) > Constraints.InternalSupplyBuffer(responseBuilding))
                {
                    Logger.LogMaterial(
                        $"TransferManager::IsValidLowPriorityOffer: {Utils.ToString(ref responseOffer, material)}, internal supply buffer, supply amount={TransferManagerInfo.GetSupplyBuildingAmount(responseBuilding)}, supply buffer={Constraints.InternalSupplyBuffer(responseBuilding)}",
                        material);
                    return(true);
                }
                else if (Constraints.OutputOutsideConnections(responseBuilding))
                {
                    Logger.LogMaterial(
                        $"TransferManager::IsValidLowPriorityOffer: {Utils.ToString(ref responseOffer, material)}, matched inside to outside offer",
                        material);
                    return(true);
                }
                else
                {
                    Logger.LogMaterial(
                        $"TransferManager::IsValidLowPriorityOffer: {Utils.ToString(ref responseOffer, material)}, disallowed outside offer",
                        material);
                    return(false);
                }
            }

            // Here, we are guaranteed that the request is a local offer.
            if (TransferManagerInfo.IsOutsideBuilding(responseBuilding))
            {
                // Don't be so aggressive in trying to serve low priority orders with outside connections.
                if (requestPriority > 1 && Constraints.InputOutsideConnections(requestBuilding))
                {
                    Logger.LogMaterial(
                        $"TransferManager::IsValidLowPriorityOffer: {Utils.ToString(ref responseOffer, material)}, matched outside to inside offer",
                        material);
                    return(true);
                }
            }
            else if (TransferManagerInfo.GetSupplyBuildingAmount(responseBuilding) > Constraints.InternalSupplyBuffer(responseBuilding))
            {
                // Only allow if the request building allows all incoming shipments
                if (Constraints.InputAllLocalAreas(requestBuilding))
                {
                    Logger.LogMaterial(
                        $"TransferManager::IsValidLowPriorityOffer: {Utils.ToString(ref responseOffer, material)}, internal supply buffer, supply amount={TransferManagerInfo.GetSupplyBuildingAmount(responseBuilding)}, supply buffer={Constraints.InternalSupplyBuffer(responseBuilding)}",
                        material);
                    return(true);
                }
            }

            Logger.LogMaterial(
                $"TransferManager::IsValidLowPriorityOffer: {Utils.ToString(ref responseOffer, material)}, not valid",
                material);
            return(false);
        }
Esempio n. 28
0
        /// <summary>
        /// Returns a descriptive text describing problems with this supply chain building ...
        /// </summary>
        /// <param name="building"></param>
        /// <returns></returns>
        public static string GetSupplyBuildingProblemsText(ushort building)
        {
            if (building == 0 || !TransferManagerInfo.IsSupplyChainBuilding(building))
            {
                return(string.Empty);
            }

            bool FindSourceBuilding(TransferManager.TransferReason material)
            {
                if (material == TransferManager.TransferReason.None)
                {
                    return(true);
                }

                // Assume for now that the outside connection can supply the building with the materials it needs.
                if (Constraints.InputOutsideConnections(building))
                {
                    return(true);
                }

                for (ushort buildingIn = 1; buildingIn < BuildingManager.MAX_BUILDING_COUNT; buildingIn++)
                {
                    if (!TransferManagerInfo.IsSupplyChainBuilding(building))
                    {
                        continue;
                    }

                    if (GetSupplyBuildingOutputMaterial(buildingIn) == material)
                    {
                        // Check if a supply link exists ...
                        if (Constraints.SupplyDestinations(buildingIn)?.Count > 0 && Constraints.SupplyDestinations(buildingIn).Contains(building))
                        {
                            return(true);
                        }

                        var requestDistrictPark  = TransferManagerInfo.GetDistrictPark(building);
                        var responseDistrictPark = TransferManagerInfo.GetDistrictPark(buildingIn);

                        if (!Constraints.InputAllLocalAreas(building))
                        {
                            var requestDistrictParksServed = Constraints.InputDistrictParkServiced(building);
                            if (!responseDistrictPark.IsServedBy(requestDistrictParksServed))
                            {
                                continue;
                            }
                        }

                        if (Constraints.OutputAllLocalAreas(buildingIn))
                        {
                            return(true);
                        }
                        else
                        {
                            // The call to TransferManagerInfo.GetDistrict applies to offers that are come from buildings, service
                            // vehicles, citizens, AND segments.  The latter needs to be considered for road maintenance.
                            var responseDistrictParksServed = Constraints.OutputDistrictParkServiced(buildingIn);
                            if (requestDistrictPark.IsServedBy(responseDistrictParksServed))
                            {
                                return(true);
                            }
                        }
                    }
                }

                return(false);
            }

            // Detect if we do have an other building that can supply materials to this building.
            List <TransferManager.TransferReason> notFound = new List <TransferManager.TransferReason>();

            switch (BuildingManager.instance.m_buildings.m_buffer[building].Info?.GetAI())
            {
            case ProcessingFacilityAI processingFacilityAI:
                if (!FindSourceBuilding(processingFacilityAI.m_inputResource1))
                {
                    notFound.Add(processingFacilityAI.m_inputResource1);
                }

                if (!FindSourceBuilding(processingFacilityAI.m_inputResource2))
                {
                    notFound.Add(processingFacilityAI.m_inputResource2);
                }

                if (!FindSourceBuilding(processingFacilityAI.m_inputResource3))
                {
                    notFound.Add(processingFacilityAI.m_inputResource3);
                }

                if (!FindSourceBuilding(processingFacilityAI.m_inputResource4))
                {
                    notFound.Add(processingFacilityAI.m_inputResource4);
                }

                break;

            default:
                break;
            }

            if (notFound.Count > 0)
            {
                return(string.Join(",", notFound.Select(x => x.ToString()).ToArray()));
            }
            else
            {
                return(string.Empty);
            }
        }
        /// <summary>
        /// Load data from given object.
        /// </summary>
        /// <param name="data"></param>
        public static void LoadData(Serialization.Datav4 data)
        {
            Logger.Log($"Constraints::LoadData: version {data.Id}");
            Clear();

            var buildings = Utils.GetSupportedServiceBuildings();

            foreach (var building in buildings)
            {
                var name         = TransferManagerInfo.GetBuildingName(building);
                var buildingInfo = BuildingManager.instance.m_buildings.m_buffer[building].Info;
                var service      = buildingInfo.GetService();
                var subService   = buildingInfo.GetSubService();
                var ai           = buildingInfo.GetAI();

                Logger.Log($"Constraints::LoadData: buildingName={name}, buildingId={building}, service={service}, subService={subService}, ai={ai}");

                var restrictions1 = data.InputBuildingToAllLocalAreas[building];
                SetAllInputLocalAreas(building, restrictions1);

                var restrictions2 = data.InputBuildingToOutsideConnections[building];
                SetAllInputOutsideConnections(building, restrictions2);

                var restrictions3 = data.InputBuildingToDistrictServiced[building];
                if (restrictions3 != null)
                {
                    foreach (var districtPark in restrictions3)
                    {
                        AddInputDistrictParkServiced(building, DistrictPark.FromSerializedInt(districtPark));
                    }
                }

                var restrictions4 = data.OutputBuildingToAllLocalAreas[building];
                SetAllOutputLocalAreas(building, restrictions4);

                var restrictions5 = data.OutputBuildingToOutsideConnections[building];
                SetAllOutputOutsideConnections(building, restrictions5);

                var restrictions6 = data.OutputBuildingToDistrictServiced[building];
                if (restrictions6 != null)
                {
                    foreach (var districtPark in restrictions6)
                    {
                        AddOutputDistrictParkServiced(building, DistrictPark.FromSerializedInt(districtPark));
                    }
                }

                if (data.BuildingToInternalSupplyBuffer != null)
                {
                    var restrictions7 = data.BuildingToInternalSupplyBuffer[building];
                    SetInternalSupplyReserve(building, restrictions7);
                }

                if (data.BuildingToBuildingServiced != null)
                {
                    var restrictions8 = data.BuildingToBuildingServiced[building];
                    if (restrictions8 != null)
                    {
                        foreach (var destination in restrictions8)
                        {
                            AddSupplyChainConnection(building, (ushort)destination);
                        }
                    }
                }

                m_globalOutsideConnectionIntensity = data.GlobalOutsideConnectionIntensity;
                m_globalOutsideToOutsideMaxPerc    = data.GlobalOutsideToOutsideMaxPerc;

                Logger.Log("");
            }
        }
        /// <summary>
        /// Stock code that transfers people/materials between the buildings/vehicles referenced in the given offers.
        /// </summary>
        private static bool StartTransfer(TransferManager.TransferReason material, TransferManager.TransferOffer offerOut, TransferManager.TransferOffer offerIn, int delta)
        {
            try
            {
                if (offerIn.Building != 0 && TransferManagerInfo.IsCustomVehiclesBuilding(offerIn.Building))
                {
                    VehicleManagerMod.CurrentSourceBuilding = offerIn.Building;
                }

                bool active1 = offerIn.Active;
                bool active2 = offerOut.Active;
                if (active1 && offerIn.Vehicle != 0)
                {
                    Array16 <Vehicle> vehicles = Singleton <VehicleManager> .instance.m_vehicles;
                    ushort            vehicle  = offerIn.Vehicle;
                    VehicleInfo       info     = vehicles.m_buffer[vehicle].Info;
                    offerOut.Amount = delta;
                    info.m_vehicleAI.StartTransfer(vehicle, ref vehicles.m_buffer[vehicle], material, offerOut);
                }
                else if (active2 && offerOut.Vehicle != 0)
                {
                    Array16 <Vehicle> vehicles = Singleton <VehicleManager> .instance.m_vehicles;
                    ushort            vehicle  = offerOut.Vehicle;
                    VehicleInfo       info     = vehicles.m_buffer[vehicle].Info;
                    offerIn.Amount = delta;
                    info.m_vehicleAI.StartTransfer(vehicle, ref vehicles.m_buffer[vehicle], material, offerIn);
                }
                else if (active1 && (int)offerIn.Citizen != 0)
                {
                    Array32 <Citizen> citizens = Singleton <CitizenManager> .instance.m_citizens;
                    uint        citizen        = offerIn.Citizen;
                    CitizenInfo citizenInfo    = citizens.m_buffer[citizen].GetCitizenInfo(citizen);
                    if (citizenInfo == null)
                    {
                        return(false);
                    }
                    offerOut.Amount = delta;

                    // Workaround a bug in ResidentAI.StartTransfer
                    if (material == TransferManager.TransferReason.ChildCare || material == TransferManager.TransferReason.ElderCare)
                    {
                        citizens.m_buffer[citizen].Sick = false;
                    }

                    citizenInfo.m_citizenAI.StartTransfer(citizen, ref citizens.m_buffer[citizen], material, offerOut);
                }
                else if (active2 && (int)offerOut.Citizen != 0)
                {
                    Array32 <Citizen> citizens = Singleton <CitizenManager> .instance.m_citizens;
                    uint        citizen        = offerOut.Citizen;
                    CitizenInfo citizenInfo    = citizens.m_buffer[citizen].GetCitizenInfo(citizen);
                    if (citizenInfo == null)
                    {
                        return(false);
                    }
                    offerIn.Amount = delta;

                    // Workaround a bug in ResidentAI.StartTransfer
                    if (material == TransferManager.TransferReason.ChildCare || material == TransferManager.TransferReason.ElderCare)
                    {
                        citizens.m_buffer[citizen].Sick = false;
                    }

                    citizenInfo.m_citizenAI.StartTransfer(citizen, ref citizens.m_buffer[citizen], material, offerIn);
                }
                else if (active2 && offerOut.Building != 0)
                {
                    Array16 <Building> buildings = Singleton <BuildingManager> .instance.m_buildings;
                    ushort             building  = offerOut.Building;
                    BuildingInfo       info      = buildings.m_buffer[building].Info;
                    offerIn.Amount = delta;
                    info.m_buildingAI.StartTransfer(building, ref buildings.m_buffer[building], material, offerIn);
                }
                else
                {
                    if (!active1 || offerIn.Building == 0)
                    {
                        return(false);
                    }
                    Array16 <Building> buildings = Singleton <BuildingManager> .instance.m_buildings;
                    ushort             building  = offerIn.Building;
                    BuildingInfo       info      = buildings.m_buffer[building].Info;
                    offerOut.Amount = delta;
                    info.m_buildingAI.StartTransfer(building, ref buildings.m_buffer[building], material, offerOut);
                }

                return(true);
            }
            finally
            {
                VehicleManagerMod.CurrentSourceBuilding = 0;
            }
        }