fish-station/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Pickup.cs
VigersRay 6c158e6f0b Merge remote-tracking branch 'space-wizards/master'
# Conflicts:
#	.github/CODEOWNERS
#	.github/ISSUE_TEMPLATE/config.yml
#	.github/ISSUE_TEMPLATE/issue_report.md
#	Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs
#	Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml
#	Content.Client/UserInterface/Systems/Ghost/Controls/Roles/MakeGhostRoleWindow.xaml
#	Content.Client/UserInterface/Systems/Ghost/Controls/Roles/MakeGhostRoleWindow.xaml.cs
#	Content.Client/Weapons/Ranged/Systems/GunSystem.cs
#	Content.IntegrationTests/Tests/Destructible/DestructibleDamageGroupTest.cs
#	Content.IntegrationTests/Tests/Destructible/DestructibleDamageTypeTest.cs
#	Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs
#	Content.Server/Arcade/SpaceVillainGame/SpaceVillainArcadeComponent.cs
#	Content.Server/Atmos/EntitySystems/FlammableSystem.cs
#	Content.Server/Chat/Systems/ChatSystem.cs
#	Content.Server/Destructible/Thresholds/DamageThreshold.cs
#	Content.Server/Entry/EntryPoint.cs
#	Content.Server/Fluids/EntitySystems/PuddleSystem.cs
#	Content.Server/GuideGenerator/ReagentEntry.cs
#	Content.Server/IoC/ServerContentIoC.cs
#	Content.Server/Medical/HealthAnalyzerSystem.cs
#	Content.Server/Medical/VomitSystem.cs
#	Content.Server/Radio/EntitySystems/HeadsetSystem.cs
#	Content.Server/Revenant/EntitySystems/RevenantSystem.cs
#	Content.Server/Weapons/Ranged/Systems/GunSystem.cs
#	Content.Shared/Armor/SharedArmorSystem.cs
#	Content.Shared/Bed/Sleep/SleepingSystem.cs
#	Content.Shared/Cabinet/ItemCabinetSystem.cs
#	Content.Shared/Chat/SharedChatSystem.Emote.cs
#	Content.Shared/Chat/SharedChatSystem.cs
#	Content.Shared/Clothing/EntitySystems/MaskSystem.cs
#	Content.Shared/Clothing/EntitySystems/SharedChameleonClothingSystem.cs
#	Content.Shared/Damage/Systems/DamageableSystem.cs
#	Content.Shared/EntityEffects/Effects/StatusEffects/GenericStatusEffect.cs
#	Content.Shared/EntityEffects/Effects/Transform/ExplosionEntityEffect.cs
#	Content.Shared/Humanoid/HumanoidCharacterAppearance.cs
#	Content.Shared/IoC/SharedContentIoC.cs
#	Content.Shared/Lock/LockSystem.cs
#	Content.Shared/Ninja/Components/NinjaSuitComponent.cs
#	Content.Shared/Preferences/HumanoidCharacterProfile.cs
#	Content.Shared/Revenant/Components/RevenantComponent.cs
#	Content.Shared/Silicons/StationAi/SharedStationAiSystem.Airlock.cs
#	Content.Shared/Trigger/Systems/TriggerSystem.Condition.cs
#	Content.Shared/VendingMachines/SharedVendingMachineSystem.cs
#	Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs
#	Content.Shared/Weapons/Ranged/HitscanPrototype.cs
#	Content.Shared/Xenoarchaeology/Artifact/XAT/BaseQueryUpdateXATSystem.cs
#	README.md
#	Resources/Audio/Weapons/attributions.yml
#	Resources/Locale/en-US/_strings/ghost/make-ghost-gui.ftl
#	Resources/Locale/en-US/_strings/guidebook/chemistry/conditions.ftl
#	Resources/Locale/en-US/_strings/guidebook/chemistry/effects.ftl
#	Resources/Locale/en-US/_strings/guidebook/chemistry/healthchange.ftl
#	Resources/Locale/en-US/_strings/guidebook/chemistry/statuseffects.ftl
#	Resources/Locale/en-US/_strings/job/job-description.ftl
#	Resources/Locale/en-US/_strings/medical/components/crew-monitoring-component.ftl
#	Resources/Locale/en-US/_strings/npc/hugbot.ftl
#	Resources/Locale/en-US/_strings/npc/npc-debug.ftl
#	Resources/Locale/en-US/_strings/speech/speech-triggers.ftl
#	Resources/Locale/en-US/_strings/ui/stat-values.ftl
#	Resources/Locale/en-US/guidebook/chemistry/conditions.ftl
#	Resources/Locale/en-US/guidebook/chemistry/effects.ftl
#	Resources/Locale/en-US/guidebook/chemistry/healthchange.ftl
#	Resources/Locale/en-US/guidebook/entity-effects/conditions.ftl
#	Resources/Locale/en-US/guidebook/entity-effects/effects.ftl
#	Resources/Locale/en-US/guidebook/entity-effects/healthchange.ftl
#	Resources/Maps/Shuttles/cargo_relic.yml
#	Resources/Maps/_Sunrise/Station/bagel.yml
#	Resources/Maps/_Sunrise/Station/barratry.yml
#	Resources/Maps/_Sunrise/Station/fland.yml
#	Resources/Maps/_Sunrise/Station/marathon.yml
#	Resources/Maps/_Sunrise/Station/oasis.yml
#	Resources/Maps/_Sunrise/Station/packed.yml
#	Resources/Maps/_Sunrise/Station/plasma.yml
#	Resources/Maps/_Sunrise/Station/relic.yml
#	Resources/Maps/_Sunrise/event/clon_mine.yml
#	Resources/Maps/_Sunrise/event/void.yml
#	Resources/Prototypes/Body/Prototypes/vox.yml
#	Resources/Prototypes/Catalog/Fills/Boxes/emergency.yml
#	Resources/Prototypes/Catalog/Fills/Crates/fun.yml
#	Resources/Prototypes/Catalog/Fills/Lockers/heads.yml
#	Resources/Prototypes/Catalog/Fills/Lockers/security.yml
#	Resources/Prototypes/Entities/Clothing/Belt/belts.yml
#	Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml
#	Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml
#	Resources/Prototypes/Entities/Mobs/Species/skeleton.yml
#	Resources/Prototypes/Entities/Objects/Devices/Electronics/base_electronics.yml
#	Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml
#	Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Cartridges/pistol.yml
#	Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/pistol.yml
#	Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml
#	Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/hitscan.yml
#	Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml
#	Resources/Prototypes/Entities/Structures/Doors/Airlocks/access.yml
#	Resources/Prototypes/Entities/Structures/Doors/Airlocks/airlocks.yml
#	Resources/Prototypes/Entities/Structures/Doors/MaterialDoors/material_doors.yml
#	Resources/Prototypes/Entities/Structures/Machines/lathe.yml
#	Resources/Prototypes/Entities/Structures/Specific/Atmospherics/sensor.yml
#	Resources/Prototypes/Loadouts/Jobs/Engineering/station_engineer.yml
#	Resources/Prototypes/Loadouts/Jobs/Science/scientist.yml
#	Resources/Prototypes/Loadouts/Jobs/Security/security_officer.yml
#	Resources/Prototypes/Loadouts/Miscellaneous/glasses.yml
#	Resources/Prototypes/Loadouts/loadout_groups.yml
#	Resources/Prototypes/Objectives/objectiveGroups.yml
#	Resources/Prototypes/Reagents/Consumable/Drink/alcohol.yml
#	Resources/Prototypes/Reagents/medicine.yml
#	Resources/Prototypes/Reagents/narcotics.yml
#	Resources/Prototypes/Reagents/toxins.yml
#	Resources/Prototypes/Recipes/Lathes/Packs/biogen.yml
#	Resources/Prototypes/Recipes/Lathes/Packs/security.yml
#	Resources/Prototypes/Recipes/Lathes/biogen.yml
#	Resources/Prototypes/Roles/Antags/nukeops.yml
#	Resources/Prototypes/Roles/Antags/revolutionary.yml
#	Resources/Prototypes/Roles/Antags/thief.yml
#	Resources/Prototypes/Roles/Antags/traitor.yml
#	Resources/Prototypes/Roles/Antags/wizard.yml
#	Resources/Prototypes/Roles/Antags/zombie.yml
#	Resources/Prototypes/Roles/Jobs/Cargo/salvage_specialist.yml
#	Resources/Prototypes/Roles/Jobs/Civilian/bartender.yml
#	Resources/Prototypes/Roles/Jobs/Civilian/chef.yml
#	Resources/Prototypes/Roles/Jobs/Civilian/mime.yml
#	Resources/Prototypes/Roles/Jobs/Engineering/atmospheric_technician.yml
#	Resources/Prototypes/Roles/Jobs/Engineering/station_engineer.yml
#	Resources/Prototypes/Roles/Jobs/Engineering/technical_assistant.yml
#	Resources/Prototypes/Roles/Jobs/Medical/chemist.yml
#	Resources/Prototypes/Roles/Jobs/Medical/medical_doctor.yml
#	Resources/Prototypes/Roles/Jobs/Medical/medical_intern.yml
#	Resources/Prototypes/Roles/Jobs/Medical/paramedic.yml
#	Resources/Prototypes/Roles/Jobs/Science/borg.yml
#	Resources/Prototypes/Roles/Jobs/Science/research_assistant.yml
#	Resources/Prototypes/Roles/Jobs/Science/scientist.yml
#	Resources/Prototypes/Roles/Jobs/Security/security_cadet.yml
#	Resources/Prototypes/Roles/Jobs/Security/security_officer.yml
#	Resources/Prototypes/Roles/Jobs/Security/warden.yml
#	Resources/Prototypes/Stacks/Materials/crystals.yml
#	Resources/Prototypes/Stacks/floor_tile_stacks.yml
#	Resources/Prototypes/tags.yml
#	Resources/ServerInfo/Guidebook/Science/ArtifactReports.xml
#	Resources/ServerInfo/Guidebook/Science/Xenoarchaeology.xml
#	Resources/Textures/Clothing/Hands/Gloves/Color/black.rsi/equipped-HAND.png
#	Resources/Textures/Clothing/Hands/Gloves/Color/black.rsi/meta.json
#	Resources/Textures/Clothing/Head/Hardsuits/mime.rsi/meta.json
#	Resources/Textures/Effects/speech.rsi/moth3.png
#	Resources/Textures/Interface/Misc/job_icons.rsi/meta.json
#	Resources/Textures/Structures/Machines/microwave.rsi/meta.json
#	Resources/Textures/Structures/Machines/microwave.rsi/mw.png
#	Resources/Textures/Structures/Machines/microwave.rsi/mw0.png
#	Resources/Textures/Structures/Machines/microwave.rsi/mw_running_unlit.png
#	Resources/Textures/Structures/Machines/microwave.rsi/mw_unlit.png
#	Resources/Textures/Structures/Machines/microwave.rsi/mwb.png
#	Resources/Textures/Structures/Machines/microwave.rsi/mwbloody.png
#	Resources/Textures/Structures/Machines/microwave.rsi/mwbloody0.png
#	Resources/Textures/Structures/Machines/microwave.rsi/mwbloody1.png
#	Resources/Textures/Structures/Machines/microwave.rsi/mwbloodyo.png
#	Resources/Textures/Structures/Machines/microwave.rsi/mwo.png
#	Resources/Textures/Structures/Machines/microwave_syndie.rsi/meta.json
#	Resources/Textures/Structures/Machines/microwave_syndie.rsi/mw.png
#	Resources/Textures/Structures/Machines/microwave_syndie.rsi/mw0.png
#	Resources/Textures/Structures/Machines/microwave_syndie.rsi/mw_running_unlit.png
#	Resources/Textures/Structures/Machines/microwave_syndie.rsi/mw_unlit.png
#	Resources/Textures/Structures/Machines/microwave_syndie.rsi/mwb.png
#	Resources/Textures/Structures/Machines/microwave_syndie.rsi/mwo.png
#	Resources/migration.yml
#	RobustToolbox
2025-10-19 17:38:04 +02:00

