diff --git a/Content.Server/Cargo/Systems/CargoSystem.Orders.cs b/Content.Server/Cargo/Systems/CargoSystem.Orders.cs index 0938052c07..9160eb44ca 100644 --- a/Content.Server/Cargo/Systems/CargoSystem.Orders.cs +++ b/Content.Server/Cargo/Systems/CargoSystem.Orders.cs @@ -14,6 +14,7 @@ using Content.Shared.Cargo.Prototypes; using Content.Shared.Database; using Content.Shared.GameTicking; using Content.Server.Paper; +using Content.Shared.Access.Components; using Robust.Server.GameObjects; using Robust.Shared.Map; using Robust.Shared.Players; @@ -243,7 +244,7 @@ namespace Content.Server.Cargo.Systems return new CargoOrderData(id, args.ProductId, args.Amount, args.Requester, args.Reason); } - private int GetOutstandingOrderCount(StationCargoOrderDatabaseComponent component) + public int GetOutstandingOrderCount(StationCargoOrderDatabaseComponent component) { var amount = 0; @@ -285,7 +286,31 @@ namespace Content.Server.Cargo.Systems } } - public bool TryAddOrder(StationCargoOrderDatabaseComponent component, CargoOrderData data) + public bool AddAndApproveOrder(StationCargoOrderDatabaseComponent component, string productId, int qty, string sender, string description, string dest) + { + if (!_prototypeManager.HasIndex(productId)) + { + _sawmill.Warning($"CargoSystem.Orders could not find CargoProductPrototype for '{productId}' in {description}."); + // Pretend that it worked OK, since we don't want the caller to try again. + return true; + } + + // Make an order + var id = GenerateOrderId(component); + var order = new CargoOrderData(id, productId, qty, sender, description); + + // Approve it now + order.SetApproverData(new IdCardComponent(){FullName = dest, JobTitle = sender}); + + // Log order addition + _adminLogger.Add(LogType.Action, LogImpact.Low, + $"AddAndApproveOrder {description} added order [orderId:{order.OrderId}, quantity:{order.OrderQuantity}, product:{order.ProductId}, requester:{order.Requester}, reason:{order.Reason}]"); + + // Add it to the list + return TryAddOrder(component, order); + } + + private bool TryAddOrder(StationCargoOrderDatabaseComponent component, CargoOrderData data) { component.Orders.Add(data); UpdateOrders(component); @@ -294,7 +319,7 @@ namespace Content.Server.Cargo.Systems private int GenerateOrderId(StationCargoOrderDatabaseComponent orderDB) { - // We need an arbitrary unique ID to idenitfy orders, since they may + // We need an arbitrary unique ID to identify orders, since they may // want to be cancelled later. return ++orderDB.NumOrdersCreated; } diff --git a/Content.Server/Cargo/Systems/CargoSystem.Shuttle.cs b/Content.Server/Cargo/Systems/CargoSystem.Shuttle.cs index f9cc9e6dcf..f104a84ed8 100644 --- a/Content.Server/Cargo/Systems/CargoSystem.Shuttle.cs +++ b/Content.Server/Cargo/Systems/CargoSystem.Shuttle.cs @@ -282,7 +282,7 @@ public sealed partial class CargoSystem var numToShip = order.OrderQuantity - order.NumDispatched; if (numToShip > spaceRemaining) { - // We won't be able to fit the whole oder on, so make one + // We won't be able to fit the whole order on, so make one // which represents the space we do have left: var reducedOrder = new CargoOrderData(order.OrderId, order.ProductId, spaceRemaining, order.Requester, order.Reason); diff --git a/Content.Server/StationEvents/Components/CargoGiftsRuleComponent.cs b/Content.Server/StationEvents/Components/CargoGiftsRuleComponent.cs new file mode 100644 index 0000000000..bb4f834cb3 --- /dev/null +++ b/Content.Server/StationEvents/Components/CargoGiftsRuleComponent.cs @@ -0,0 +1,55 @@ +using Content.Server.StationEvents.Events; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Server.StationEvents.Components; + +/// +/// Used an event that gifts the station with certian cargo +/// +[RegisterComponent, Access(typeof(CargoGiftsRule))] +public sealed class CargoGiftsRuleComponent : Component +{ + /// + /// The base announcement string (which then incorporates the strings below) + /// + [DataField("announce"), ViewVariables(VVAccess.ReadWrite)] + public string Announce = "cargo-gifts-event-announcement"; + + /// + /// What is being sent + /// + [DataField("description"), ViewVariables(VVAccess.ReadWrite)] + public string Description = "cargo-gift-default-description"; + + /// + /// Sender of the gifts + /// + [DataField("sender"), ViewVariables(VVAccess.ReadWrite)] + public string Sender = "cargo-gift-default-sender"; + + /// + /// Destination of the gifts (who they get sent to on the station) + /// + [DataField("dest"), ViewVariables(VVAccess.ReadWrite)] + public string Dest = "cargo-gift-default-dest"; + + /// + /// Cargo that you would like gifted to the station, with the quantity for each + /// Use Ids from cargoProduct Prototypes + /// + [DataField("gifts"), ViewVariables(VVAccess.ReadWrite)] + public Dictionary Gifts = new Dictionary(); + + /// + /// How much space (minimum) you want to leave in the order database for supply to actually do their work + /// + [DataField("orderSpaceToLeave"), ViewVariables(VVAccess.ReadWrite)] + public int OrderSpaceToLeave = 5; + + /// + /// Time until we consider next lot of gifts (if supply is overflowing with orders) + /// + [DataField("timeUntilNextGifts"), ViewVariables(VVAccess.ReadWrite)] + public float TimeUntilNextGifts = 10.0f; +} diff --git a/Content.Server/StationEvents/Events/CargoGiftsRule.cs b/Content.Server/StationEvents/Events/CargoGiftsRule.cs new file mode 100644 index 0000000000..496355fd22 --- /dev/null +++ b/Content.Server/StationEvents/Events/CargoGiftsRule.cs @@ -0,0 +1,84 @@ +using System.Linq; +using Content.Server.Anomaly; +using Content.Server.Cargo.Components; +using Content.Server.Cargo.Systems; +using Content.Server.GameTicking; +using Content.Server.GameTicking.Rules.Components; +using Content.Server.Station.Components; +using Content.Server.Station.Systems; +using Content.Server.StationEvents.Components; +using Content.Shared.Access.Components; +using Content.Shared.Administration.Logs; +using Content.Shared.Cargo; +using Content.Shared.Cargo.Prototypes; +using Content.Shared.Database; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; +using Robust.Shared.Utility; + +namespace Content.Server.StationEvents.Events; + +public sealed class CargoGiftsRule : StationEventSystem +{ + [Dependency] private readonly CargoSystem _cargoSystem = default!; + [Dependency] private readonly StationSystem _stationSystem = default!; + [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly GameTicker _ticker = default!; + + protected override void Added(EntityUid uid, CargoGiftsRuleComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args) + { + base.Added(uid, component, gameRule, args); + + var str = Loc.GetString(component.Announce, + ("sender", Loc.GetString(component.Sender)), ("description", Loc.GetString(component.Description)), ("dest", Loc.GetString(component.Dest))); + ChatSystem.DispatchGlobalAnnouncement(str, colorOverride: Color.FromHex("#18abf5")); + } + + /// + /// Called on an active gamerule entity in the Update function + /// + protected override void ActiveTick(EntityUid uid, CargoGiftsRuleComponent component, GameRuleComponent gameRule, float frameTime) + { + if (component.Gifts.Count == 0) + { + return; + } + + if (component.TimeUntilNextGifts > 0) + { + component.TimeUntilNextGifts -= frameTime; + return; + } + component.TimeUntilNextGifts = 30f; + + if (!TryGetRandomStation(out var station, HasComp)) + return; + + if (!TryComp(station, out var cargoDb)) + { + return; + } + + // Add some presents + int outstanding = _cargoSystem.GetOutstandingOrderCount(cargoDb); + while (outstanding < cargoDb.Capacity - component.OrderSpaceToLeave && component.Gifts.Count > 0) + { + // I wish there was a nice way to pop this + var (productId, qty) = component.Gifts.First(); + component.Gifts.Remove(productId); + + if (!_cargoSystem.AddAndApproveOrder(cargoDb, productId, qty, Loc.GetString(component.Sender), Loc.GetString(component.Description), Loc.GetString(component.Dest))) + { + break; + } + } + + if (component.Gifts.Count == 0) + { + // We're done here! + _ticker.EndGameRule(uid, gameRule); + } + } + +} diff --git a/Resources/Locale/en-US/station-events/events/cargo-gifts.ftl b/Resources/Locale/en-US/station-events/events/cargo-gifts.ftl new file mode 100644 index 0000000000..08fa099d08 --- /dev/null +++ b/Resources/Locale/en-US/station-events/events/cargo-gifts.ftl @@ -0,0 +1,23 @@ +cargo-gifts-event-announcement = Congratulations! { $sender } has decided to send { $description } to the station { $dest }. Look for it in your next cargo shipment. +cargo-gift-default-description = A bundle of gifts +cargo-gift-default-sender = NanoTrasen +cargo-gift-default-dest = Cargo Dept. + +cargo-gift-dest-bar = bar +cargo-gift-dest-eng = Engineering Dept +cargo-gift-dest-supp = Cargo Dept +cargo-gift-dest-janitor = Service Dept +cargo-gift-dest-med = Medical Dept +cargo-gift-dest-sec = Security Dept + +cargo-gift-pizza-small = A small pizza party +cargo-gift-pizza-large = A large pizza party + +cargo-gift-eng = Repair Materials +cargo-gift-vending = Vending machines refills +cargo-gift-cleaning = Cleaning equipment +cargo-gift-medical-supply = Medical supplies +cargo-gift-space-protection = Space Hazard Protection +cargo-gift-fire-protection = Fire Protection +cargo-gift-security-guns = Lethal Weapons +cargo-gift-security-riot = Riot Gear diff --git a/Resources/Prototypes/Catalog/Cargo/cargo_emergency.yml b/Resources/Prototypes/Catalog/Cargo/cargo_emergency.yml index 7e931bc4c2..cda8f60e45 100644 --- a/Resources/Prototypes/Catalog/Cargo/cargo_emergency.yml +++ b/Resources/Prototypes/Catalog/Cargo/cargo_emergency.yml @@ -28,6 +28,16 @@ category: Emergency group: market +- type: cargoProduct + id: EmergencyInternalsLarge + icon: + sprite: Clothing/Mask/breath.rsi + state: icon + product: CrateEmergencyInternalsLarge + cost: 2000 + category: Emergency + group: market + - type: cargoProduct id: EmergencyRadiation icon: diff --git a/Resources/Prototypes/Catalog/Cargo/cargo_food.yml b/Resources/Prototypes/Catalog/Cargo/cargo_food.yml index f757080fb7..c38535bb56 100644 --- a/Resources/Prototypes/Catalog/Cargo/cargo_food.yml +++ b/Resources/Prototypes/Catalog/Cargo/cargo_food.yml @@ -8,6 +8,16 @@ category: Food group: market +- type: cargoProduct + id: FoodPizzaLarge + icon: + sprite: Objects/Consumable/Food/Baked/pizza.rsi + state: margherita + product: CrateFoodPizzaLarge + cost: 1800 + category: Food + group: market + - type: cargoProduct id: FoodMRE icon: @@ -47,3 +57,23 @@ cost: 750 category: Food group: market + +- type: cargoProduct + id: FoodSoftdrinks + icon: + sprite: Objects/Consumable/Drinks/beerglass.rsi + state: icon + product: CrateFoodSoftdrinks + cost: 1200 + category: Food + group: market + +- type: cargoProduct + id: FoodSoftdrinksLarge + icon: + sprite: Objects/Consumable/Drinks/beerglass.rsi + state: icon + product: CrateFoodSoftdrinksLarge + cost: 2400 + category: Food + group: market diff --git a/Resources/Prototypes/Catalog/Fills/Crates/emergency.yml b/Resources/Prototypes/Catalog/Fills/Crates/emergency.yml index 956664ec65..31cb6e0910 100644 --- a/Resources/Prototypes/Catalog/Fills/Crates/emergency.yml +++ b/Resources/Prototypes/Catalog/Fills/Crates/emergency.yml @@ -33,6 +33,7 @@ - type: entity id: CrateEmergencyInternals parent: CrateInternals + name: Emergency Suits components: - type: StorageFill contents: @@ -42,11 +43,29 @@ amount: 3 - id: OxygenTankFilled amount: 3 - - id: EmergencyOxygenTankFilled + - id: NitrogenTankFilled amount: 3 - id: ClothingOuterSuitEmergency amount: 3 +- type: entity + id: CrateEmergencyInternalsLarge + parent: CrateInternals + name: Emergency Suits (Large) + components: + - type: StorageFill + contents: + - id: ClothingMaskGas + amount: 12 + - id: ClothingMaskBreath + amount: 12 + - id: OxygenTankFilled + amount: 12 + - id: NitrogenTankFilled + amount: 12 + - id: ClothingOuterSuitEmergency + amount: 12 + - type: entity id: CrateSlimepersonLifeSupport parent: CrateInternals diff --git a/Resources/Prototypes/Catalog/Fills/Crates/food.yml b/Resources/Prototypes/Catalog/Fills/Crates/food.yml index e81c8dec93..5813521424 100644 --- a/Resources/Prototypes/Catalog/Fills/Crates/food.yml +++ b/Resources/Prototypes/Catalog/Fills/Crates/food.yml @@ -10,6 +10,19 @@ - id: LidSalami prob: 0.01 +- type: entity + id: CrateFoodPizzaLarge + parent: CratePlastic + components: + - type: StorageFill + contents: + - id: FoodBoxPizzaFilled + amount: 16 + - id: KnifePlastic + amount: 4 + - id: LidSalami + prob: 0.04 + - type: entity id: CrateFoodMRE parent: CratePlastic @@ -90,4 +103,43 @@ - id: DrinkSodaWaterCan amount: 2 - id: DrinkCreamCarton - amount: 2 \ No newline at end of file + amount: 2 + +- type: entity + id: CrateFoodSoftdrinks + parent: CratePlastic + components: + - type: StorageFill + contents: + - id: DrinkColaCan + amount: 4 + - id: DrinkGrapeCan + amount: 2 + - id: DrinkRootBeerCan + amount: 2 + - id: DrinkIcedTeaCan + amount: 2 + - id: DrinkLemonLimeCan + amount: 2 + - id: DrinkFourteenLokoCan + amount: 2 + +- type: entity + id: CrateFoodSoftdrinksLarge + parent: CratePlastic + components: + - type: StorageFill + contents: + - id: DrinkColaCan + amount: 8 + - id: DrinkGrapeCan + amount: 4 + - id: DrinkRootBeerCan + amount: 4 + - id: DrinkIcedTeaCan + amount: 4 + - id: DrinkLemonLimeCan + amount: 4 + - id: DrinkFourteenLokoCan + amount: 4 + diff --git a/Resources/Prototypes/GameRules/cargo_gifts.yml b/Resources/Prototypes/GameRules/cargo_gifts.yml new file mode 100644 index 0000000000..aaf4cbadc0 --- /dev/null +++ b/Resources/Prototypes/GameRules/cargo_gifts.yml @@ -0,0 +1,210 @@ +- type: entity + id: GiftsPizzaPartySmall + parent: BaseGameRule + noSpawn: true + components: + - type: StationEvent + weight: 10 + startDelay: 10 + duration: 120 + earliestStart: 20 + - type: CargoGiftsRule + description: cargo-gift-pizza-small + sender: cargo-gift-default-sender + dest: cargo-gift-dest-bar + gifts: + FoodPizzaLarge: 1 # 16 pizzas + FoodBarSupply: 1 + FoodSoftdrinks: 1 + CrateVendingMachineRestockRobustSoftdrinks: 1 + +- type: entity + id: GiftsPizzaPartyLarge + parent: BaseGameRule + noSpawn: true + components: + - type: StationEvent + weight: 6 + startDelay: 10 + duration: 240 + minimumPlayers: 50 + earliestStart: 40 + - type: CargoGiftsRule + description: cargo-gift-pizza-large + sender: cargo-gift-default-sender + dest: cargo-gift-dest-bar + gifts: + FoodPizzaLarge: 4 # 64 pizzas + FoodBarSupply: 1 + FoodSoftdrinksLarge: 1 + +- type: entity + id: GiftsEngineering + parent: BaseGameRule + noSpawn: true + components: + - type: StationEvent + weight: 4 + startDelay: 10 + duration: 240 + earliestStart: 30 + minimumPlayers: 10 + - type: CargoGiftsRule + description: cargo-gift-eng + sender: cargo-gift-default-sender + dest: cargo-gift-dest-eng + gifts: + EngineeringCableBulk: 1 + AirlockKit: 1 + MaterialSteel: 1 + MaterialPlasteel: 1 + MaterialGlass: 1 + CrateVendingMachineRestockEngineering: 1 + +- type: entity + id: GiftsVendingRestock + parent: BaseGameRule + noSpawn: true + components: + - type: StationEvent + weight: 4 + startDelay: 10 + duration: 120 + minimumPlayers: 40 + earliestStart: 30 + - type: CargoGiftsRule + description: cargo-gift-vending + sender: cargo-gift-default-sender + dest: cargo-gift-dest-supp + gifts: + CrateVendingMachineRestockHotDrinks: 3 + CrateVendingMachineRestockBooze: 1 + CrateVendingMachineRestockNutriMax: 1 + CrateVendingMachineRestockRobustSoftdrinks: 2 + CrateVendingMachineRestockVendomat: 1 + CrateVendingMachineRestockGetmoreChocolateCorp: 1 + +- type: entity + id: GiftsJanitor + parent: BaseGameRule + noSpawn: true + components: + - type: StationEvent + weight: 8 + startDelay: 10 + duration: 120 + minimumPlayers: 30 + earliestStart: 20 + - type: CargoGiftsRule + description: cargo-gift-cleaning + sender: cargo-gift-default-sender + dest: cargo-gift-dest-janitor + gifts: + ServiceJanitorial: 2 + ServiceLightsReplacement: 2 + ServiceJanitorBiosuit: 1 + +- type: entity + id: GiftsMedical + parent: BaseGameRule + noSpawn: true + components: + - type: StationEvent + weight: 8 + startDelay: 10 + duration: 120 + earliestStart: 20 + minimumPlayers: 30 + - type: CargoGiftsRule + description: cargo-gift-medical-supply + sender: cargo-gift-default-sender + dest: cargo-gift-dest-med + gifts: + MedicalSupplies: 1 + MedicalChemistrySupplies: 1 + EmergencyBruteKit: 1 + EmergencyAdvancedKit: 1 + MedicalBiosuit: 1 + +- type: entity + id: GiftsSpacingSupplies + parent: BaseGameRule + noSpawn: true + components: + - type: StationEvent + weight: 6 + startDelay: 10 + duration: 120 + earliestStart: 10 + minimumPlayers: 40 + - type: CargoGiftsRule + description: cargo-gift-space-protection + sender: cargo-gift-default-sender + dest: cargo-gift-dest-supp + gifts: + EmergencyInternalsLarge: 2 + EmergencyInflatablewall: 1 + EmergencyAdvancedKit: 1 + MedicalBiosuit: 1 + EmergencyO2Kit: 1 + +- type: entity + id: GiftsFireProtection + parent: BaseGameRule + noSpawn: true + components: + - type: StationEvent + weight: 3 + startDelay: 10 + duration: 120 + earliestStart: 20 + minimumPlayers: 40 + - type: CargoGiftsRule + description: cargo-gift-fire-protection + sender: cargo-gift-default-sender + dest: cargo-gift-dest-supp + gifts: + EmergencyFire: 2 + EmergencyBurnKit: 1 + EmergencyBruteKit: 1 + +- type: entity + id: GiftsSecurityGuns + parent: BaseGameRule + noSpawn: true + components: + - type: StationEvent + weight: 2 + startDelay: 10 + duration: 120 + earliestStart: 20 + minimumPlayers: 50 + - type: CargoGiftsRule + description: cargo-gift-security-guns + sender: cargo-gift-default-sender + dest: cargo-gift-dest-sec + gifts: + CrateSecurityArmor: 3 + ArmorySmg: 1 + ArmoryShotgun: 1 + ArmoryLaser: 1 + +- type: entity + id: GiftsSecurityRiot + parent: BaseGameRule + noSpawn: true + components: + - type: StationEvent + weight: 4 + startDelay: 10 + duration: 120 + earliestStart: 20 + minimumPlayers: 50 + - type: CargoGiftsRule + description: cargo-gift-security-riot + sender: cargo-gift-default-sender + dest: cargo-gift-dest-sec + gifts: + SecurityRiot: 2 + CrateRestraints: 2 + CrateSecurityNonlethal: 2