622 lines
23 KiB
C#
622 lines
23 KiB
C#
using System.Diagnostics.CodeAnalysis;
|
|
using System.Linq;
|
|
using Content.Server._NF.Contraband.Components;
|
|
using Content.Server._NF.Pirate.Components;
|
|
using Content.Server.NameIdentifier;
|
|
using Content.Shared._NF.Bank;
|
|
using Content.Shared._NF.Pirate;
|
|
using Content.Shared._NF.Pirate.Components;
|
|
using Content.Shared._NF.Pirate.Prototypes;
|
|
using Content.Shared._NF.Pirate.Events;
|
|
using Content.Shared.Access.Components;
|
|
using Content.Shared.Database;
|
|
using Content.Shared.Labels.EntitySystems;
|
|
using Content.Shared.NameIdentifier;
|
|
using Content.Shared.Paper;
|
|
using Content.Shared.Stacks;
|
|
using JetBrains.Annotations;
|
|
using Robust.Shared.Containers;
|
|
using Robust.Shared.Random;
|
|
using Robust.Shared.Utility;
|
|
|
|
namespace Content.Server._NF.Cargo.Systems;
|
|
|
|
public sealed partial class NFCargoSystem
|
|
{
|
|
[Dependency] private readonly NameIdentifierSystem _nameIdentifier = default!;
|
|
|
|
[ValidatePrototypeId<NameIdentifierGroupPrototype>]
|
|
private const string PirateBountyNameIdentifierGroup = "Bounty"; // Use the bounty name ID group (0-999) for now.
|
|
|
|
private EntityQuery<ContainerManagerComponent> _containerQuery;
|
|
private EntityQuery<PirateBountyLabelComponent> _pirateBountyLabelQuery;
|
|
|
|
private readonly TimeSpan _redemptionDelay = TimeSpan.FromSeconds(2);
|
|
|
|
private void InitializePirateBounty()
|
|
{
|
|
SubscribeLocalEvent<PirateBountyConsoleComponent, BoundUIOpenedEvent>(OnPirateBountyConsoleOpened);
|
|
SubscribeLocalEvent<PirateBountyConsoleComponent, PirateBountyAcceptMessage>(OnPirateBountyAccept);
|
|
SubscribeLocalEvent<PirateBountyConsoleComponent, PirateBountySkipMessage>(OnSkipPirateBountyMessage);
|
|
|
|
SubscribeLocalEvent<PirateBountyRedemptionConsoleComponent, PirateBountyRedemptionMessage>(OnRedeemBounty);
|
|
|
|
SubscribeLocalEvent<SectorPirateBountyDatabaseComponent, MapInitEvent>(OnPirateMapInit);
|
|
|
|
_pirateBountyLabelQuery = GetEntityQuery<PirateBountyLabelComponent>();
|
|
_containerQuery = GetEntityQuery<ContainerManagerComponent>();
|
|
}
|
|
|
|
private void OnPirateBountyConsoleOpened(EntityUid uid, PirateBountyConsoleComponent component, BoundUIOpenedEvent args)
|
|
{
|
|
var service = _sectorService.GetServiceEntity();
|
|
if (!TryComp<SectorPirateBountyDatabaseComponent>(service, out var bountyDb))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var untilNextSkip = bountyDb.NextSkipTime - _timing.CurTime;
|
|
_ui.SetUiState(uid, PirateConsoleUiKey.Bounty, new PirateBountyConsoleState(bountyDb.Bounties, untilNextSkip));
|
|
}
|
|
|
|
private void OnPirateBountyAccept(EntityUid uid, PirateBountyConsoleComponent component, PirateBountyAcceptMessage args)
|
|
{
|
|
if (_timing.CurTime < component.NextPrintTime)
|
|
return;
|
|
|
|
var service = _sectorService.GetServiceEntity();
|
|
if (!TryGetPirateBountyFromId(service, args.BountyId, out var bounty))
|
|
return;
|
|
|
|
var bountyObj = bounty.Value;
|
|
|
|
// Check if the crate for this bounty has already been summoned. If not, create a new one.
|
|
if (bountyObj.Accepted || !_proto.TryIndex(bountyObj.Bounty, out var bountyPrototype))
|
|
return;
|
|
|
|
PirateBountyData bountyData = new PirateBountyData(bountyPrototype!, bountyObj.Id, true);
|
|
|
|
TryOverwritePirateBountyFromId(service, bountyData);
|
|
|
|
if (bountyPrototype.SpawnChest)
|
|
{
|
|
var chest = Spawn(component.BountyCrateId, Transform(uid).Coordinates);
|
|
SetupPirateBountyChest(chest, bountyData, bountyPrototype);
|
|
_audio.PlayPvs(component.SpawnChestSound, uid);
|
|
}
|
|
else
|
|
{
|
|
var label = Spawn(component.BountyLabelId, Transform(uid).Coordinates);
|
|
SetupPirateBountyManifest(label, bountyData, bountyPrototype);
|
|
_audio.PlayPvs(component.PrintSound, uid);
|
|
}
|
|
|
|
component.NextPrintTime = _timing.CurTime + component.PrintDelay;
|
|
UpdatePirateBountyConsoles();
|
|
}
|
|
|
|
private void OnSkipPirateBountyMessage(EntityUid uid, PirateBountyConsoleComponent component, PirateBountySkipMessage args)
|
|
{
|
|
var service = _sectorService.GetServiceEntity();
|
|
if (!TryComp<SectorPirateBountyDatabaseComponent>(service, out var db))
|
|
return;
|
|
|
|
if (_timing.CurTime < db.NextSkipTime)
|
|
return;
|
|
|
|
if (!TryGetPirateBountyFromId(service, args.BountyId, out var bounty))
|
|
return;
|
|
|
|
if (args.Actor is not { Valid: true } mob)
|
|
return;
|
|
|
|
if (TryComp<AccessReaderComponent>(uid, out var accessReaderComponent) &&
|
|
!_accessReader.IsAllowed(mob, uid, accessReaderComponent))
|
|
{
|
|
_audio.PlayPvs(component.DenySound, uid);
|
|
return;
|
|
}
|
|
|
|
if (!TryRemovePirateBounty(service, bounty.Value.Id))
|
|
return;
|
|
|
|
FillPirateBountyDatabase(service);
|
|
if (bounty.Value.Accepted)
|
|
db.NextSkipTime = _timing.CurTime + db.SkipDelay;
|
|
else
|
|
db.NextSkipTime = _timing.CurTime + db.CancelDelay;
|
|
|
|
var untilNextSkip = db.NextSkipTime - _timing.CurTime;
|
|
_ui.SetUiState(uid, PirateConsoleUiKey.Bounty, new PirateBountyConsoleState(db.Bounties, untilNextSkip));
|
|
_audio.PlayPvs(component.SkipSound, uid);
|
|
}
|
|
|
|
private void SetupPirateBountyChest(EntityUid uid, PirateBountyData bounty, PirateBountyPrototype prototype)
|
|
{
|
|
_meta.SetEntityName(uid, Loc.GetString("pirate-bounty-chest-name", ("id", bounty.Id)));
|
|
|
|
FormattedMessage message = new FormattedMessage();
|
|
message.TryAddMarkup(Loc.GetString("pirate-bounty-chest-description-start"), out var _);
|
|
foreach (var entry in prototype.Entries)
|
|
{
|
|
message.PushNewline();
|
|
message.TryAddMarkup($"- {Loc.GetString("pirate-bounty-console-manifest-entry",
|
|
("amount", entry.Amount),
|
|
("item", Loc.GetString(entry.Name)))}", out var _);
|
|
}
|
|
message.PushNewline();
|
|
message.TryAddMarkup(Loc.GetString("pirate-bounty-console-manifest-reward", ("reward", BankSystemExtensions.ToDoubloonString(prototype.Reward))), out var _);
|
|
|
|
_meta.SetEntityDescription(uid, message.ToMarkup());
|
|
|
|
if (TryComp<PirateBountyLabelComponent>(uid, out var label))
|
|
label.Id = bounty.Id;
|
|
}
|
|
|
|
private void SetupPirateBountyManifest(EntityUid uid, PirateBountyData bounty, PirateBountyPrototype prototype, PaperComponent? paper = null)
|
|
{
|
|
_meta.SetEntityName(uid, Loc.GetString("pirate-bounty-manifest-name", ("id", bounty.Id)));
|
|
|
|
if (!Resolve(uid, ref paper))
|
|
return;
|
|
|
|
var msg = new FormattedMessage();
|
|
msg.AddText(Loc.GetString("pirate-bounty-manifest-header", ("id", bounty.Id)));
|
|
msg.PushNewline();
|
|
msg.AddText(Loc.GetString("pirate-bounty-manifest-list-start"));
|
|
msg.PushNewline();
|
|
foreach (var entry in prototype.Entries)
|
|
{
|
|
msg.TryAddMarkup($"- {Loc.GetString("pirate-bounty-console-manifest-entry",
|
|
("amount", entry.Amount),
|
|
("item", Loc.GetString(entry.Name)))}", out var _);
|
|
msg.PushNewline();
|
|
}
|
|
msg.TryAddMarkup(Loc.GetString("pirate-bounty-console-manifest-reward", ("reward", BankSystemExtensions.ToDoubloonString(prototype.Reward))), out var _);
|
|
_paper.SetContent((uid, paper), msg.ToMarkup());
|
|
}
|
|
|
|
private bool TryGetPirateBountyLabel(EntityUid uid,
|
|
[NotNullWhen(true)] out EntityUid? labelEnt,
|
|
[NotNullWhen(true)] out PirateBountyLabelComponent? labelComp)
|
|
{
|
|
labelEnt = null;
|
|
labelComp = null;
|
|
if (!_containerQuery.TryGetComponent(uid, out var containerMan))
|
|
return false;
|
|
|
|
// make sure this label was actually applied to a crate.
|
|
if (!_container.TryGetContainer(uid, LabelSystem.ContainerName, out var container, containerMan))
|
|
return false;
|
|
|
|
if (container.ContainedEntities.FirstOrNull() is not { } label ||
|
|
!_pirateBountyLabelQuery.TryGetComponent(label, out var component))
|
|
return false;
|
|
|
|
labelEnt = label;
|
|
labelComp = component;
|
|
return true;
|
|
}
|
|
|
|
private void OnPirateMapInit(EntityUid uid, SectorPirateBountyDatabaseComponent component, MapInitEvent args)
|
|
{
|
|
FillPirateBountyDatabase(uid, component);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Fills up the bounty database with random bounties.
|
|
/// </summary>
|
|
public void FillPirateBountyDatabase(EntityUid serviceId, SectorPirateBountyDatabaseComponent? component = null)
|
|
{
|
|
if (!Resolve(serviceId, ref component))
|
|
return;
|
|
|
|
while (component?.Bounties.Count < component?.MaxBounties)
|
|
{
|
|
if (!TryAddPirateBounty(serviceId, component))
|
|
break;
|
|
}
|
|
|
|
UpdatePirateBountyConsoles();
|
|
}
|
|
|
|
[PublicAPI]
|
|
public bool TryAddPirateBounty(EntityUid serviceId, SectorPirateBountyDatabaseComponent? component = null)
|
|
{
|
|
if (!Resolve(serviceId, ref component))
|
|
return false;
|
|
|
|
// todo: consider making the pirate bounties weighted.
|
|
var allBounties = _proto.EnumeratePrototypes<PirateBountyPrototype>().ToList();
|
|
var filteredBounties = new List<PirateBountyPrototype>();
|
|
foreach (var proto in allBounties)
|
|
{
|
|
if (component.Bounties.Any(b => b.Bounty == proto.ID))
|
|
continue;
|
|
filteredBounties.Add(proto);
|
|
}
|
|
|
|
var pool = filteredBounties.Count == 0 ? allBounties : filteredBounties;
|
|
var bounty = _random.Pick(pool);
|
|
return TryAddPirateBounty(serviceId, bounty, component);
|
|
}
|
|
|
|
[PublicAPI]
|
|
public bool TryAddPirateBounty(EntityUid serviceId, string bountyId, SectorPirateBountyDatabaseComponent? component = null)
|
|
{
|
|
if (!_proto.TryIndex<PirateBountyPrototype>(bountyId, out var bounty))
|
|
return false;
|
|
|
|
return TryAddPirateBounty(serviceId, bounty, component);
|
|
}
|
|
|
|
public bool TryAddPirateBounty(EntityUid serviceId, PirateBountyPrototype bounty, SectorPirateBountyDatabaseComponent? component = null)
|
|
{
|
|
if (!Resolve(serviceId, ref component))
|
|
return false;
|
|
|
|
if (component.Bounties.Count >= component.MaxBounties)
|
|
return false;
|
|
|
|
_nameIdentifier.GenerateUniqueName(serviceId, PirateBountyNameIdentifierGroup, out var randomVal); // Need a string ID for internal name, probably doesn't need to be outward facing.
|
|
component.Bounties.Add(new PirateBountyData(bounty, randomVal, false));
|
|
_adminLogger.Add(LogType.Action, LogImpact.Low, $"Added pirate bounty \"{bounty.ID}\" (id:{component.TotalBounties}) to service {ToPrettyString(serviceId)}");
|
|
component.TotalBounties++;
|
|
return true;
|
|
}
|
|
|
|
[PublicAPI]
|
|
public bool TryRemovePirateBounty(EntityUid serviceId, string dataId, SectorPirateBountyDatabaseComponent? component = null)
|
|
{
|
|
if (!TryGetPirateBountyFromId(serviceId, dataId, out var data, component))
|
|
return false;
|
|
|
|
return TryRemovePirateBounty(serviceId, data.Value, component);
|
|
}
|
|
|
|
public bool TryRemovePirateBounty(EntityUid serviceId, PirateBountyData data, SectorPirateBountyDatabaseComponent? component = null)
|
|
{
|
|
if (!Resolve(serviceId, ref component))
|
|
return false;
|
|
|
|
for (var i = 0; i < component.Bounties.Count; i++)
|
|
{
|
|
if (component.Bounties[i].Id == data.Id)
|
|
{
|
|
component.Bounties.RemoveAt(i);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public bool TryGetPirateBountyFromId(
|
|
EntityUid uid,
|
|
string id,
|
|
[NotNullWhen(true)] out PirateBountyData? bounty,
|
|
SectorPirateBountyDatabaseComponent? component = null)
|
|
{
|
|
bounty = null;
|
|
if (!Resolve(uid, ref component))
|
|
return false;
|
|
|
|
foreach (var bountyData in component.Bounties)
|
|
{
|
|
if (bountyData.Id != id)
|
|
continue;
|
|
bounty = bountyData;
|
|
break;
|
|
}
|
|
|
|
return bounty != null;
|
|
}
|
|
|
|
private bool TryOverwritePirateBountyFromId(
|
|
EntityUid uid,
|
|
PirateBountyData bounty,
|
|
SectorPirateBountyDatabaseComponent? component = null)
|
|
{
|
|
if (!Resolve(uid, ref component))
|
|
return false;
|
|
|
|
for (int i = 0; i < component.Bounties.Count; i++)
|
|
{
|
|
if (bounty.Id == component.Bounties[i].Id)
|
|
{
|
|
component.Bounties[i] = bounty;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public void UpdatePirateBountyConsoles()
|
|
{
|
|
var query = EntityQueryEnumerator<PirateBountyConsoleComponent, UserInterfaceComponent>();
|
|
|
|
var serviceId = _sectorService.GetServiceEntity();
|
|
if (!TryComp<SectorPirateBountyDatabaseComponent>(serviceId, out var db))
|
|
return;
|
|
|
|
while (query.MoveNext(out var uid, out _, out var ui))
|
|
{
|
|
var untilNextSkip = db.NextSkipTime - _timing.CurTime;
|
|
_ui.SetUiState((uid, ui), PirateConsoleUiKey.Bounty, new PirateBountyConsoleState(db.Bounties, untilNextSkip));
|
|
}
|
|
}
|
|
|
|
private List<(EntityUid Entity, ContrabandPalletComponent Component)> GetContrabandPallets(EntityUid gridUid)
|
|
{
|
|
var pads = new List<(EntityUid, ContrabandPalletComponent)>();
|
|
var query = AllEntityQuery<ContrabandPalletComponent, TransformComponent>();
|
|
|
|
while (query.MoveNext(out var uid, out var comp, out var compXform))
|
|
{
|
|
if (compXform.ParentUid != gridUid ||
|
|
!compXform.Anchored)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
pads.Add((uid, comp));
|
|
}
|
|
|
|
return pads;
|
|
}
|
|
|
|
private void OnRedeemBounty(EntityUid uid, PirateBountyRedemptionConsoleComponent component, PirateBountyRedemptionMessage args)
|
|
{
|
|
var amount = 0;
|
|
|
|
// Component still cooling down.
|
|
if (component.LastRedeemAttempt + _redemptionDelay > _timing.CurTime)
|
|
return;
|
|
|
|
EntityUid gridUid = Transform(uid).GridUid ?? EntityUid.Invalid;
|
|
if (gridUid == EntityUid.Invalid)
|
|
return;
|
|
|
|
// 1. Separate out accepted crate and non-crate bounties. Create a tracker for non-crate bounties.
|
|
if (!TryComp<SectorPirateBountyDatabaseComponent>(_sectorService.GetServiceEntity(), out var bountyDb))
|
|
return;
|
|
|
|
PirateBountyEntitySearchState bountySearchState = new PirateBountyEntitySearchState();
|
|
|
|
foreach (var bounty in bountyDb.Bounties)
|
|
{
|
|
if (bounty.Accepted)
|
|
{
|
|
if (!_proto.TryIndex(bounty.Bounty, out var bountyPrototype))
|
|
continue;
|
|
if (bountyPrototype.SpawnChest)
|
|
{
|
|
var newState = new PirateBountyState(bounty, bountyPrototype);
|
|
foreach (var entry in bountyPrototype.Entries)
|
|
{
|
|
newState.Entries[entry.Name] = 0;
|
|
}
|
|
bountySearchState.CrateBounties[bounty.Id] = newState;
|
|
}
|
|
else
|
|
{
|
|
var newState = new PirateBountyState(bounty, bountyPrototype);
|
|
foreach (var entry in bountyPrototype.Entries)
|
|
{
|
|
newState.Entries[entry.Name] = 0;
|
|
}
|
|
bountySearchState.LooseObjectBounties[bounty.Id] = newState;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 2. Iterate over bounty pads, find all tagged, non-tagged items.
|
|
foreach (var (palletUid, _) in GetContrabandPallets(gridUid))
|
|
{
|
|
foreach (var ent in _lookup.GetEntitiesIntersecting(palletUid,
|
|
LookupFlags.Dynamic | LookupFlags.Sundries | LookupFlags.Approximate | LookupFlags.Sensors))
|
|
{
|
|
// Dont match:
|
|
// - anything anchored (e.g. light fixtures)
|
|
// Checks against already handled set done by CheckEntityForPirateBounties
|
|
if (_xformQuery.TryGetComponent(ent, out var xform) &&
|
|
xform.Anchored)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
CheckEntityForPirateBounties(ent, ref bountySearchState);
|
|
}
|
|
}
|
|
|
|
// 4. When done, note all completed bounties. Remove them from the list of accepted bounties, and spawn the rewards.
|
|
bool bountiesRemoved = false;
|
|
string redeemedBounties = string.Empty;
|
|
foreach (var (id, bounty) in bountySearchState.CrateBounties)
|
|
{
|
|
bool bountyMet = true;
|
|
var prototype = bounty.Prototype;
|
|
foreach (var entry in prototype.Entries)
|
|
{
|
|
if (!bounty.Entries.ContainsKey(entry.Name) ||
|
|
entry.Amount > bounty.Entries[entry.Name])
|
|
{
|
|
bountyMet = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bountyMet)
|
|
{
|
|
bountiesRemoved = true;
|
|
redeemedBounties = Loc.GetString("pirate-bounty-redemption-append", ("bounty", id), ("empty", string.IsNullOrEmpty(redeemedBounties) ? 0 : 1), ("prev", redeemedBounties));
|
|
|
|
TryRemovePirateBounty(_sectorService.GetServiceEntity(), id);
|
|
amount += prototype.Reward;
|
|
foreach (var entity in bounty.Entities)
|
|
{
|
|
Del(entity);
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (var (id, bounty) in bountySearchState.LooseObjectBounties)
|
|
{
|
|
bool bountyMet = true;
|
|
var prototype = bounty.Prototype;
|
|
foreach (var entry in prototype.Entries)
|
|
{
|
|
if (!bounty.Entries.ContainsKey(entry.Name) ||
|
|
entry.Amount > bounty.Entries[entry.Name])
|
|
{
|
|
bountyMet = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bountyMet)
|
|
{
|
|
bountiesRemoved = true;
|
|
redeemedBounties = Loc.GetString("pirate-bounty-redemption-append", ("bounty", id), ("empty", string.IsNullOrEmpty(redeemedBounties) ? 0 : 1), ("prev", redeemedBounties));
|
|
|
|
TryRemovePirateBounty(_sectorService.GetServiceEntity(), id);
|
|
amount += prototype.Reward;
|
|
foreach (var entity in bounty.Entities)
|
|
{
|
|
Del(entity);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (amount > 0)
|
|
{
|
|
var stackUid = _stack.Spawn(amount, "Doubloon", Transform(args.Actor).Coordinates);
|
|
if (!_hands.TryPickupAnyHand(args.Actor, stackUid))
|
|
_transform.SetLocalRotation(stackUid, Angle.Zero);
|
|
_audio.PlayPvs(component.AcceptSound, uid);
|
|
_popup.PopupEntity(Loc.GetString("pirate-bounty-redemption-success", ("bounties", redeemedBounties), ("amount", amount)), args.Actor);
|
|
}
|
|
else
|
|
{
|
|
_audio.PlayPvs(component.DenySound, uid);
|
|
_popup.PopupEntity(Loc.GetString("pirate-bounty-redemption-deny"), args.Actor);
|
|
}
|
|
|
|
// Bounties removed, restore database list
|
|
if (bountiesRemoved)
|
|
{
|
|
FillPirateBountyDatabase(_sectorService.GetServiceEntity());
|
|
}
|
|
component.LastRedeemAttempt = _timing.CurTime;
|
|
}
|
|
|
|
sealed class PirateBountyState
|
|
{
|
|
public readonly PirateBountyData Data;
|
|
public PirateBountyPrototype Prototype;
|
|
public HashSet<EntityUid> Entities = new();
|
|
public Dictionary<string, int> Entries = new();
|
|
public bool Calculating = false; // Relevant only for crate bounties (due to tree traversal)
|
|
|
|
public PirateBountyState(PirateBountyData data, PirateBountyPrototype prototype)
|
|
{
|
|
Data = data;
|
|
Prototype = prototype;
|
|
}
|
|
}
|
|
|
|
sealed class PirateBountyEntitySearchState
|
|
{
|
|
public HashSet<EntityUid> HandledEntities = new();
|
|
public Dictionary<string, PirateBountyState> LooseObjectBounties = new();
|
|
public Dictionary<string, PirateBountyState> CrateBounties = new();
|
|
}
|
|
|
|
private void CheckEntityForPirateCrateBounty(EntityUid uid, ref PirateBountyEntitySearchState state, string id)
|
|
{
|
|
// Sanity check: entity previously handled, this subtree is done.
|
|
if (state.HandledEntities.Contains(uid))
|
|
return;
|
|
|
|
// Add this container to the list of entities to remove.
|
|
var bounty = state.CrateBounties[id]; // store the particular bounty we're looking up.
|
|
if (bounty.Calculating) // Bounty check is already happening in a parent, return.
|
|
{
|
|
state.HandledEntities.Add(uid);
|
|
return;
|
|
}
|
|
|
|
if (TryComp<ContainerManagerComponent>(uid, out var containers))
|
|
{
|
|
bounty.Entities.Add(uid);
|
|
bounty.Calculating = true;
|
|
|
|
foreach (var container in containers.Containers.Values)
|
|
{
|
|
foreach (var ent in container.ContainedEntities)
|
|
{
|
|
// Subtree has a separate label, run check on that label
|
|
if (TryComp<PirateBountyLabelComponent>(ent, out var label))
|
|
{
|
|
CheckEntityForPirateCrateBounty(ent, ref state, label.Id);
|
|
}
|
|
else
|
|
{
|
|
AdjustBountyForEntity(ent, bounty);
|
|
state.HandledEntities.Add(ent);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
state.HandledEntities.Add(uid);
|
|
}
|
|
|
|
// Return two lists: a list of non-labelled entities (nodes), and a list of labelled entities (subtrees)
|
|
private void CheckEntityForPirateBounties(EntityUid uid, ref PirateBountyEntitySearchState state)
|
|
{
|
|
// Entity previously handled, this subtree is done.
|
|
if (state.HandledEntities.Contains(uid))
|
|
return;
|
|
|
|
// 3a. If tagged as labelled, check contents against crate bounties. If it satisfies any of them, note it as solved.
|
|
if (TryComp<PirateBountyLabelComponent>(uid, out var label))
|
|
CheckEntityForPirateCrateBounty(uid, ref state, label.Id);
|
|
else
|
|
{
|
|
// 3b. If not tagged as labelled, check contents against non-create bounties. If it satisfies any of them, increase the quantity.
|
|
foreach (var (_, bounty) in state.LooseObjectBounties)
|
|
{
|
|
if (AdjustBountyForEntity(uid, bounty))
|
|
break;
|
|
}
|
|
}
|
|
state.HandledEntities.Add(uid);
|
|
}
|
|
|
|
// Checks an object against a bounty, adjusts the bounty's state and returns true if it matches.
|
|
private bool AdjustBountyForEntity(EntityUid target, PirateBountyState bounty)
|
|
{
|
|
foreach (var entry in bounty.Prototype.Entries)
|
|
{
|
|
// Should add an assertion here, entry.Name should exist.
|
|
// Entry already fulfilled, skip this entity.
|
|
if (bounty.Entries[entry.Name] >= entry.Amount)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Check whitelists for the pirate bounty.
|
|
if (TryComp<PirateBountyItemComponent>(target, out var targetBounty) && targetBounty.ID == entry.ID)
|
|
{
|
|
if (TryComp<StackComponent>(target, out var stack))
|
|
bounty.Entries[entry.Name] += stack.Count;
|
|
else
|
|
bounty.Entries[entry.Name]++;
|
|
bounty.Entities.Add(target);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|