| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530 |
- using System.Diagnostics.CodeAnalysis;
- using System.Linq;
- using Content.Server.Cargo.Components;
- using Content.Server.Labels;
- using Content.Server.NameIdentifier;
- using Content.Shared.Access.Components;
- using Content.Shared.Cargo;
- using Content.Shared.Cargo.Components;
- using Content.Shared.Cargo.Prototypes;
- using Content.Shared.Database;
- using Content.Shared.IdentityManagement;
- using Content.Shared.NameIdentifier;
- using Content.Shared.Paper;
- using Content.Shared.Stacks;
- using Content.Shared.Whitelist;
- using JetBrains.Annotations;
- using Robust.Server.Containers;
- using Robust.Shared.Containers;
- using Robust.Shared.Random;
- using Robust.Shared.Timing;
- using Robust.Shared.Utility;
- namespace Content.Server.Cargo.Systems;
- public sealed partial class CargoSystem
- {
- [Dependency] private readonly ContainerSystem _container = default!;
- [Dependency] private readonly NameIdentifierSystem _nameIdentifier = default!;
- [Dependency] private readonly EntityWhitelistSystem _whitelistSys = default!;
- [Dependency] private readonly IGameTiming _gameTiming = default!;
- [ValidatePrototypeId<NameIdentifierGroupPrototype>]
- private const string BountyNameIdentifierGroup = "Bounty";
- private EntityQuery<StackComponent> _stackQuery;
- private EntityQuery<ContainerManagerComponent> _containerQuery;
- private EntityQuery<CargoBountyLabelComponent> _bountyLabelQuery;
- private void InitializeBounty()
- {
- SubscribeLocalEvent<CargoBountyConsoleComponent, BoundUIOpenedEvent>(OnBountyConsoleOpened);
- SubscribeLocalEvent<CargoBountyConsoleComponent, BountyPrintLabelMessage>(OnPrintLabelMessage);
- SubscribeLocalEvent<CargoBountyConsoleComponent, BountySkipMessage>(OnSkipBountyMessage);
- SubscribeLocalEvent<CargoBountyLabelComponent, PriceCalculationEvent>(OnGetBountyPrice);
- SubscribeLocalEvent<EntitySoldEvent>(OnSold);
- SubscribeLocalEvent<StationCargoBountyDatabaseComponent, MapInitEvent>(OnMapInit);
- _stackQuery = GetEntityQuery<StackComponent>();
- _containerQuery = GetEntityQuery<ContainerManagerComponent>();
- _bountyLabelQuery = GetEntityQuery<CargoBountyLabelComponent>();
- }
- private void OnBountyConsoleOpened(EntityUid uid, CargoBountyConsoleComponent component, BoundUIOpenedEvent args)
- {
- if (_station.GetOwningStation(uid) is not { } station ||
- !TryComp<StationCargoBountyDatabaseComponent>(station, out var bountyDb))
- return;
- var untilNextSkip = bountyDb.NextSkipTime - _timing.CurTime;
- _uiSystem.SetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(bountyDb.Bounties, bountyDb.History, untilNextSkip));
- }
- private void OnPrintLabelMessage(EntityUid uid, CargoBountyConsoleComponent component, BountyPrintLabelMessage args)
- {
- if (_timing.CurTime < component.NextPrintTime)
- return;
- if (_station.GetOwningStation(uid) is not { } station)
- return;
- if (!TryGetBountyFromId(station, args.BountyId, out var bounty))
- return;
- var label = Spawn(component.BountyLabelId, Transform(uid).Coordinates);
- component.NextPrintTime = _timing.CurTime + component.PrintDelay;
- SetupBountyLabel(label, station, bounty.Value);
- _audio.PlayPvs(component.PrintSound, uid);
- }
- private void OnSkipBountyMessage(EntityUid uid, CargoBountyConsoleComponent component, BountySkipMessage args)
- {
- if (_station.GetOwningStation(uid) is not { } station || !TryComp<StationCargoBountyDatabaseComponent>(station, out var db))
- return;
- if (_timing.CurTime < db.NextSkipTime)
- return;
- if (!TryGetBountyFromId(station, args.BountyId, out var bounty))
- return;
- if (args.Actor is not { Valid: true } mob)
- return;
- if (TryComp<AccessReaderComponent>(uid, out var accessReaderComponent) &&
- !_accessReaderSystem.IsAllowed(mob, uid, accessReaderComponent))
- {
- _audio.PlayPvs(component.DenySound, uid);
- return;
- }
- if (!TryRemoveBounty(station, bounty.Value, true, args.Actor))
- return;
- FillBountyDatabase(station);
- db.NextSkipTime = _timing.CurTime + db.SkipDelay;
- var untilNextSkip = db.NextSkipTime - _timing.CurTime;
- _uiSystem.SetUiState(uid, CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties, db.History, untilNextSkip));
- _audio.PlayPvs(component.SkipSound, uid);
- }
- public void SetupBountyLabel(EntityUid uid, EntityUid stationId, CargoBountyData bounty, PaperComponent? paper = null, CargoBountyLabelComponent? label = null)
- {
- if (!Resolve(uid, ref paper, ref label) || !_protoMan.TryIndex<CargoBountyPrototype>(bounty.Bounty, out var prototype))
- return;
- label.Id = bounty.Id;
- label.AssociatedStationId = stationId;
- var msg = new FormattedMessage();
- msg.AddText(Loc.GetString("bounty-manifest-header", ("id", bounty.Id)));
- msg.PushNewline();
- msg.AddText(Loc.GetString("bounty-manifest-list-start"));
- msg.PushNewline();
- foreach (var entry in prototype.Entries)
- {
- msg.AddMarkupOrThrow($"- {Loc.GetString("bounty-console-manifest-entry",
- ("amount", entry.Amount),
- ("item", Loc.GetString(entry.Name)))}");
- msg.PushNewline();
- }
- msg.AddMarkupOrThrow(Loc.GetString("bounty-console-manifest-reward", ("reward", prototype.Reward)));
- _paperSystem.SetContent((uid, paper), msg.ToMarkup());
- }
- /// <summary>
- /// Bounties do not sell for any currency. The reward for a bounty is
- /// calculated after it is sold separately from the selling system.
- /// </summary>
- private void OnGetBountyPrice(EntityUid uid, CargoBountyLabelComponent component, ref PriceCalculationEvent args)
- {
- if (args.Handled || component.Calculating)
- return;
- // make sure this label was actually applied to a crate.
- if (!_container.TryGetContainingContainer((uid, null, null), out var container) || container.ID != LabelSystem.ContainerName)
- return;
- if (component.AssociatedStationId is not { } station || !TryComp<StationCargoBountyDatabaseComponent>(station, out var database))
- return;
- if (database.CheckedBounties.Contains(component.Id))
- return;
- if (!TryGetBountyFromId(station, component.Id, out var bounty, database))
- return;
- if (!_protoMan.TryIndex(bounty.Value.Bounty, out var bountyPrototype) ||
- !IsBountyComplete(container.Owner, bountyPrototype))
- return;
- database.CheckedBounties.Add(component.Id);
- args.Handled = true;
- component.Calculating = true;
- args.Price = bountyPrototype.Reward - _pricing.GetPrice(container.Owner);
- component.Calculating = false;
- }
- private void OnSold(ref EntitySoldEvent args)
- {
- foreach (var sold in args.Sold)
- {
- if (!TryGetBountyLabel(sold, out _, out var component))
- continue;
- if (component.AssociatedStationId is not { } station || !TryGetBountyFromId(station, component.Id, out var bounty))
- {
- continue;
- }
- if (!IsBountyComplete(sold, bounty.Value))
- {
- continue;
- }
- TryRemoveBounty(station, bounty.Value, false);
- FillBountyDatabase(station);
- _adminLogger.Add(LogType.Action, LogImpact.Low, $"Bounty \"{bounty.Value.Bounty}\" (id:{bounty.Value.Id}) was fulfilled");
- }
- }
- private bool TryGetBountyLabel(EntityUid uid,
- [NotNullWhen(true)] out EntityUid? labelEnt,
- [NotNullWhen(true)] out CargoBountyLabelComponent? 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 ||
- !_bountyLabelQuery.TryGetComponent(label, out var component))
- return false;
- labelEnt = label;
- labelComp = component;
- return true;
- }
- private void OnMapInit(EntityUid uid, StationCargoBountyDatabaseComponent component, MapInitEvent args)
- {
- FillBountyDatabase(uid, component);
- }
- /// <summary>
- /// Fills up the bounty database with random bounties.
- /// </summary>
- public void FillBountyDatabase(EntityUid uid, StationCargoBountyDatabaseComponent? component = null)
- {
- if (!Resolve(uid, ref component))
- return;
- while (component.Bounties.Count < component.MaxBounties)
- {
- if (!TryAddBounty(uid, component))
- break;
- }
- UpdateBountyConsoles();
- }
- public void RerollBountyDatabase(Entity<StationCargoBountyDatabaseComponent?> entity)
- {
- if (!Resolve(entity, ref entity.Comp))
- return;
- entity.Comp.Bounties.Clear();
- FillBountyDatabase(entity);
- }
- public bool IsBountyComplete(EntityUid container, out HashSet<EntityUid> bountyEntities)
- {
- if (!TryGetBountyLabel(container, out _, out var component))
- {
- bountyEntities = new();
- return false;
- }
- var station = component.AssociatedStationId;
- if (station == null)
- {
- bountyEntities = new();
- return false;
- }
- if (!TryGetBountyFromId(station.Value, component.Id, out var bounty))
- {
- bountyEntities = new();
- return false;
- }
- return IsBountyComplete(container, bounty.Value, out bountyEntities);
- }
- public bool IsBountyComplete(EntityUid container, CargoBountyData data)
- {
- return IsBountyComplete(container, data, out _);
- }
- public bool IsBountyComplete(EntityUid container, CargoBountyData data, out HashSet<EntityUid> bountyEntities)
- {
- if (!_protoMan.TryIndex(data.Bounty, out var proto))
- {
- bountyEntities = new();
- return false;
- }
- return IsBountyComplete(container, proto.Entries, out bountyEntities);
- }
- public bool IsBountyComplete(EntityUid container, string id)
- {
- if (!_protoMan.TryIndex<CargoBountyPrototype>(id, out var proto))
- return false;
- return IsBountyComplete(container, proto.Entries);
- }
- public bool IsBountyComplete(EntityUid container, CargoBountyPrototype prototype)
- {
- return IsBountyComplete(container, prototype.Entries);
- }
- public bool IsBountyComplete(EntityUid container, IEnumerable<CargoBountyItemEntry> entries)
- {
- return IsBountyComplete(container, entries, out _);
- }
- public bool IsBountyComplete(EntityUid container, IEnumerable<CargoBountyItemEntry> entries, out HashSet<EntityUid> bountyEntities)
- {
- return IsBountyComplete(GetBountyEntities(container), entries, out bountyEntities);
- }
- /// <summary>
- /// Determines whether the <paramref name="entity"/> meets the criteria for the bounty <paramref name="entry"/>.
- /// </summary>
- /// <returns>true if <paramref name="entity"/> is a valid item for the bounty entry, otherwise false</returns>
- public bool IsValidBountyEntry(EntityUid entity, CargoBountyItemEntry entry)
- {
- if (!_whitelistSys.IsValid(entry.Whitelist, entity))
- return false;
- if (entry.Blacklist != null && _whitelistSys.IsValid(entry.Blacklist, entity))
- return false;
- return true;
- }
- public bool IsBountyComplete(HashSet<EntityUid> entities, IEnumerable<CargoBountyItemEntry> entries, out HashSet<EntityUid> bountyEntities)
- {
- bountyEntities = new();
- foreach (var entry in entries)
- {
- var count = 0;
- // store entities that already satisfied an
- // entry so we don't double-count them.
- var temp = new HashSet<EntityUid>();
- foreach (var entity in entities)
- {
- if (!IsValidBountyEntry(entity, entry))
- continue;
- count += _stackQuery.CompOrNull(entity)?.Count ?? 1;
- temp.Add(entity);
- if (count >= entry.Amount)
- break;
- }
- if (count < entry.Amount)
- return false;
- foreach (var ent in temp)
- {
- entities.Remove(ent);
- bountyEntities.Add(ent);
- }
- }
- return true;
- }
- private HashSet<EntityUid> GetBountyEntities(EntityUid uid)
- {
- var entities = new HashSet<EntityUid>
- {
- uid
- };
- if (!TryComp<ContainerManagerComponent>(uid, out var containers))
- return entities;
- foreach (var container in containers.Containers.Values)
- {
- foreach (var ent in container.ContainedEntities)
- {
- if (_bountyLabelQuery.HasComponent(ent))
- continue;
- var children = GetBountyEntities(ent);
- foreach (var child in children)
- {
- entities.Add(child);
- }
- }
- }
- return entities;
- }
- [PublicAPI]
- public bool TryAddBounty(EntityUid uid, StationCargoBountyDatabaseComponent? component = null)
- {
- if (!Resolve(uid, ref component))
- return false;
- // todo: consider making the cargo bounties weighted.
- var allBounties = _protoMan.EnumeratePrototypes<CargoBountyPrototype>().ToList();
- var filteredBounties = new List<CargoBountyPrototype>();
- 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 TryAddBounty(uid, bounty, component);
- }
- [PublicAPI]
- public bool TryAddBounty(EntityUid uid, string bountyId, StationCargoBountyDatabaseComponent? component = null)
- {
- if (!_protoMan.TryIndex<CargoBountyPrototype>(bountyId, out var bounty))
- {
- return false;
- }
- return TryAddBounty(uid, bounty, component);
- }
- public bool TryAddBounty(EntityUid uid, CargoBountyPrototype bounty, StationCargoBountyDatabaseComponent? component = null)
- {
- if (!Resolve(uid, ref component))
- return false;
- if (component.Bounties.Count >= component.MaxBounties)
- return false;
- _nameIdentifier.GenerateUniqueName(uid, BountyNameIdentifierGroup, out var randomVal);
- var newBounty = new CargoBountyData(bounty, randomVal);
- // This bounty id already exists! Probably because NameIdentifierSystem ran out of ids.
- if (component.Bounties.Any(b => b.Id == newBounty.Id))
- {
- Log.Error("Failed to add bounty {ID} because another one with the same ID already existed!", newBounty.Id);
- return false;
- }
- component.Bounties.Add(new CargoBountyData(bounty, randomVal));
- _adminLogger.Add(LogType.Action, LogImpact.Low, $"Added bounty \"{bounty.ID}\" (id:{component.TotalBounties}) to station {ToPrettyString(uid)}");
- component.TotalBounties++;
- return true;
- }
- [PublicAPI]
- public bool TryRemoveBounty(Entity<StationCargoBountyDatabaseComponent?> ent,
- string dataId,
- bool skipped,
- EntityUid? actor = null)
- {
- if (!TryGetBountyFromId(ent.Owner, dataId, out var data, ent.Comp))
- return false;
- return TryRemoveBounty(ent, data.Value, skipped, actor);
- }
- public bool TryRemoveBounty(Entity<StationCargoBountyDatabaseComponent?> ent,
- CargoBountyData data,
- bool skipped,
- EntityUid? actor = null)
- {
- if (!Resolve(ent, ref ent.Comp))
- return false;
- for (var i = 0; i < ent.Comp.Bounties.Count; i++)
- {
- if (ent.Comp.Bounties[i].Id == data.Id)
- {
- string? actorName = null;
- if (actor != null)
- {
- var getIdentityEvent = new TryGetIdentityShortInfoEvent(ent.Owner, actor.Value);
- RaiseLocalEvent(getIdentityEvent);
- actorName = getIdentityEvent.Title;
- }
- ent.Comp.History.Add(new CargoBountyHistoryData(data,
- skipped
- ? CargoBountyHistoryData.BountyResult.Skipped
- : CargoBountyHistoryData.BountyResult.Completed,
- _gameTiming.CurTime,
- actorName));
- ent.Comp.Bounties.RemoveAt(i);
- return true;
- }
- }
- return false;
- }
- public bool TryGetBountyFromId(
- EntityUid uid,
- string id,
- [NotNullWhen(true)] out CargoBountyData? bounty,
- StationCargoBountyDatabaseComponent? 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;
- }
- public void UpdateBountyConsoles()
- {
- var query = EntityQueryEnumerator<CargoBountyConsoleComponent, UserInterfaceComponent>();
- while (query.MoveNext(out var uid, out _, out var ui))
- {
- if (_station.GetOwningStation(uid) is not { } station ||
- !TryComp<StationCargoBountyDatabaseComponent>(station, out var db))
- {
- continue;
- }
- var untilNextSkip = db.NextSkipTime - _timing.CurTime;
- _uiSystem.SetUiState((uid, ui), CargoConsoleUiKey.Bounty, new CargoBountyConsoleState(db.Bounties, db.History, untilNextSkip));
- }
- }
- private void UpdateBounty()
- {
- var query = EntityQueryEnumerator<StationCargoBountyDatabaseComponent>();
- while (query.MoveNext(out var bountyDatabase))
- {
- bountyDatabase.CheckedBounties.Clear();
- }
- }
- }
|