292 lines
12 KiB
C#
292 lines
12 KiB
C#
using Content.Server.Cargo.Components;
|
|
using Content.Server._NF.Cargo.Components;
|
|
using Content.Shared._NF.Bank.Components;
|
|
using Content.Shared._NF.Cargo.BUI;
|
|
using Content.Shared.Cargo;
|
|
using Content.Shared.Cargo.Events;
|
|
using Content.Shared.GameTicking;
|
|
using Content.Shared.Mobs;
|
|
using Robust.Shared.Audio;
|
|
using Robust.Shared.Map;
|
|
using System.Numerics;
|
|
using Content.Shared.Coordinates;
|
|
using Robust.Shared.Random;
|
|
|
|
namespace Content.Server._NF.Cargo.Systems;
|
|
|
|
/// <summary>
|
|
/// Handles cargo pallet (sale) mechanics.
|
|
/// Based off of Wizden's CargoSystem.
|
|
/// </summary>
|
|
public sealed partial class NFCargoSystem
|
|
{
|
|
// The maximum distance from the console to look for pallets.
|
|
private const int DefaultPalletDistance = 8;
|
|
|
|
private static readonly SoundPathSpecifier ApproveSound = new("/Audio/Effects/Cargo/ping.ogg");
|
|
|
|
private void InitializeShuttle()
|
|
{
|
|
SubscribeLocalEvent<NFCargoPalletConsoleComponent, CargoPalletSellMessage>(OnPalletSale);
|
|
SubscribeLocalEvent<NFCargoPalletConsoleComponent, CargoPalletAppraiseMessage>(OnPalletAppraise);
|
|
SubscribeLocalEvent<NFCargoPalletConsoleComponent, BoundUIOpenedEvent>(OnPalletUIOpen);
|
|
|
|
SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRoundRestart);
|
|
}
|
|
|
|
#region Console
|
|
|
|
private void UpdatePalletConsoleInterface(Entity<NFCargoPalletConsoleComponent> ent, EntityUid actor) // Frontier: EntityUid<Entity // Horizon - добавил поле actor
|
|
{
|
|
if (Transform(ent).GridUid is not EntityUid gridUid)
|
|
{
|
|
_ui.SetUiState(ent.Owner, CargoPalletConsoleUiKey.Sale,
|
|
new NFCargoPalletConsoleInterfaceState(0, 0, false));
|
|
return;
|
|
}
|
|
|
|
// Modify prices based on modifier.
|
|
GetPalletGoods(ent, gridUid, actor, out var toSell, out var amount, out var noModAmount, out Dictionary<string, double> additionalCurrency);
|
|
if (TryComp<MarketModifierComponent>(ent, out var priceMod))
|
|
{
|
|
amount *= priceMod.Mod;
|
|
}
|
|
amount += noModAmount;
|
|
|
|
_ui.SetUiState(ent.Owner, CargoPalletConsoleUiKey.Sale,
|
|
new NFCargoPalletConsoleInterfaceState((int)amount, toSell.Count, true));
|
|
}
|
|
|
|
private void OnPalletUIOpen(Entity<NFCargoPalletConsoleComponent> ent, ref BoundUIOpenedEvent args)
|
|
{
|
|
UpdatePalletConsoleInterface(ent, args.Actor); // Horizon - добавил поле actor
|
|
}
|
|
|
|
/// <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(Entity<NFCargoPalletConsoleComponent> ent, ref CargoPalletAppraiseMessage args)
|
|
{
|
|
UpdatePalletConsoleInterface(ent, args.Actor); // Horizon - добавил поле actor
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Shuttle
|
|
|
|
/// <summary>
|
|
/// Calculates distance between two EntityCoordinates
|
|
/// Used to check for cargo pallets around the console instead of on the grid.
|
|
/// </summary>
|
|
/// <param name="point1">first point to get distance between</param>
|
|
/// <param name="point2">second point to get distance between</param>
|
|
/// <returns></returns>
|
|
public static double CalculateDistance(EntityCoordinates point1, EntityCoordinates point2)
|
|
{
|
|
var xDifference = point2.X - point1.X;
|
|
var yDifference = point2.Y - point1.Y;
|
|
|
|
return Math.Sqrt(xDifference * xDifference + yDifference * yDifference);
|
|
}
|
|
|
|
/// 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 consoleUid, EntityUid gridUid, BuySellType requestType = BuySellType.All)
|
|
{
|
|
_pads.Clear();
|
|
|
|
if (!TryComp(consoleUid, out TransformComponent? consoleXform))
|
|
return _pads;
|
|
|
|
var query = AllEntityQuery<CargoPalletComponent, TransformComponent>();
|
|
while (query.MoveNext(out var uid, out var comp, out var compXform))
|
|
{
|
|
// Short-path easy checks
|
|
if (compXform.ParentUid != gridUid
|
|
|| !compXform.Anchored
|
|
|| (requestType & comp.PalletType) == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Check distance on pallets
|
|
var distance = CalculateDistance(compXform.Coordinates, consoleXform.Coordinates);
|
|
var maxPalletDistance = DefaultPalletDistance;
|
|
|
|
// Get the mapped checking distance from the console
|
|
if (TryComp<NFCargoPalletConsoleComponent>(consoleUid, out var cargoShuttleComponent))
|
|
maxPalletDistance = cargoShuttleComponent.PalletDistance;
|
|
|
|
if (distance > maxPalletDistance)
|
|
continue;
|
|
|
|
_pads.Add((uid, comp, compXform));
|
|
|
|
}
|
|
|
|
return _pads;
|
|
}
|
|
#endregion
|
|
|
|
#region Station
|
|
|
|
private bool SellPallets(Entity<NFCargoPalletConsoleComponent> consoleUid, EntityUid gridUid, EntityUid actor, out double amount, out double noMultiplierAmount, out Dictionary<string, double> additionalCurrency) // Frontier: first arg to Entity, add noMultiplierAmount // Horizon - добавил поля actor и cash
|
|
{
|
|
GetPalletGoods(consoleUid, gridUid, actor, out var toSell, out amount, out noMultiplierAmount, out additionalCurrency); // Horizon - добавил поле actor
|
|
|
|
Log.Debug($"Cargo sold {toSell.Count} entities for {amount} (plus {noMultiplierAmount} without mods)");
|
|
|
|
if (toSell.Count == 0)
|
|
return false;
|
|
|
|
var ev = new NFEntitySoldEvent(toSell, gridUid, actor); // Horizon - добавил поле actor
|
|
RaiseLocalEvent(ref ev);
|
|
|
|
foreach (var ent in toSell)
|
|
Del(ent);
|
|
|
|
return true;
|
|
}
|
|
|
|
private void GetPalletGoods(Entity<NFCargoPalletConsoleComponent> consoleUid, EntityUid gridUid, EntityUid actor, out HashSet<EntityUid> toSell, out double amount, out double noMultiplierAmount, out Dictionary<string, double> additionalCurrency) // Frontier: first arg to Entity, add noMultiplierAmount // Horizon - добавил поле actor
|
|
{
|
|
amount = 0;
|
|
noMultiplierAmount = 0;
|
|
toSell = new HashSet<EntityUid>();
|
|
additionalCurrency = new();
|
|
|
|
foreach (var (palletUid, _, _) in GetCargoPallets(consoleUid, 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 (_whitelist.IsWhitelistFail(consoleUid.Comp.Whitelist, ent))
|
|
continue;
|
|
|
|
if (_blacklistQuery.HasComponent(ent))
|
|
continue;
|
|
|
|
var price = _pricing.GetPrice(ent, currency: consoleUid.Comp.CashType, user: actor);
|
|
if (price == 0)
|
|
continue;
|
|
toSell.Add(ent);
|
|
|
|
// Check for items that are immune to market modifiers
|
|
if (HasComp<IgnoreMarketModifierComponent>(ent))
|
|
noMultiplierAmount += price;
|
|
else
|
|
amount += price;
|
|
|
|
// Check for any additional currency payouts
|
|
if (TryComp(ent, out AdditionalPalletCurrencyComponent? currencyComponent))
|
|
{
|
|
if (_random.Prob(currencyComponent.SpawnProbability))
|
|
{
|
|
if (!additionalCurrency.ContainsKey(currencyComponent.Currency))
|
|
{
|
|
additionalCurrency.TryAdd(currencyComponent.Currency, 0);
|
|
}
|
|
additionalCurrency[currencyComponent.Currency] += currencyComponent.Amount;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private bool CanSell(EntityUid uid, TransformComponent xform)
|
|
{
|
|
// Look for blacklisted items and stop the selling of the container.
|
|
if (_blacklistQuery.HasComponent(uid))
|
|
return false;
|
|
|
|
// Allow selling dead mobs
|
|
if (_mobQuery.TryComp(uid, out var mob) && mob.CurrentState != MobState.Dead)
|
|
return false;
|
|
|
|
// NOTE: no bounties for now
|
|
// var complete = IsBountyComplete(uid, out var bountyEntities);
|
|
|
|
// Recursively check for mobs at any point.
|
|
var children = xform.ChildEnumerator;
|
|
while (children.MoveNext(out var child))
|
|
{
|
|
// NOTE: no bounties for now
|
|
// if (complete && bountyEntities.Contains(child))
|
|
// continue;
|
|
|
|
if (!CanSell(child, _xformQuery.GetComponent(child)))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private void OnPalletSale(Entity<NFCargoPalletConsoleComponent> ent, ref CargoPalletSellMessage args)
|
|
{
|
|
if (!TryComp(ent, out TransformComponent? xform))
|
|
return;
|
|
|
|
if (xform.GridUid is not EntityUid gridUid)
|
|
{
|
|
_ui.SetUiState(ent.Owner, CargoPalletConsoleUiKey.Sale,
|
|
new NFCargoPalletConsoleInterfaceState(0, 0, false));
|
|
return;
|
|
}
|
|
|
|
if (!SellPallets(ent, gridUid, args.Actor, out var price, out var noMultiplierPrice, out Dictionary<string, double> additionalCurrency)) // Horizon - добавил поле actor
|
|
return;
|
|
|
|
// Handle market modifiers & immune objects
|
|
if (TryComp<MarketModifierComponent>(ent, out var priceMod))
|
|
{
|
|
price *= priceMod.Mod;
|
|
}
|
|
price += noMultiplierPrice;
|
|
|
|
var stackPrototype = _proto.Index(ent.Comp.CashType);
|
|
var stackUid = _stack.Spawn((int)price, stackPrototype, args.Actor.ToCoordinates());
|
|
if (!_hands.TryPickupAnyHand(args.Actor, stackUid))
|
|
_transform.SetLocalRotation(stackUid, Angle.Zero); // Orient these to grid north instead of map north
|
|
|
|
// Iterate through additional currency payouts, putting them in hand if possible
|
|
foreach (var (currencyId, currencyAmount) in additionalCurrency)
|
|
{
|
|
var currencyUid = _stack.Spawn((int)currencyAmount, currencyId, args.Actor.ToCoordinates());
|
|
if (!_hands.TryPickupAnyHand(args.Actor, currencyUid))
|
|
_transform.SetLocalRotation(currencyUid, Angle.Zero);
|
|
}
|
|
_audio.PlayPvs(ApproveSound, ent);
|
|
UpdatePalletConsoleInterface(ent, args.Actor); // Horizon - добавил поле actor
|
|
}
|
|
|
|
#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 NFEntitySoldEvent(HashSet<EntityUid> Sold, EntityUid Grid, EntityUid Actor); // Horizon - добавил поле Actor
|