Merge remote-tracking branch 'upstream/stable' into ed-25-08-2025-upstream-sync

# Conflicts:
#	.github/CODEOWNERS
#	Content.Client/UserInterface/Systems/Actions/Controls/ActionButton.cs
#	Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs
#	Content.Server/Chat/Systems/ChatSystem.cs
#	Content.Server/Explosion/EntitySystems/TriggerSystem.cs
#	Content.Server/Nutrition/EntitySystems/SliceableFoodSystem.cs
#	Content.Shared/Lock/LockSystem.cs
#	Content.Shared/Nutrition/Components/FoodComponent.cs
#	Content.Shared/Speech/ListenEvent.cs
#	Resources/Prototypes/Entities/Effects/admin_triggers.yml
This commit is contained in:
Ed
2025-08-25 16:22:32 +03:00
1069 changed files with 42396 additions and 29527 deletions

View File

@@ -0,0 +1,72 @@
using System.Linq;
using Content.Shared.Database;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Storage.Components;
using Content.Shared.Verbs;
using Content.Shared.Whitelist;
using Robust.Shared.Containers;
using Robust.Shared.Network;
using Robust.Shared.Random;
namespace Content.Shared.Storage.EntitySystems;
public sealed class PickRandomSystem : EntitySystem
{
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
[Dependency] private readonly INetManager _net = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<PickRandomComponent, GetVerbsEvent<AlternativeVerb>>(OnGetAlternativeVerbs);
}
private void OnGetAlternativeVerbs(EntityUid uid, PickRandomComponent comp, GetVerbsEvent<AlternativeVerb> args)
{
if (!args.CanAccess || !args.CanInteract || !TryComp<StorageComponent>(uid, out var storage))
return;
var user = args.User;
var enabled = storage.Container.ContainedEntities.Any(item => _whitelistSystem.IsWhitelistPassOrNull(comp.Whitelist, item));
// alt-click / alt-z to pick an item
args.Verbs.Add(new AlternativeVerb
{
Act = () =>
{
TryPick(uid, comp, storage, user);
},
Impact = LogImpact.Low,
Text = Loc.GetString(comp.VerbText),
Disabled = !enabled,
Message = enabled ? null : Loc.GetString(comp.EmptyText, ("storage", uid))
});
}
private void TryPick(EntityUid uid, PickRandomComponent comp, StorageComponent storage, EntityUid user)
{
// It's hard to predict picking a random entity from a container since the contained entity list will have a different order on the server and client.
// One idea might be to sort them by NetEntity ID, but that is expensive if there are a lot of entities.
// Another option would be to make this client authorative.
if (_net.IsClient)
return;
var entities = storage.Container.ContainedEntities.Where(item => _whitelistSystem.IsWhitelistPassOrNull(comp.Whitelist, item)).ToArray();
if (entities.Length == 0)
return;
var picked = _random.Pick(entities);
// if it fails to go into a hand of the user, will be on the storage
_container.AttachParentToContainerOrGrid((picked, Transform(picked)));
// TODO: try to put in hands, failing that put it on the storage
_hands.TryPickupAnyHand(user, picked);
}
}

View File

@@ -8,6 +8,7 @@ using Content.Shared.Interaction;
using Content.Shared.Item;
using Content.Shared.Materials;
using Content.Shared.Nutrition;
using Content.Shared.Nutrition.EntitySystems;
using Content.Shared.Popups;
using Content.Shared.Storage.Components;
using Content.Shared.Tools.EntitySystems;
@@ -25,6 +26,7 @@ namespace Content.Shared.Storage.EntitySystems;
/// </summary>
public sealed class SecretStashSystem : EntitySystem
{
[Dependency] private readonly IngestionSystem _ingestion = default!;
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
@@ -41,7 +43,7 @@ public sealed class SecretStashSystem : EntitySystem
SubscribeLocalEvent<SecretStashComponent, DestructionEventArgs>(OnDestroyed);
SubscribeLocalEvent<SecretStashComponent, GotReclaimedEvent>(OnReclaimed);
SubscribeLocalEvent<SecretStashComponent, InteractUsingEvent>(OnInteractUsing, after: new[] { typeof(ToolOpenableSystem), typeof(AnchorableSystem) });
SubscribeLocalEvent<SecretStashComponent, AfterFullyEatenEvent>(OnEaten);
SubscribeLocalEvent<SecretStashComponent, FullyEatenEvent>(OnFullyEaten);
SubscribeLocalEvent<SecretStashComponent, InteractHandEvent>(OnInteractHand);
SubscribeLocalEvent<SecretStashComponent, GetVerbsEvent<InteractionVerb>>(OnGetVerb);
}
@@ -61,7 +63,7 @@ public sealed class SecretStashSystem : EntitySystem
DropContentsAndAlert(entity, args.ReclaimerCoordinates);
}
private void OnEaten(Entity<SecretStashComponent> entity, ref AfterFullyEatenEvent args)
private void OnFullyEaten(Entity<SecretStashComponent> entity, ref FullyEatenEvent args)
{
// TODO: When newmed is finished should do damage to teeth (Or something like that!)
var damage = entity.Comp.DamageEatenItemInside;

View File

@@ -16,6 +16,7 @@ using Content.Shared.Verbs;
using Content.Shared.Wall;
using Content.Shared.Whitelist;
using Content.Shared.ActionBlocker;
using Content.Shared.Mobs.Components;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
@@ -355,7 +356,7 @@ public abstract class SharedEntityStorageSystem : EntitySystem
return _whitelistSystem.IsValid(component.Whitelist, toInsert);
// The inserted entity must be a mob or an item.
return HasComp<BodyComponent>(toInsert) || HasComp<ItemComponent>(toInsert);
return HasComp<MobStateComponent>(toInsert) || HasComp<ItemComponent>(toInsert);
}
public bool TryOpenStorage(EntityUid user, EntityUid target, bool silent = false)

