fish-station/Content.Server/Cargo/Systems/CargoSystem.Shuttle.cs
Vigers Ray d6c0a011b7 Merge remote-tracking branch 'space-wizards/master'
# Conflicts:
#	.github/workflows/publish.yml
#	Content.Client/Clothing/ClientClothingSystem.cs
#	Content.Client/Clothing/Systems/ChameleonClothingSystem.cs
#	Content.Client/Fax/UI/FaxWindow.xaml
#	Content.Client/Ghost/GhostSystem.cs
#	Content.Client/Holiday/HolidaySystem.cs
#	Content.Client/Humanoid/HumanoidAppearanceSystem.cs
#	Content.Client/Lobby/UI/Loadouts/LoadoutWindow.xaml.cs
#	Content.Client/Lobby/UI/LobbyGui.xaml
#	Content.Client/Lobby/UI/LobbyGui.xaml.cs
#	Content.Client/Mech/MechSystem.cs
#	Content.Client/Stealth/StealthSystem.cs
#	Content.Client/Weapons/Ranged/Systems/GunSystem.cs
#	Content.Server.Database/Migrations/Sqlite/SqliteServerDbContextModelSnapshot.cs
#	Content.Server/Bed/BedSystem.cs
#	Content.Server/Body/Systems/BodySystem.cs
#	Content.Server/Body/Systems/RespiratorSystem.cs
#	Content.Server/Cargo/Systems/CargoSystem.Orders.cs
#	Content.Server/Communications/CommunicationsConsoleComponent.cs
#	Content.Server/EntityEffects/Effects/AreaReactionEffect.cs
#	Content.Server/EntityEffects/Effects/MakeSentient.cs
#	Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustPests.cs
#	Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustToxins.cs
#	Content.Server/EntityEffects/Effects/PlantMetabolism/PlantAdjustWeeds.cs
#	Content.Server/EntityEffects/Effects/PlantMutateGases.cs
#	Content.Server/Fluids/EntitySystems/AbsorbentSystem.cs
#	Content.Server/GameTicking/GameTicker.Spawning.cs
#	Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs
#	Content.Server/IoC/ServerContentIoC.cs
#	Content.Server/Nutrition/EntitySystems/FoodSystem.cs
#	Content.Server/Power/EntitySystems/PowerNetSystem.cs
#	Content.Server/Preferences/Managers/ServerPreferencesManager.cs
#	Content.Server/RoundEnd/RoundEndSystem.cs
#	Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs
#	Content.Server/Station/Systems/StationSpawningSystem.cs
#	Content.Server/VoiceMask/VoiceMaskSystem.cs
#	Content.Server/Weapons/Ranged/Systems/GunSystem.cs
#	Content.Shared/Bed/Components/HealOnBuckleComponent.cs
#	Content.Shared/Clothing/EntitySystems/ClothingSystem.cs
#	Content.Shared/EntityEffects/Effects/ExplosionReactionEffect.cs
#	Content.Shared/Inventory/InventorySystem.Relay.cs
#	Content.Shared/Item/ItemToggle/Components/ComponentTogglerComponent.cs
#	Content.Shared/Preferences/HumanoidCharacterProfile.cs
#	Content.Shared/Preferences/Loadouts/RoleLoadout.cs
#	Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs
#	Content.Shared/Wieldable/SharedWieldableSystem.cs
#	Resources/Audio/Machines/attributions.yml
#	Resources/Locale/en-US/_strings/atmos/plaque-component.ftl
#	Resources/Locale/en-US/_strings/chat/chat-channel.ftl
#	Resources/Locale/en-US/_strings/components/screen-component.ftl
#	Resources/Locale/en-US/_strings/inventory/components/slot-block-component.ftl
#	Resources/Locale/en-US/_strings/mass-media/news-discord.ftl
#	Resources/Locale/en-US/_strings/objectives/commands/stripall.ftl
#	Resources/Locale/en-US/_strings/salvage/job-board.ftl
#	Resources/Locale/en-US/_strings/ui/spray-painter-window.ftl
#	Resources/Locale/en-US/_strings/ui/turret-controls.ftl
#	Resources/Maps/_Sunrise/Station/box.yml
#	Resources/Prototypes/Access/misc.yml
#	Resources/Prototypes/Catalog/Fills/Boxes/emergency.yml
#	Resources/Prototypes/Catalog/Fills/Lockers/engineer.yml
#	Resources/Prototypes/Catalog/Fills/Lockers/heads.yml
#	Resources/Prototypes/Catalog/Fills/Lockers/service.yml
#	Resources/Prototypes/Catalog/Fills/Lockers/suit_storage.yml
#	Resources/Prototypes/Entities/Clothing/Head/helmets.yml
#	Resources/Prototypes/Entities/Clothing/Masks/masks.yml
#	Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml
#	Resources/Prototypes/Entities/Markers/Spawners/mobs.yml
#	Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml
#	Resources/Prototypes/Entities/Mobs/NPCs/animals.yml
#	Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml
#	Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml
#	Resources/Prototypes/Entities/Mobs/Player/dragon.yml
#	Resources/Prototypes/Entities/Mobs/Player/silicon.yml
#	Resources/Prototypes/Entities/Mobs/Species/arachnid.yml
#	Resources/Prototypes/Entities/Mobs/Species/moth.yml
#	Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml
#	Resources/Prototypes/Entities/Objects/Weapons/Guns/Shotguns/shotguns.yml
#	Resources/Prototypes/LootTables/suspicion_loot_table.yml
#	Resources/Prototypes/Reagents/Consumable/Drink/drinks.yml
#	Resources/Prototypes/Reagents/medicine.yml
#	Resources/Prototypes/Recipes/Lathes/Packs/science.yml
#	Resources/Prototypes/Recipes/Lathes/medical.yml
#	Resources/Prototypes/SoundCollections/NukeMusic.yml
#	Resources/ServerInfo/Guidebook/Cargo/Salvage.xml
#	Resources/Textures/Clothing/Hands/Gloves/KnuckleDusters/brassknuckleduster.rsi/equipped-OUTERCLOTHING-reptilian.png
#	Resources/Textures/Clothing/OuterClothing/Hardsuits/basic.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/Suits/eva.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/Suits/eva_emergency.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/Suits/eva_prisoner.rsi/meta.json
#	Resources/Textures/Effects/speech.rsi/alien3.png
#	Resources/Textures/Effects/speech.rsi/alienroyal3.png
#	Resources/Textures/Effects/speech.rsi/default3.png
#	Resources/Textures/Effects/speech.rsi/guardian3.png
#	Resources/Textures/Effects/speech.rsi/holo3.png
#	Resources/Textures/Effects/speech.rsi/lawyer3.png
#	Resources/Textures/Effects/speech.rsi/lizard3.png
#	Resources/Textures/Effects/speech.rsi/meta.json
#	Resources/Textures/Effects/speech.rsi/moth3.png
#	Resources/Textures/Effects/speech.rsi/robot3.png
#	Resources/Textures/Effects/speech.rsi/slime3.png
#	Resources/Textures/Effects/speech.rsi/spider3.png
#	Resources/Textures/Effects/speech.rsi/syndibot3.png
#	Resources/Textures/Interface/Misc/job_icons.rsi/meta.json
#	Resources/Textures/Mobs/Silicon/chassis.rsi/meta.json
#	Resources/Textures/Mobs/Species/Reptilian/parts.rsi/head_f.png
#	Resources/Textures/Mobs/Species/Reptilian/parts.rsi/head_m.png
#	Resources/Textures/Objects/Misc/id_cards.rsi/meta.json
#	Resources/Textures/Structures/Doors/Airlocks/Glass/external.rsi/emergency_unlit.png
#	Resources/Textures/Structures/Piping/disposal.rsi/meta.json
#	Resources/Textures/Tiles/attributions.yml
#	Resources/Textures/Tiles/steel_dirty.png
#	Resources/migration.yml
#	runclient-Tools.bat
#	runclient.bat
2025-05-27 03:41:41 +03:00

