CatStation/Content.Shared/Storage/EntitySystems/EntityProviderSystem.cs
Sir Warock e6b3b3a441
Predict Light Replacers & Add Radial Menu (#40636)
* Predict Light Replacers

* Public once more

* Light changes

* Introduce an OrderList.

* Added Radial Menu

* Fix missing Dirty()

* Introduce EntityProvider

* Migratiooon!

* Adress Reviews

* Forgot Comments

* Test-Fail Fix

* Fixes

* Update buttons

* Revert "Update buttons"

This reverts commit f84d8f2502600905a2ab4bce5463af6f2b4b1c3e.

* Introduce Dedicated Short String

* review

* Another review has hit the PR

Co-Authored-By: Whatstone <166147148+whatston3@users.noreply.github.com>

* Adressing reviews

Co-Authored-By: Whatstone <166147148+whatston3@users.noreply.github.com>

* Update LightReplacerMenuBoundUserInterface.cs

* refactor: do not pass ref type by ref, try not to create impure methods where it is not required

* refactor: random xml-doc changes

* refactor: more xml-doc, and 1 call per line convention

* Small fixes

* Iterate through the container

* updated xml docs

* IReadOnlyfix

* refactor: renaming, whitespaces, use deconstructor in foreach, use Enumerable instead of creating additional list when its possible that it will be empty and wont be modified

* refactor: fix collction cloning to not throw during modificaiton

* Replace popups with sounds.

* Added singular sounds

* refactor: fix error on moving items from one EntityProvider to another and back when they have some spawned entities in container

* Replaced Popup with Sound

---------

Co-authored-by: Pok245 <113675512+Pok27@users.noreply.github.com>
Co-authored-by: Whatstone <166147148+whatston3@users.noreply.github.com>
Co-authored-by: Fildrance <fildrance@gmail.com>
2026-08-18 10:21:46 +00:00

188 lines
7.3 KiB
C#

using System.Linq;
using Content.Shared.Interaction;
using Content.Shared.Popups;
using Content.Shared.Storage.Components;
using Content.Shared.Whitelist;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Containers;
using Robust.Shared.Prototypes;
namespace Content.Shared.Storage.EntitySystems;
/// <summary>
/// System for managing providing entities from storage.
/// <seealso cref="EntityProviderComponent"/>
/// </summary>
public sealed partial class EntityProviderSystem : EntitySystem
{
[Dependency] private SharedAudioSystem _audio = default!;
[Dependency] private SharedContainerSystem _container = default!;
[Dependency] private SharedPopupSystem _popup = default!;
[Dependency] private EntityWhitelistSystem _whitelist = default!;
[Dependency] private EntityQuery<EntityProviderComponent> _providerQuery;
[Dependency] private EntityQuery<StorageComponent> _storageQuery;
private const string ContainerId = "entity-provider";
/// <summary> Initialize container on component. </summary>
[SubscribeLocalEvent]
private void OnInit(Entity<EntityProviderComponent> provider, ref ComponentInit args)
{
provider.Comp.Container = _container.EnsureContainer<Container>(provider, ContainerId);
}
[SubscribeLocalEvent]
public void OnAfterInteract(Entity<EntityProviderComponent> provider, ref AfterInteractEvent args)
{
if (args.Handled || !provider.Comp.CanTransfer || args.Target == null)
return;
if (TryInsertIntoProvider(provider, args.Target.Value, args.User))
{
args.Handled = true;
return;
}
if (_providerQuery.TryComp(args.Target, out var targetComp))
args.Handled = TryFillOtherProvider(provider, (args.Target.Value, targetComp), args.User);
}
[SubscribeLocalEvent]
public void OnInteractUsing(Entity<EntityProviderComponent> provider, ref InteractUsingEvent args)
{
if (args.Handled || !provider.Comp.CanReceive)
return;
if (TryInsertIntoProvider(provider, args.Used, args.User))
{
args.Handled = true;
return;
}
if (_storageQuery.TryComp(args.Used, out var storage))
args.Handled = TryFillFromStorage(provider, (args.Used, storage), args.User);
}
/// <summary>
/// Fill the entityStorage of a provider with the entityStorage of another provider.
/// </summary>
/// <param name="provider">The provider to refill the target.</param>
/// <param name="refillTarget">The provider whose entityStorage is to be refilled.</param>
/// <param name="user">The user who attempts to refill the provider with the other. Leave null to avoid popups.</param>
/// <returns>Returns true if it was able to insert at least one entity, otherwise false.</returns>
private bool TryFillOtherProvider(Entity<EntityProviderComponent> provider, Entity<EntityProviderComponent> refillTarget, EntityUid? user = null)
{
var success = false;
List<EntProtoId> toRemove = [];
if (!provider.Comp.CanTransfer)
{
_popup.PopupEntity(Loc.GetString("comp-entity-provider-cannot-transfer", ("provider", provider)), provider, user);
return false;
}
if (!refillTarget.Comp.CanReceive)
{
_popup.PopupEntity(Loc.GetString("comp-entity-provider-cannot-receive", ("refillTarget", refillTarget)), refillTarget, user);
return false;
}
foreach (var (entProtoId, count) in provider.Comp.EntityCounter)
{
if (_whitelist.IsWhitelistFail(refillTarget.Comp.Whitelist, entProtoId))
continue;
if (!refillTarget.Comp.EntityCounter.TryAdd(entProtoId, count))
refillTarget.Comp.EntityCounter[entProtoId] += count;
// Move all spawned entities over to the new provider.
var existingEntities = GetEntitiesFromContainer(provider.AsNullable(), entProtoId)
.ToArray();
foreach (var spawnedEntity in existingEntities)
{
_container.Insert(spawnedEntity, refillTarget.Comp.Container);
}
success = true;
toRemove.Add(entProtoId);
}
foreach (var removedEntProtoId in toRemove)
{
provider.Comp.EntityCounter.Remove(removedEntProtoId);
}
if (provider.Comp is { DeleteIfEmpty: true, EntityCounter.Count: 0 })
PredictedQueueDel(provider);
Dirty(provider);
Dirty(refillTarget);
if (!success)
return success;
// The refilling provider dictates the sound happening at the refillTarget.
_audio.PlayPredicted(provider.Comp.PluralTransferSound, refillTarget, user);
return success;
}
/// <summary>
/// Refill an entityProvider with entities inside a storage.
/// </summary>
/// <param name="refillTarget">The provider to refill.</param>
/// <param name="storage">The storage whose contents will refill the provider.</param>
/// <param name="user">The user who attempts to refill the provider with the storage. Leave null to avoid popups.</param>
/// <returns>Returns true if it was able to insert at least one entity, otherwise false.</returns>
private bool TryFillFromStorage(Entity<EntityProviderComponent> refillTarget, Entity<StorageComponent?> storage, EntityUid? user = null)
{
if (!Resolve(storage, ref storage.Comp))
return false;
var storedEntities = storage.Comp.Container.ContainedEntities.ToArray();
var insertionSuccess = false;
foreach (var ent in storedEntities)
{
if (TryInsertIntoProvider(refillTarget, ent)) // Not passing along the user to avoid the popup.
insertionSuccess = true;
}
// show some message if success
if (!insertionSuccess || user == null)
return insertionSuccess;
_audio.PlayPredicted(refillTarget.Comp.PluralTransferSound, refillTarget, user);
return insertionSuccess;
}
/// <summary>
/// Attempts to get only the spawned entities of a certain prototype from the container.
/// </summary>
/// <param name="provider">The entityProvider with the container.</param>
/// <param name="protoId">The entity prototype to check for. </param>
/// <param name="requestedAmount">The amount of entities to retrieve. If null, it'll retrieve all of them.</param>
/// <returns>Returns a list of all currently spawned entities of that prototype. It will NOT spawn more to reach <see cref="requestedAmount"/>.</returns>
private IEnumerable<EntityUid> GetEntitiesFromContainer(Entity<EntityProviderComponent?> provider, EntProtoId protoId, int? requestedAmount = null)
{
if (requestedAmount <= 0 || !Resolve(provider, ref provider.Comp))
yield break;
var containedEntities = provider.Comp.Container.ContainedEntities;
var count = 0;
foreach (var containedEntity in containedEntities)
{
var meta = MetaData(containedEntity).EntityPrototype;
if (meta != null && meta.ID == protoId)
{
yield return containedEntity;
count++;
}
// Check if we have enough.
if (count == requestedAmount)
yield break;
}
}
}