/// <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}) ..."); } }
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); }
/// <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); }
/// <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; } }
/// <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())); }
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); }
/// <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; } }