280 lines
9.1 KiB
C#

using System.Linq;
using Content.Server.Cargo.Components;
using Content.Shared.Cargo;
using Content.Shared.Cargo.BUI;
using Content.Shared.Cargo.Components;
using Content.Shared.Cargo.Events;
using Content.Shared.Cargo.Prototypes;
using Content.Shared.CCVar;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
namespace Content.Server.Cargo.Systems;
public sealed partial class CargoSystem
{
/*
* Handles cargo shuttle / trade mechanics.
*/
private static readonly SoundPathSpecifier ApproveSound = new("/Audio/Effects/Cargo/ping.ogg");
private bool _lockboxCutEnabled;
private void InitializeShuttle()
{
SubscribeLocalEvent<TradeStationComponent, GridSplitEvent>(OnTradeSplit);
SubscribeLocalEvent<CargoPalletConsoleComponent, CargoPalletSellMessage>(OnPalletSale);
SubscribeLocalEvent<CargoPalletConsoleComponent, CargoPalletAppraiseMessage>(OnPalletAppraise);
SubscribeLocalEvent<CargoPalletConsoleComponent, BoundUIOpenedEvent>(OnPalletUIOpen);
_cfg.OnValueChanged(CCVars.LockboxCutEnabled, (enabled) => { _lockboxCutEnabled = enabled; }, true);
}
#region Console
private void UpdatePalletConsoleInterface(EntityUid uid)
{
if (Transform(uid).GridUid is not { } gridUid)
{
_uiSystem.SetUiState(uid,
CargoPalletConsoleUiKey.Sale,
new CargoPalletConsoleInterfaceState(0, 0, false));
return;
}
GetPalletGoods(gridUid, out var toSell, out var goods);
var totalAmount = goods.Sum(t => t.Item3);
_uiSystem.SetUiState(uid,
CargoPalletConsoleUiKey.Sale,
new CargoPalletConsoleInterfaceState((int) totalAmount, toSell.Count, true));
}
private void OnPalletUIOpen(EntityUid uid, CargoPalletConsoleComponent component, BoundUIOpenedEvent args)
{
UpdatePalletConsoleInterface(uid);
}
/// <summary>
/// Ok so this is just the same thing as opening the UI, its a refresh button.
/// I know this would probably feel better if it were like predicted and dynamic as pallet contents change
/// However.
/// I dont want it to explode if cargo uses a conveyor to move 8000 pineapple slices or whatever, they are
/// known for their entity spam i wouldnt put it past them
/// </summary>
private void OnPalletAppraise(EntityUid uid, CargoPalletConsoleComponent component, CargoPalletAppraiseMessage args)
{
UpdatePalletConsoleInterface(uid);
}
#endregion
private void OnTradeSplit(EntityUid uid, TradeStationComponent component, ref GridSplitEvent args)
{
// If the trade station gets bombed it's still a trade station.
foreach (var gridUid in args.NewGrids)
{
EnsureComp<TradeStationComponent>(gridUid);
}
}
#region Shuttle
/// GetCargoPallets(gridUid, BuySellType.Sell) to return only Sell pads
/// GetCargoPallets(gridUid, BuySellType.Buy) to return only Buy pads
private List<(EntityUid Entity, CargoPalletComponent Component, TransformComponent PalletXform)> GetCargoPallets(EntityUid gridUid, BuySellType requestType = BuySellType.All)
{
_pads.Clear();
var query = AllEntityQuery<CargoPalletComponent, TransformComponent>();
while (query.MoveNext(out var uid, out var comp, out var compXform))
{
if (compXform.ParentUid != gridUid ||
!compXform.Anchored)
{
continue;
}
if ((requestType & comp.PalletType) == 0)
{
continue;
}
_pads.Add((uid, comp, compXform));
}
return _pads;
}
private List<(EntityUid Entity, CargoPalletComponent Component, TransformComponent Transform)>
GetFreeCargoPallets(EntityUid gridUid,
List<(EntityUid Entity, CargoPalletComponent Component, TransformComponent Transform)> pallets)
{
_setEnts.Clear();
List<(EntityUid Entity, CargoPalletComponent Component, TransformComponent Transform)> outList = new();
foreach (var pallet in pallets)
{
var aabb = _lookup.GetAABBNoContainer(pallet.Entity, pallet.Transform.LocalPosition, pallet.Transform.LocalRotation);
if (_lookup.AnyLocalEntitiesIntersecting(gridUid, aabb, LookupFlags.Dynamic))
continue;
outList.Add(pallet);
}
return outList;
}
#endregion
#region Station
private bool SellPallets(EntityUid gridUid, EntityUid station, out HashSet<(EntityUid, OverrideSellComponent?, double)> goods)
{
GetPalletGoods(gridUid, out var toSell, out goods);
if (toSell.Count == 0)
return false;
var ev = new EntitySoldEvent(toSell, station);
RaiseLocalEvent(ref ev);
foreach (var ent in toSell)
{
Del(ent);
}
return true;
}
private void GetPalletGoods(EntityUid gridUid, out HashSet<EntityUid> toSell, out HashSet<(EntityUid, OverrideSellComponent?, double)> goods)
{
goods = new HashSet<(EntityUid, OverrideSellComponent?, double)>();
toSell = new HashSet<EntityUid>();
foreach (var (palletUid, _, _) in GetCargoPallets(gridUid, BuySellType.Sell))
{
// Containers should already get the sell price of their children so can skip those.
_setEnts.Clear();
_lookup.GetEntitiesIntersecting(
palletUid,
_setEnts,
LookupFlags.Dynamic | LookupFlags.Sundries);
foreach (var ent in _setEnts)
{
// Dont sell:
// - anything already being sold
// - anything anchored (e.g. light fixtures)
// - anything blacklisted (e.g. players).
if (toSell.Contains(ent) ||
_xformQuery.TryGetComponent(ent, out var xform) &&
(xform.Anchored || !CanSell(ent, xform)))
{
continue;
}
if (_blacklistQuery.HasComponent(ent))
continue;
var price = _pricing.GetPrice(ent);
if (price == 0)
continue;
toSell.Add(ent);
goods.Add((ent, CompOrNull<OverrideSellComponent>(ent), price));
}
}
}
private bool CanSell(EntityUid uid, TransformComponent xform)
{
if (_mobQuery.HasComponent(uid))
{
return false;
}
var complete = IsBountyComplete(uid, out var bountyEntities);
// Recursively check for mobs at any point.
var children = xform.ChildEnumerator;
while (children.MoveNext(out var child))
{
if (complete && bountyEntities.Contains(child))
continue;
if (!CanSell(child, _xformQuery.GetComponent(child)))
return false;
}
return true;
}
private void OnPalletSale(EntityUid uid, CargoPalletConsoleComponent component, CargoPalletSellMessage args)
{
// Sunrise-Start
var player = args.Actor;
if (!_accessReaderSystem.IsAllowed(player, uid))
{
ConsolePopup(args.Actor, Loc.GetString("cargo-console-order-not-allowed"));
PlayDenySound(uid, component.ErrorSound);
return;
}
// Sunrise-End
var xform = Transform(uid);
if (_station.GetOwningStation(uid) is not { } station ||
!TryComp<StationBankAccountComponent>(station, out var bankAccount))
{
return;
}
if (xform.GridUid is not { } gridUid)
{
_uiSystem.SetUiState(uid,
CargoPalletConsoleUiKey.Sale,
new CargoPalletConsoleInterfaceState(0, 0, false));
return;
}
if (!SellPallets(gridUid, station, out var goods))
return;
var baseDistribution = CreateAccountDistribution((station, bankAccount));
foreach (var (_, sellComponent, value) in goods)
{
Dictionary<ProtoId<CargoAccountPrototype>, double> distribution;
if (sellComponent != null)
{
var cut = _lockboxCutEnabled ? bankAccount.LockboxCut : bankAccount.PrimaryCut;
distribution = new Dictionary<ProtoId<CargoAccountPrototype>, double>
{
{ sellComponent.OverrideAccount, cut },
{ bankAccount.PrimaryAccount, 1.0 - cut },
};
}
else
{
distribution = baseDistribution;
}
UpdateBankAccount((station, bankAccount), (int) Math.Round(value), distribution, false);
}
Dirty(station, bankAccount);
_audio.PlayPvs(ApproveSound, uid);
UpdatePalletConsoleInterface(uid);
}
#endregion
}
/// <summary>
/// Event broadcast raised by-ref before it is sold and
/// deleted but after the price has been calculated.
/// </summary>
[ByRefEvent]
public readonly record struct EntitySoldEvent(HashSet<EntityUid> Sold, EntityUid Station);