330 lines
13 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Content.Shared.Database;
using Content.Shared.Hands.Components;
using Content.Shared.Item;
using Robust.Shared.Containers;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Utility;
using Robust.Shared.Timing; // Sunrise-add
using Content.Shared._Sunrise.Item; // Sunrise-add
namespace Content.Shared.Hands.EntitySystems;
public abstract partial class SharedHandsSystem
{
[Dependency] private readonly IGameTiming _timing = default!; // Sunrise-add
private void InitializePickup()
{
SubscribeLocalEvent<HandsComponent, EntInsertedIntoContainerMessage>(HandleEntityInserted);
}
protected virtual void HandleEntityInserted(EntityUid uid, HandsComponent hands, EntInsertedIntoContainerMessage args)
{
if (!TryGetHand(uid, args.Container.ID, out var hand))
{
return;
}
var didEquip = new DidEquipHandEvent(uid, args.Entity, hand.Value);
RaiseLocalEvent(uid, didEquip);
var gotEquipped = new GotEquippedHandEvent(uid, args.Entity, hand.Value);
RaiseLocalEvent(args.Entity, gotEquipped);
}
/// <summary>
/// Maximum pickup distance for which the pickup animation plays.
/// </summary>
public const float MaxAnimationRange = 10;
/// <summary>
/// Attempts to pick up an item into any empty hand. Prioritizes the currently active hand.
/// </summary>
/// <remarks>
/// If one empty hand fails to pick up the item, this will NOT check other hands. If ever hand-specific item
/// restrictions are added, there a might need to be a TryPickupAllHands or something like that.
/// </remarks>
public bool TryPickupAnyHand(
EntityUid uid,
EntityUid entity,
bool checkActionBlocker = true,
bool animateUser = false,
bool animate = true,
HandsComponent? handsComp = null,
ItemComponent? item = null)
{
if (!Resolve(uid, ref handsComp, false))
return false;
if (!TryGetEmptyHand((uid, handsComp), out var hand))
return false;
return TryPickup(uid, entity, hand, checkActionBlocker, animateUser, animate, handsComp, item);
}
/// <summary>
/// Tries to pick up an entity to a specific hand. If no explicit hand is specified, defaults to using the currently active hand.
/// </summary>
public bool TryPickup(
EntityUid uid,
EntityUid entity,
string? handId = null,
bool checkActionBlocker = true,
bool animateUser = false,
bool animate = true,
HandsComponent? handsComp = null,
ItemComponent? item = null)
{
if (!Resolve(uid, ref handsComp, false))
return false;
handId ??= handsComp.ActiveHandId;
if (handId == null)
return false;
if (!Resolve(entity, ref item, false))
return false;
if (!CanPickupToHand(uid, entity, handId, checkActionBlocker: checkActionBlocker, showPopup: true, handsComp: handsComp, item: item))
return false;
if (!BeforeDoPickup((uid, handsComp), entity))
return false;
// Sunrise-start
if (!CheckRepickupDuration(entity))
return false;
// Sunrise-end
if (animate)
{
var xform = Transform(uid);
var coordinateEntity = xform.ParentUid.IsValid() ? xform.ParentUid : uid;
var itemXform = Transform(entity);
var itemPos = TransformSystem.GetMapCoordinates(entity, xform: itemXform);
if (itemPos.MapId == xform.MapID
&& (itemPos.Position - TransformSystem.GetMapCoordinates(uid, xform: xform).Position).Length() <= MaxAnimationRange
&& MetaData(entity).VisibilityMask == MetaData(uid).VisibilityMask) // Don't animate aghost pickups.
{
var initialPosition = TransformSystem.ToCoordinates(coordinateEntity, itemPos);
_storage.PlayPickupAnimation(entity, initialPosition, xform.Coordinates, itemXform.LocalRotation, uid);
}
}
DoPickup(uid, handId, entity, handsComp);
return true;
}
// Sunrise-start
/// <summary>
/// Проверка для <see cref="ItemRepickupCooldownComponent"/>. Остановка досрочного поднятия
/// Всю логику нельзя запихнуть в <see cref="ItemRepickupCooldownSystem"/>, потому что
/// <see cref="PickupAttemptEvent"/> вызывается на мобе, который пытается поднять, а не на цели. Создавать новый
/// ивент... Это пока не надо, мне кажется только больше конфликтов будет
/// </summary>
/// <param name="entity">Для какой сущности проверять</param>
/// <returns>Результат проверки</returns>
public bool CheckRepickupDuration(Entity<ItemRepickupCooldownComponent?> entity)
{
// У сущности просто нет запрашиваемого компонента
if (!Resolve(entity.Owner, ref entity.Comp, logMissing: false))
return true;
if (entity.Comp.PrevDrop is not null &&
_timing.CurTime - entity.Comp.PrevDrop < entity.Comp.CooldownDuration)
return false;
return true;
}
// Sunrise-end
/// <summary>
/// Tries to pick up an entity into a hand, forcing to drop an item if its not free.
/// By default it does check if it's possible to drop items.
/// </summary>
public bool TryForcePickup(
Entity<HandsComponent?> ent,
EntityUid entity,
string hand,
bool checkActionBlocker = true,
bool animate = true,
HandsComponent? handsComp = null,
ItemComponent? item = null)
{
if (!Resolve(ent, ref ent.Comp, false))
return false;
TryDrop(ent, hand, checkActionBlocker: checkActionBlocker);
return TryPickup(ent, entity, hand, checkActionBlocker, animate: animate, handsComp: handsComp, item: item);
}
/// <summary>
/// Tries to pick up an entity into any hand, forcing to drop an item if there are no free hands
/// By default it does check if it's possible to drop items
/// </summary>
public bool TryForcePickupAnyHand(EntityUid uid, EntityUid entity, bool checkActionBlocker = true, HandsComponent? handsComp = null, ItemComponent? item = null)
{
if (!Resolve(uid, ref handsComp, false))
return false;
if (TryPickupAnyHand(uid, entity, checkActionBlocker: checkActionBlocker, handsComp: handsComp))
return true;
foreach (var hand in handsComp.Hands.Keys)
{
if (TryDrop((uid, handsComp), hand, checkActionBlocker: checkActionBlocker) &&
TryPickup(uid, entity, hand, checkActionBlocker: checkActionBlocker, handsComp: handsComp))
{
return true;
}
}
return false;
}
/// <summary>
/// Checks whether a given item will fit into the user's first free hand.
/// Unless otherwise specified, this will also check the general CanPickup action blocker.
/// </summary>
public bool CanPickupAnyHand(EntityUid uid, EntityUid entity, bool checkActionBlocker = true, bool showPopup = false, HandsComponent? handsComp = null, ItemComponent? item = null)
{
if (!Resolve(uid, ref handsComp, false))
return false;
if (!TryGetEmptyHand((uid, handsComp), out var hand))
return false;
return CanPickupToHand(uid, entity, hand, checkActionBlocker, showPopup, handsComp, item);
}
/// <summary>
/// Checks whether a given item will fit into a specific user's hand.
/// Unless otherwise specified, this will also check the general CanPickup action blocker.
/// </summary>
public bool CanPickupToHand(EntityUid uid, EntityUid entity, string handId, bool checkActionBlocker = true, bool showPopup = false, HandsComponent? handsComp = null, ItemComponent? item = null)
{
if (!Resolve(uid, ref handsComp, false))
return false;
if (!ContainerSystem.TryGetContainer(uid, handId, out var handContainer))
return false;
if (handContainer.ContainedEntities.FirstOrNull() != null)
return false;
// Huh, seems kinda weird that this system passes item comp around
// everywhere but it's never actually used besides being resolved.
// I wouldn't be surprised if there's some API simplifications that
// could be made with respect to that.
if (!Resolve(entity, ref item, false))
return false;
if (TryComp(entity, out PhysicsComponent? physics) && physics.BodyType == BodyType.Static)
return false;
if (checkActionBlocker && !_actionBlocker.CanPickup(uid, entity, showPopup))
return false;
if (!CheckWhitelists((uid, handsComp), handId, entity))
return false;
if (ContainerSystem.TryGetContainingContainer((entity, null, null), out var container))
{
if (!ContainerSystem.CanRemove(entity, container))
return false;
if (_inventory.TryGetSlotEntity(uid, container.ID, out var slotEnt) &&
slotEnt == entity &&
!_inventory.CanUnequip(uid, entity, container.ID, out _))
return false;
}
// check can insert (including raising attempt events).
return ContainerSystem.CanInsert(entity, handContainer);
}
/// <summary>
/// Puts an item into any hand, preferring the active hand, or puts it on the floor.
/// </summary>
/// <param name="dropNear">If true, the item will be dropped near the owner of the hand if possible.</param>
public void PickupOrDrop(
EntityUid? uid,
EntityUid entity,
bool checkActionBlocker = true,
bool animateUser = false,
bool animate = true,
bool dropNear = false,
HandsComponent? handsComp = null,
ItemComponent? item = null)
{
if (uid == null
|| !Resolve(uid.Value, ref handsComp, false)
|| !TryPickupAnyHand(uid.Value, entity, checkActionBlocker, animateUser, animate, handsComp, item))
{
// TODO make this check upwards for any container, and parent to that.
// Currently this just checks the direct parent, so items can still teleport through containers.
ContainerSystem.AttachParentToContainerOrGrid((entity, Transform(entity)));
if (dropNear && uid.HasValue)
{
TransformSystem.PlaceNextTo(entity, uid.Value);
}
}
}
/// <summary>
/// Small helper function meant as a last step before <see cref="DoPickup"/>
/// is called. Used to run a cancelable before pickup event that can have
/// side effects, unlike the side effect free <see cref="GettingPickedUpAttemptEvent"/>.
/// </summary>
private bool BeforeDoPickup(Entity<HandsComponent?> user, EntityUid item)
{
if (!Resolve(user, ref user.Comp))
return false;
var userEv = new BeforeEquippingHandEvent(item);
RaiseLocalEvent(user, ref userEv);
if (userEv.Cancelled)
return false;
var itemEv = new BeforeGettingEquippedHandEvent(user);
RaiseLocalEvent(item, ref itemEv);
return !itemEv.Cancelled;
}
/// <summary>
/// Puts an entity into the player's hand, assumes that the insertion is allowed. In general, you should not be calling this function directly.
/// </summary>
public virtual void DoPickup(EntityUid uid, string hand, EntityUid entity, HandsComponent? hands = null, bool log = true)
{
if (!Resolve(uid, ref hands))
return;
if (!ContainerSystem.TryGetContainer(uid, hand, out var handContainer))
return;
if (handContainer.ContainedEntities.FirstOrNull() != null)
return;
if (!ContainerSystem.Insert(entity, handContainer))
{
Log.Error($"Failed to insert {ToPrettyString(entity)} into users hand container when picking up. User: {ToPrettyString(uid)}. Hand: {hand}.");
return;
}
_interactionSystem.DoContactInteraction(uid, entity); //Possibly fires twice if manually picked up via interacting with the object
if (log)
_adminLogger.Add(LogType.Pickup, LogImpact.Low, $"{ToPrettyString(uid):user} picked up {ToPrettyString(entity):entity}");
Dirty(uid, hands);
if (hand == hands.ActiveHandId)
RaiseLocalEvent(entity, new HandSelectedEvent(uid));
}
}