View File

@@ -236,7 +236,14 @@ public abstract class SharedStorageSystem : EntitySystem
StoredItems = storedItems,
SavedLocations = component.SavedLocations,
Whitelist = component.Whitelist,
Blacklist = component.Blacklist
Blacklist = component.Blacklist,
QuickInsert = component.QuickInsert,
AreaInsert = component.AreaInsert,
StorageInsertSound = component.StorageInsertSound,
StorageRemoveSound = component.StorageRemoveSound,
StorageOpenSound = component.StorageOpenSound,
StorageCloseSound = component.StorageCloseSound,
DefaultStorageOrientation = component.DefaultStorageOrientation,
};
}
@@ -352,6 +359,44 @@ public abstract class SharedStorageSystem : EntitySystem
args.Verbs.Add(verb);
}
/// <summary>
/// Copy this component's datafields from one entity to another.
/// This can't use CopyComp because we don't want to copy the references to the items inside the storage.
/// <summary>
public void CopyComponent(Entity<StorageComponent?> source, EntityUid target)
{
if (!Resolve(source, ref source.Comp))
return;
var targetComp = EnsureComp<StorageComponent>(target);
targetComp.Grid = new List<Box2i>(source.Comp.Grid);
targetComp.MaxItemSize = source.Comp.MaxItemSize;
targetComp.QuickInsert = source.Comp.QuickInsert;
targetComp.QuickInsertCooldown = source.Comp.QuickInsertCooldown;
targetComp.OpenUiCooldown = source.Comp.OpenUiCooldown;
targetComp.ClickInsert = source.Comp.ClickInsert;
targetComp.OpenOnActivate = source.Comp.OpenOnActivate;
targetComp.AreaInsert = source.Comp.AreaInsert;
targetComp.AreaInsertRadius = source.Comp.AreaInsertRadius;
targetComp.Whitelist = source.Comp.Whitelist;
targetComp.Blacklist = source.Comp.Blacklist;
targetComp.StorageInsertSound = source.Comp.StorageInsertSound;
targetComp.StorageRemoveSound = source.Comp.StorageRemoveSound;
targetComp.StorageOpenSound = source.Comp.StorageOpenSound;
targetComp.StorageCloseSound = source.Comp.StorageCloseSound;
targetComp.DefaultStorageOrientation = source.Comp.DefaultStorageOrientation;
targetComp.HideStackVisualsWhenClosed = source.Comp.HideStackVisualsWhenClosed;
targetComp.SilentStorageUserTag = source.Comp.SilentStorageUserTag;
targetComp.ShowVerb = source.Comp.ShowVerb;
UpdateOccupied((target, targetComp));
Dirty(target, targetComp);
var targetUI = EnsureComp<UserInterfaceComponent>(target);
UI.SetUi((target, targetUI), StorageComponent.StorageUiKey.Key, new InterfaceData("StorageBoundUserInterface"));
}
/// <summary>
/// Tries to get the storage location of an item.
/// </summary>
@@ -1303,7 +1348,7 @@ public abstract class SharedStorageSystem : EntitySystem
Angle startAngle;
if (storageEnt.Comp.DefaultStorageOrientation == null)
{
startAngle = Angle.FromDegrees(-itemEnt.Comp.StoredRotation);
startAngle = Angle.Zero;
}
else
{
@@ -1970,15 +2015,17 @@ public abstract class SharedStorageSystem : EntitySystem
protected sealed class StorageComponentState : ComponentState
{
public Dictionary<NetEntity, ItemStorageLocation> StoredItems = new();
public Dictionary<string, List<ItemStorageLocation>> SavedLocations = new();
public List<Box2i> Grid = new();
public ProtoId<ItemSizePrototype>? MaxItemSize;
public EntityWhitelist? Whitelist;
public EntityWhitelist? Blacklist;
public bool QuickInsert;
public bool AreaInsert;
public SoundSpecifier? StorageInsertSound;
public SoundSpecifier? StorageRemoveSound;
public SoundSpecifier? StorageOpenSound;
public SoundSpecifier? StorageCloseSound;
public StorageDefaultOrientation? DefaultStorageOrientation;
}
}