CatStation/Content.Shared/Wieldable/SharedWieldableSystem.cs
Dmitry 25c5e59248
Upstream (#3172)
Signed-off-by: Prole <172158352+Prole0@users.noreply.github.com>
Co-authored-by: PJBot <pieterjan.briers+bot@gmail.com>
Co-authored-by: ScarKy0 <106310278+ScarKy0@users.noreply.github.com>
Co-authored-by: Samuka-C <47865393+Samuka-C@users.noreply.github.com>
Co-authored-by: ArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com>
Co-authored-by: Partmedia <kevinz5000@gmail.com>
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
Co-authored-by: themias <89101928+themias@users.noreply.github.com>
Co-authored-by: Victor Shen <71985089+Vexerot@users.noreply.github.com>
Co-authored-by: Ed <96445749+TheShuEd@users.noreply.github.com>
Co-authored-by: Milon <milonpl.git@proton.me>
Co-authored-by: Kirus59 <145689588+Kirus59@users.noreply.github.com>
Co-authored-by: Nemanja <98561806+EmoGarbage404@users.noreply.github.com>
Co-authored-by: Stomf <5dorkydorks@gmail.com>
Co-authored-by: drakewill-CRL <46307022+drakewill-CRL@users.noreply.github.com>
Co-authored-by: PraxisMapper <praxismapper@gmail.com>
Co-authored-by: EmoGarbage404 <retron404@gmail.com>
Co-authored-by: lzk <124214523+lzk228@users.noreply.github.com>
Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
Co-authored-by: IProduceWidgets <107586145+IProduceWidgets@users.noreply.github.com>
Co-authored-by: TytosB <54259736+TytosB@users.noreply.github.com>
Co-authored-by: abadaba695 <spacestation13thingy@gmail.com>
Co-authored-by: kosticia <kosticia46@gmail.com>
Co-authored-by: Thinbug <101073555+Thinbug0@users.noreply.github.com>
Co-authored-by: pathetic meowmeow <uhhadd@gmail.com>
Co-authored-by: SlamBamActionman <83650252+SlamBamActionman@users.noreply.github.com>
Co-authored-by: Boaz1111 <149967078+Boaz1111@users.noreply.github.com>
Co-authored-by: ActiveMammmoth <140334666+ActiveMammmoth@users.noreply.github.com>
Co-authored-by: Myra <vasilis@pikachu.systems>
Co-authored-by: Whatstone <166147148+whatston3@users.noreply.github.com>
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
Co-authored-by: K-Dynamic <20566341+K-Dynamic@users.noreply.github.com>
Co-authored-by: Gentleman-Bird <dcgreen406@gmail.com>
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Co-authored-by: BIGZi0348 <svalker0348@gmail.com>
Co-authored-by: LaCumbiaDelCoronavirus <90893484+LaCumbiaDelCoronavirus@users.noreply.github.com>
Co-authored-by: imatsoup <93290208+imatsoup@users.noreply.github.com>
Co-authored-by: Matthew Herber <32679887+happyrobot33@users.noreply.github.com>
Co-authored-by: Ertanic <36124833+Ertanic@users.noreply.github.com>
Co-authored-by: MissKay1994 <15877268+MissKay1994@users.noreply.github.com>
Co-authored-by: Errant <35878406+Errant-4@users.noreply.github.com>
Co-authored-by: eoineoineoin <helloworld@eoinrul.es>
Co-authored-by: Tiniest Shark <head.rebel@yahoo.com>
Co-authored-by: nikitosych <boriszyn@gmail.com>
Co-authored-by: Tayrtahn <tayrtahn@gmail.com>
Co-authored-by: Perry Fraser <perryprog@users.noreply.github.com>
Co-authored-by: YoungThug <ramialanbagy@gmail.com>
Co-authored-by: beck-thompson <107373427+beck-thompson@users.noreply.github.com>
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
Co-authored-by: Southbridge <7013162+southbridge-fur@users.noreply.github.com>
Co-authored-by: Vladislav Suchkov <20380250+murolem@users.noreply.github.com>
Co-authored-by: Prole <172158352+Prole0@users.noreply.github.com>
Co-authored-by: Unkn0wn_Gh0st <shadowstalkermll@gmail.com>
Co-authored-by: 3nderall <101940324+3nderall@users.noreply.github.com>
Co-authored-by: Radezolid <snappednexus@gmail.com>
Co-authored-by: J <billsmith116@gmail.com>
Co-authored-by: Ghagliiarghii <68826635+Ghagliiarghii@users.noreply.github.com>
Co-authored-by: chromiumboy <50505512+chromiumboy@users.noreply.github.com>
Co-authored-by: youtissoum <51883137+youtissoum@users.noreply.github.com>
Co-authored-by: Minemoder5000 <minemoder50000@gmail.com>
Co-authored-by: Spanky <scott@wearejacob.com>
Co-authored-by: Spessmann <156740760+Spessmann@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: brainfood1183 <113240905+brainfood1183@users.noreply.github.com>
Co-authored-by: Deerstop <edainturner@gmail.com>
Co-authored-by: B_Kirill <153602297+B-Kirill@users.noreply.github.com>
Co-authored-by: archee1 <archee3@hotmail.co.uk>
Co-authored-by: Cojoke <83733158+Cojoke-dot@users.noreply.github.com>
Co-authored-by: Quantum-cross <7065792+Quantum-cross@users.noreply.github.com>
Co-authored-by: poklj <compgeek223@gmail.com>
Co-authored-by: Krunklehorn <42424291+Krunklehorn@users.noreply.github.com>
Co-authored-by: OnyxTheBrave <131422822+OnyxTheBrave@users.noreply.github.com>
Co-authored-by: UpAndLeaves <92269094+Alpha-Two@users.noreply.github.com>
Co-authored-by: Flareguy <78941145+Flareguy@users.noreply.github.com>
Co-authored-by: Zalycon <84675130+Zalycon@users.noreply.github.com>
Co-authored-by: deltanedas <39013340+deltanedas@users.noreply.github.com>
Co-authored-by: Verm <32827189+Vermidia@users.noreply.github.com>
Co-authored-by: nikthechampiongr <32041239+nikthechampiongr@users.noreply.github.com>
Co-authored-by: ScarKy0 <scarky0@onet.eu>
Co-authored-by: Dmitry <57028746+dimm00n@users.noreply.github.com>
2025-05-24 11:00:17 +03:00

365 lines
14 KiB
C#

using System.Linq;
using Content.Shared.Camera;
using Content.Shared.Examine;
using Content.Shared.Hands;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction.Events;
using Content.Shared.Inventory.VirtualItem;
using Content.Shared.Item;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Systems;
using Content.Shared.Popups;
using Content.Shared.Timing;
using Content.Shared.Verbs;
using Content.Shared.Weapons.Melee;
using Content.Shared.Weapons.Melee.Components;
using Content.Shared.Weapons.Melee.Events;
using Content.Shared.Weapons.Ranged.Components;
using Content.Shared.Weapons.Ranged.Events;
using Content.Shared.Weapons.Ranged.Systems;
using Content.Shared.Wieldable.Components;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Collections;
using Robust.Shared.Network;
using Robust.Shared.Timing;
namespace Content.Shared.Wieldable;
public abstract class SharedWieldableSystem : EntitySystem
{
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedGunSystem _gun = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly SharedItemSystem _item = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly SharedVirtualItemSystem _virtualItem = default!;
[Dependency] private readonly UseDelaySystem _delay = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<WieldableComponent, UseInHandEvent>(OnUseInHand, before: [typeof(SharedGunSystem), typeof(BatteryWeaponFireModesSystem)]);
SubscribeLocalEvent<WieldableComponent, ItemUnwieldedEvent>(OnItemUnwielded);
SubscribeLocalEvent<WieldableComponent, GotUnequippedHandEvent>(OnItemLeaveHand);
SubscribeLocalEvent<WieldableComponent, VirtualItemDeletedEvent>(OnVirtualItemDeleted);
SubscribeLocalEvent<WieldableComponent, GetVerbsEvent<InteractionVerb>>(AddToggleWieldVerb);
SubscribeLocalEvent<WieldableComponent, HandDeselectedEvent>(OnDeselectWieldable);
SubscribeLocalEvent<MeleeRequiresWieldComponent, AttemptMeleeEvent>(OnMeleeAttempt);
SubscribeLocalEvent<GunRequiresWieldComponent, ExaminedEvent>(OnExamineRequires);
SubscribeLocalEvent<GunRequiresWieldComponent, ShotAttemptedEvent>(OnShootAttempt);
SubscribeLocalEvent<GunWieldBonusComponent, ItemWieldedEvent>(OnGunWielded);
SubscribeLocalEvent<GunWieldBonusComponent, ItemUnwieldedEvent>(OnGunUnwielded);
SubscribeLocalEvent<GunWieldBonusComponent, GunRefreshModifiersEvent>(OnGunRefreshModifiers);
SubscribeLocalEvent<GunWieldBonusComponent, ExaminedEvent>(OnExamine);
SubscribeLocalEvent<SpeedModifiedOnWieldComponent, ItemWieldedEvent>(OnSpeedModifierWielded);
SubscribeLocalEvent<SpeedModifiedOnWieldComponent, ItemUnwieldedEvent>(OnSpeedModifierUnwielded);
SubscribeLocalEvent<SpeedModifiedOnWieldComponent, HeldRelayedEvent<RefreshMovementSpeedModifiersEvent>>(OnRefreshSpeedWielded);
SubscribeLocalEvent<IncreaseDamageOnWieldComponent, GetMeleeDamageEvent>(OnGetMeleeDamage);
}
private void OnMeleeAttempt(EntityUid uid, MeleeRequiresWieldComponent component, ref AttemptMeleeEvent args)
{
if (TryComp<WieldableComponent>(uid, out var wieldable) &&
!wieldable.Wielded)
{
args.Cancelled = true;
args.Message = Loc.GetString("wieldable-component-requires", ("item", uid));
}
}
private void OnShootAttempt(EntityUid uid, GunRequiresWieldComponent component, ref ShotAttemptedEvent args)
{
if (TryComp<WieldableComponent>(uid, out var wieldable) &&
!wieldable.Wielded)
{
args.Cancel();
var time = _timing.CurTime;
if (time > component.LastPopup + component.PopupCooldown &&
!HasComp<MeleeWeaponComponent>(uid) &&
!HasComp<MeleeRequiresWieldComponent>(uid))
{
component.LastPopup = time;
var message = Loc.GetString("wieldable-component-requires", ("item", uid));
_popup.PopupClient(message, args.Used, args.User);
}
}
}
private void OnGunUnwielded(EntityUid uid, GunWieldBonusComponent component, ItemUnwieldedEvent args)
{
_gun.RefreshModifiers(uid);
}
private void OnGunWielded(EntityUid uid, GunWieldBonusComponent component, ref ItemWieldedEvent args)
{
_gun.RefreshModifiers(uid);
}
private void OnDeselectWieldable(EntityUid uid, WieldableComponent component, HandDeselectedEvent args)
{
if (_hands.EnumerateHands(args.User).Count() > 2)
return;
TryUnwield(uid, component, args.User);
}
private void OnGunRefreshModifiers(Entity<GunWieldBonusComponent> bonus, ref GunRefreshModifiersEvent args)
{
if (TryComp(bonus, out WieldableComponent? wield) &&
wield.Wielded)
{
args.MinAngle += bonus.Comp.MinAngle;
args.MaxAngle += bonus.Comp.MaxAngle;
args.AngleDecay += bonus.Comp.AngleDecay;
args.AngleIncrease += bonus.Comp.AngleIncrease;
}
}
private void OnSpeedModifierWielded(EntityUid uid, SpeedModifiedOnWieldComponent component, ItemWieldedEvent args)
{
_movementSpeedModifier.RefreshMovementSpeedModifiers(args.User);
}
private void OnSpeedModifierUnwielded(EntityUid uid, SpeedModifiedOnWieldComponent component, ItemUnwieldedEvent args)
{
_movementSpeedModifier.RefreshMovementSpeedModifiers(args.User);
}
private void OnRefreshSpeedWielded(EntityUid uid, SpeedModifiedOnWieldComponent component, ref HeldRelayedEvent<RefreshMovementSpeedModifiersEvent> args)
{
if (TryComp<WieldableComponent>(uid, out var wield) && wield.Wielded)
{
args.Args.ModifySpeed(component.WalkModifier, component.SprintModifier);
}
}
private void OnExamineRequires(Entity<GunRequiresWieldComponent> entity, ref ExaminedEvent args)
{
if (entity.Comp.WieldRequiresExamineMessage != null)
args.PushText(Loc.GetString(entity.Comp.WieldRequiresExamineMessage));
}
private void OnExamine(EntityUid uid, GunWieldBonusComponent component, ref ExaminedEvent args)
{
if (HasComp<GunRequiresWieldComponent>(uid))
return;
if (component.WieldBonusExamineMessage != null)
args.PushText(Loc.GetString(component.WieldBonusExamineMessage));
}
private void AddToggleWieldVerb(EntityUid uid, WieldableComponent component, GetVerbsEvent<InteractionVerb> args)
{
if (args.Hands == null || !args.CanAccess || !args.CanInteract)
return;
if (!_hands.IsHolding(args.User, uid, out _, args.Hands))
return;
// TODO VERB TOOLTIPS Make CanWield or some other function return string, set as verb tooltip and disable
// verb. Or just don't add it to the list if the action is not executable.
// TODO VERBS ICON
InteractionVerb verb = new()
{
Text = component.Wielded ? Loc.GetString("wieldable-verb-text-unwield") : Loc.GetString("wieldable-verb-text-wield"),
Act = component.Wielded
? () => TryUnwield(uid, component, args.User)
: () => TryWield(uid, component, args.User)
};
args.Verbs.Add(verb);
}
private void OnUseInHand(EntityUid uid, WieldableComponent component, UseInHandEvent args)
{
if (args.Handled)
return;
if (!component.Wielded)
args.Handled = TryWield(uid, component, args.User);
else if (component.UnwieldOnUse)
args.Handled = TryUnwield(uid, component, args.User);
if (HasComp<UseDelayComponent>(uid) && !component.UseDelayOnWield)
args.ApplyDelay = false;
}
public bool CanWield(EntityUid uid, WieldableComponent component, EntityUid user, bool quiet = false)
{
// Do they have enough hands free?
if (!TryComp<HandsComponent>(user, out var hands))
{
if (!quiet)
_popup.PopupClient(Loc.GetString("wieldable-component-no-hands"), user, user);
return false;
}
// Is it.. actually in one of their hands?
if (!_hands.IsHolding(user, uid, out _, hands))
{
if (!quiet)
_popup.PopupClient(Loc.GetString("wieldable-component-not-in-hands", ("item", uid)), user, user);
return false;
}
if (_hands.CountFreeableHands((user, hands)) < component.FreeHandsRequired)
{
if (!quiet)
{
var message = Loc.GetString("wieldable-component-not-enough-free-hands",
("number", component.FreeHandsRequired), ("item", uid));
_popup.PopupClient(message, user, user);
}
return false;
}
// Seems legit.
return true;
}
/// <summary>
/// Attempts to wield an item, starting a UseDelay after.
/// </summary>
/// <returns>True if the attempt wasn't blocked.</returns>
public bool TryWield(EntityUid used, WieldableComponent component, EntityUid user)
{
if (!CanWield(used, component, user))
return false;
if (TryComp(used, out UseDelayComponent? useDelay) && component.UseDelayOnWield)
{
if (!_delay.TryResetDelay((used, useDelay), true))
return false;
}
var attemptEv = new WieldAttemptEvent(user);
RaiseLocalEvent(used, ref attemptEv);
if (attemptEv.Cancelled)
return false;
if (TryComp<ItemComponent>(used, out var item))
{
component.OldInhandPrefix = item.HeldPrefix;
_item.SetHeldPrefix(used, component.WieldedInhandPrefix, component: item);
}
SetWielded((used, component), true);
if (component.WieldSound != null)
_audio.PlayPredicted(component.WieldSound, used, user);
//This section handles spawning the virtual item(s) to occupy the required additional hand(s).
var virtuals = new ValueList<EntityUid>();
for (var i = 0; i < component.FreeHandsRequired; i++)
{
if (_virtualItem.TrySpawnVirtualItemInHand(used, user, out var virtualItem, true))
{
virtuals.Add(virtualItem.Value);
continue;
}
foreach (var existingVirtual in virtuals)
{
QueueDel(existingVirtual);
}
return false;
}
var selfMessage = Loc.GetString("wieldable-component-successful-wield", ("item", used));
var othersMessage = Loc.GetString("wieldable-component-successful-wield-other", ("user", Identity.Entity(user, EntityManager)), ("item", used));
_popup.PopupPredicted(selfMessage, othersMessage, user, user);
var ev = new ItemWieldedEvent(user);
RaiseLocalEvent(used, ref ev);
return true;
}
/// <summary>
/// Attempts to unwield an item, with no use delay.
/// </summary>
/// <returns>True if the attempt wasn't blocked.</returns>
public bool TryUnwield(EntityUid used, WieldableComponent component, EntityUid user, bool force = false)
{
if (!component.Wielded)
return false; // already unwielded
if (!force)
{
var attemptEv = new UnwieldAttemptEvent(user);
RaiseLocalEvent(used, ref attemptEv);
if (attemptEv.Cancelled)
return false;
}
SetWielded((used, component), false);
var ev = new ItemUnwieldedEvent(user, force);
RaiseLocalEvent(used, ref ev);
return true;
}
/// <summary>
/// Sets wielded without doing any checks.
/// </summary>
private void SetWielded(Entity<WieldableComponent> ent, bool wielded)
{
ent.Comp.Wielded = wielded;
Dirty(ent);
_appearance.SetData(ent, WieldableVisuals.Wielded, wielded);
}
private void OnItemUnwielded(EntityUid uid, WieldableComponent component, ItemUnwieldedEvent args)
{
_item.SetHeldPrefix(uid, component.OldInhandPrefix);
var user = args.User;
_virtualItem.DeleteInHandsMatching(user, uid);
if (!args.Force) // don't play sound/popup if this was a forced unwield
{
if (component.UnwieldSound != null)
_audio.PlayPredicted(component.UnwieldSound, uid, user);
var selfMessage = Loc.GetString("wieldable-component-failed-wield", ("item", uid));
var othersMessage = Loc.GetString("wieldable-component-failed-wield-other", ("user", Identity.Entity(args.User, EntityManager)), ("item", uid));
_popup.PopupPredicted(selfMessage, othersMessage, user, user);
}
}
private void OnItemLeaveHand(EntityUid uid, WieldableComponent component, GotUnequippedHandEvent args)
{
if (uid == args.Unequipped)
TryUnwield(uid, component, args.User, force: true);
}
private void OnVirtualItemDeleted(EntityUid uid, WieldableComponent component, VirtualItemDeletedEvent args)
{
if (args.BlockingEntity == uid)
TryUnwield(uid, component, args.User, force: true);
}
private void OnGetMeleeDamage(EntityUid uid, IncreaseDamageOnWieldComponent component, ref GetMeleeDamageEvent args)
{
if (!TryComp<WieldableComponent>(uid, out var wield))
return;
if (!wield.Wielded)
return;
args.Damage += component.BonusDamage;
}
}