diff --git a/Content.Client/Input/ContentContexts.cs b/Content.Client/Input/ContentContexts.cs index a5cfc1d966..1a5e7bdd83 100644 --- a/Content.Client/Input/ContentContexts.cs +++ b/Content.Client/Input/ContentContexts.cs @@ -76,6 +76,7 @@ namespace Content.Client.Input human.AddFunction(ContentKeyFunctions.Arcade1); human.AddFunction(ContentKeyFunctions.Arcade2); human.AddFunction(ContentKeyFunctions.Arcade3); + human.AddFunction(ContentKeyFunctions.LieDown); // WD EDIT // actions should be common (for ghosts, mobs, etc) common.AddFunction(ContentKeyFunctions.OpenActionsMenu); diff --git a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs index 5b786a9122..0b1cf71c7a 100644 --- a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs +++ b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs @@ -183,6 +183,7 @@ namespace Content.Client.Options.UI.Tabs AddButton(ContentKeyFunctions.SwapHands); AddButton(ContentKeyFunctions.MoveStoredItem); AddButton(ContentKeyFunctions.RotateStoredItem); + AddButton(ContentKeyFunctions.LieDown); // WD EDIT AddHeader("ui-options-header-interaction-adv"); AddButton(ContentKeyFunctions.SmartEquipBackpack); diff --git a/Content.Client/Weapons/Ranged/Systems/GunSystem.cs b/Content.Client/Weapons/Ranged/Systems/GunSystem.cs index f7cc2f1252..b3a0cfd5b5 100644 --- a/Content.Client/Weapons/Ranged/Systems/GunSystem.cs +++ b/Content.Client/Weapons/Ranged/Systems/GunSystem.cs @@ -1,4 +1,5 @@ using System.Numerics; +using Content.Client.Gameplay; using Content.Client.Items; using Content.Client.Weapons.Ranged.Components; using Content.Shared.Camera; @@ -12,13 +13,13 @@ using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Client.Input; using Robust.Client.Player; +using Robust.Client.State; using Robust.Shared.Animations; using Robust.Shared.Input; using Robust.Shared.Map; using Robust.Shared.Prototypes; +using Robust.Shared.Spawners; using Robust.Shared.Utility; -using SharedGunSystem = Content.Shared.Weapons.Ranged.Systems.SharedGunSystem; -using TimedDespawnComponent = Robust.Shared.Spawners.TimedDespawnComponent; namespace Content.Client.Weapons.Ranged.Systems; @@ -31,6 +32,7 @@ public sealed partial class GunSystem : SharedGunSystem [Dependency] private readonly InputSystem _inputSystem = default!; [Dependency] private readonly SharedCameraRecoilSystem _recoil = default!; [Dependency] private readonly IComponentFactory _factory = default!; + [Dependency] private readonly IStateManager _state = default!; [ValidatePrototypeId] public const string HitscanProto = "HitscanEffect"; @@ -184,9 +186,15 @@ public sealed partial class GunSystem : SharedGunSystem // WD EDIT END Log.Debug($"Sending shoot request tick {Timing.CurTick} / {Timing.CurTime}"); + //Set the target entity that is directly clicked on. + EntityUid? shootingTarget = null; + if (_state.CurrentState is GameplayStateBase screen) + shootingTarget = screen.GetClickedEntity(mousePos); + EntityManager.RaisePredictiveEvent(new RequestShootEvent { + Target = GetNetEntity(shootingTarget), Coordinates = GetNetCoordinates(coordinates), Gun = GetNetEntity(gunUid), }); diff --git a/Content.Server/Projectiles/ProjectileSystem.cs b/Content.Server/Projectiles/ProjectileSystem.cs index e9345bf7f9..b83cf2dede 100644 --- a/Content.Server/Projectiles/ProjectileSystem.cs +++ b/Content.Server/Projectiles/ProjectileSystem.cs @@ -42,9 +42,19 @@ public sealed class ProjectileSystem : SharedProjectileSystem // This is so entities that shouldn't get a collision are ignored. if (args.OurFixtureId != ProjectileFixture || !args.OtherFixture.Hard || component.DamagedEntity || component is { Weapon: null, OnlyCollideWhenShot: true }) + { return; + } var target = args.OtherEntity; + + var collideAttemptEv = new ProjectileCollideAttemptEvent(uid, component, false); + RaiseLocalEvent(target, ref collideAttemptEv); + if (collideAttemptEv.Cancelled) + { + return; + } + // it's here so this check is only done once before possible hit var attemptEv = new ProjectileReflectAttemptEvent(uid, component, false); RaiseLocalEvent(target, ref attemptEv); diff --git a/Content.Server/Weapons/Ranged/Systems/GunSystem.cs b/Content.Server/Weapons/Ranged/Systems/GunSystem.cs index 11d7d7adc2..054d914576 100644 --- a/Content.Server/Weapons/Ranged/Systems/GunSystem.cs +++ b/Content.Server/Weapons/Ranged/Systems/GunSystem.cs @@ -215,14 +215,31 @@ public sealed partial class GunSystem : SharedGunSystem var ray = new CollisionRay(from.Position, dir, hitscan.CollisionMask); var rayCastResults = Physics.IntersectRay(from.MapId, ray, hitscan.MaxLength, lastUser, false).ToList(); - if (!rayCastResults.Any()) + if (rayCastResults.Count == 0) break; - var result = rayCastResults[0]; - var hit = result.HitEntity; - lastHit = hit; + RayCastResults? result = null; + foreach (var castResults in rayCastResults) + { + var hitscanAttemptEv = new HitscanHitAttemptEvent(castResults.HitEntity, gun.Target); + RaiseLocalEvent(castResults.HitEntity, ref hitscanAttemptEv); + if (hitscanAttemptEv.Cancelled) + { + continue; + } - FireEffects(fromEffect, result.Distance, dir.Normalized().ToAngle(), hitscan, hit); + result = castResults; + break; + } + + if (!result.HasValue) + { + return; + } + + var hit = result.Value.HitEntity; + lastHit = hit; + FireEffects(fromEffect, result.Value.Distance, dir.Normalized().ToAngle(), hitscan, hit); var ev = new HitScanReflectAttemptEvent(user, gunUid, hitscan.Reflective, dir, false); RaiseLocalEvent(hit, ref ev); @@ -296,25 +313,30 @@ public sealed partial class GunSystem : SharedGunSystem private void ShootOrThrow(EntityUid uid, Vector2 mapDirection, Vector2 gunVelocity, GunComponent gun, EntityUid gunUid, EntityUid? user) { // Do a throw - if (!HasComp(uid)) + if (TryComp(uid, out ProjectileComponent? projectile)) { - RemoveShootable(uid); - // TODO: Someone can probably yeet this a billion miles so need to pre-validate input somewhere up the call stack. - // WD EDIT START - var coefficient = _powered.GetPowerCoefficient(gunUid); - if (gun.ForceThrowingAngle) - { - var angle = EnsureComp(uid); - angle.Angle = gun.Angle; - } - ThrowingSystem.TryThrow(uid, mapDirection.Normalized() * 7f * coefficient, gun.ProjectileSpeedModified, user); - if (gun.ForceThrowingAngle) - RemComp(uid); - // WD EDIT END + projectile.Target = gun.Target; + ShootProjectile(uid, mapDirection, gunVelocity, gunUid, user, gun.ProjectileSpeedModified); return; } - ShootProjectile(uid, mapDirection, gunVelocity, gunUid, user, gun.ProjectileSpeedModified); + RemoveShootable(uid); + // TODO: Someone can probably yeet this a billion miles so need to pre-validate input somewhere up the call stack. + // WD EDIT START + var coefficient = _powered.GetPowerCoefficient(gunUid); + if (gun.ForceThrowingAngle) + { + var angle = EnsureComp(uid); + angle.Angle = gun.Angle; + } + + ThrowingSystem.TryThrow(uid, mapDirection.Normalized() * 7f * coefficient, gun.ProjectileSpeedModified, + user); + + if (gun.ForceThrowingAngle) + RemComp(uid); + + // WD EDIT END } /// diff --git a/Content.Shared/Input/ContentKeyFunctions.cs b/Content.Shared/Input/ContentKeyFunctions.cs index 37876fd629..d3a29f9f78 100644 --- a/Content.Shared/Input/ContentKeyFunctions.cs +++ b/Content.Shared/Input/ContentKeyFunctions.cs @@ -56,6 +56,7 @@ namespace Content.Shared.Input public static readonly BoundKeyFunction ZoomOut = "ZoomOut"; public static readonly BoundKeyFunction ZoomIn = "ZoomIn"; public static readonly BoundKeyFunction ResetZoom = "ResetZoom"; + public static readonly BoundKeyFunction LieDown = "LieDown"; // WD EDIT public static readonly BoundKeyFunction ArcadeUp = "ArcadeUp"; public static readonly BoundKeyFunction ArcadeDown = "ArcadeDown"; diff --git a/Content.Shared/Projectiles/ProjectileComponent.cs b/Content.Shared/Projectiles/ProjectileComponent.cs index c24cf2b5ee..ea5d35955f 100644 --- a/Content.Shared/Projectiles/ProjectileComponent.cs +++ b/Content.Shared/Projectiles/ProjectileComponent.cs @@ -26,6 +26,12 @@ public sealed partial class ProjectileComponent : Component [DataField, AutoNetworkedField] public EntityUid? Weapon; + /// + /// Entity being targeted by crosshair. + /// + [AutoNetworkedField] + public EntityUid? Target; + /// /// The projectile spawns inside the shooter most of the time, this prevents entities from shooting themselves. /// diff --git a/Content.Shared/Projectiles/SharedProjectileSystem.cs b/Content.Shared/Projectiles/SharedProjectileSystem.cs index cec92aaa7d..d331f00e78 100644 --- a/Content.Shared/Projectiles/SharedProjectileSystem.cs +++ b/Content.Shared/Projectiles/SharedProjectileSystem.cs @@ -334,6 +334,12 @@ public sealed class ImpactEffectEvent : EntityEventArgs } } +/// +/// Raised when an entity is just about to be hit with a projectile +/// +[ByRefEvent] +public record struct ProjectileCollideAttemptEvent(EntityUid ProjUid, ProjectileComponent Component, bool Cancelled); + /// /// Raised when an entity is just about to be hit with a projectile but can reflect it /// @@ -350,4 +356,4 @@ public record struct ProjectileHitEvent(DamageSpecifier Damage, EntityUid Target /// Raised after a projectile has dealt it's damage. /// [ByRefEvent] -public record struct AfterProjectileHitEvent(DamageSpecifier Damage, EntityUid Target, Fixture Fixture); +public record struct AfterProjectileHitEvent(DamageSpecifier Damage, EntityUid Target, Fixture Fixture); \ No newline at end of file diff --git a/Content.Shared/Standing/StandingStateComponent.cs b/Content.Shared/Standing/StandingStateComponent.cs index 5d7bb0a59f..ed9918e235 100644 --- a/Content.Shared/Standing/StandingStateComponent.cs +++ b/Content.Shared/Standing/StandingStateComponent.cs @@ -1,24 +1,47 @@ using Robust.Shared.Audio; using Robust.Shared.GameStates; +using Robust.Shared.Serialization; namespace Content.Shared.Standing { - [RegisterComponent, NetworkedComponent, AutoGenerateComponentState] - [Access(typeof(StandingStateSystem))] + [RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(StandingStateSystem))] public sealed partial class StandingStateComponent : Component { - [ViewVariables(VVAccess.ReadWrite)] - [DataField] + [ViewVariables(VVAccess.ReadWrite), DataField] public SoundSpecifier DownSound { get; private set; } = new SoundCollectionSpecifier("BodyFall"); [DataField, AutoNetworkedField] - public bool Standing { get; set; } = true; + public StandingState CurrentState { get; set; } = StandingState.Standing; // WD EDIT + /// + /// Time required to get up. + /// + [DataField, AutoNetworkedField] + public TimeSpan StandingUpTime { get; set; } = TimeSpan.FromSeconds(1); // WD EDIT + + // WD EDIT + [DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)] + public bool CanLieDown = false; + /// /// List of fixtures that had their collision mask changed when the entity was downed. /// Required for re-adding the collision mask. /// [DataField, AutoNetworkedField] public List ChangedFixtures = new(); + } } + +[Serializable, NetSerializable] +public sealed class ChangeStandingStateEvent : EntityEventArgs +{ +} + +// WD EDIT +public enum StandingState +{ + Lying, + GettingUp, + Standing +} \ No newline at end of file diff --git a/Content.Shared/Standing/StandingStateSystem.cs b/Content.Shared/Standing/StandingStateSystem.cs index 517831b8a1..6c40181e3a 100644 --- a/Content.Shared/Standing/StandingStateSystem.cs +++ b/Content.Shared/Standing/StandingStateSystem.cs @@ -1,166 +1,324 @@ +using Content.Shared.DoAfter; using Content.Shared.Hands.Components; +using Content.Shared.Input; +using Content.Shared.Movement.Systems; using Content.Shared.Physics; +using Content.Shared.Projectiles; using Content.Shared.Rotation; -using Robust.Shared.Audio; +using Content.Shared.Weapons.Ranged.Systems; using Robust.Shared.Audio.Systems; +using Robust.Shared.Input.Binding; using Robust.Shared.Physics; using Robust.Shared.Physics.Systems; +using Robust.Shared.Player; +using Robust.Shared.Serialization; -namespace Content.Shared.Standing +namespace Content.Shared.Standing; + +public sealed class StandingStateSystem : EntitySystem { - public sealed class StandingStateSystem : EntitySystem + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly SharedPhysicsSystem _physics = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; // WD EDIT + [Dependency] private readonly MovementSpeedModifierSystem _movement = default!; // WD EDIT + + // If StandingCollisionLayer value is ever changed to more than one layer, the logic needs to be edited. + private const int StandingCollisionLayer = (int) CollisionGroup.MidImpassable; + + // WD EDIT START + + /// + public override void Initialize() { - [Dependency] private readonly SharedAppearanceSystem _appearance = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly SharedPhysicsSystem _physics = default!; + base.Initialize(); - // If StandingCollisionLayer value is ever changed to more than one layer, the logic needs to be edited. - private const int StandingCollisionLayer = (int) CollisionGroup.MidImpassable; + SubscribeLocalEvent(OnStandingUpDoAfter); + SubscribeLocalEvent(OnRefreshMovementSpeed); + SubscribeLocalEvent(OnProjectileCollideAttempt); + SubscribeLocalEvent(OnHitscanHitAttempt); - public bool IsDown(EntityUid uid, StandingStateComponent? standingState = null) + SubscribeNetworkEvent(OnChangeState); + + CommandBinds.Builder + .Bind(ContentKeyFunctions.LieDown, InputCmdHandler.FromDelegate(ChangeLyingState)) + .Register(); + } + + + private void OnRefreshMovementSpeed( + EntityUid uid, + StandingStateComponent component, + RefreshMovementSpeedModifiersEvent args) + { + if (IsDown(uid)) + args.ModifySpeed(0.4f, 0.4f); + else + args.ModifySpeed(1f, 1f); + } + + private void OnProjectileCollideAttempt(EntityUid uid, StandingStateComponent component, ref ProjectileCollideAttemptEvent args) + { + if (component.CurrentState is StandingState.Standing) { - if (!Resolve(uid, ref standingState, false)) - return false; + return; + } + + if (!args.Component.Target.HasValue || args.Component.Target != uid) + { + args.Cancelled = true; + } + } + + private void OnHitscanHitAttempt(EntityUid uid, StandingStateComponent component, ref HitscanHitAttemptEvent args) + { + if (component.CurrentState is StandingState.Standing) + { + return; + } + + if (!args.Target.HasValue || args.Target != uid) + { + args.Cancelled = true; + } + } - return !standingState.Standing; + private void OnStandingUpDoAfter(EntityUid uid, StandingStateComponent component, StandingUpDoAfterEvent args) + { + Stand(uid); + _movement.RefreshMovementSpeedModifiers(uid); + } + + private void OnChangeState(ChangeStandingStateEvent ev, EntitySessionEventArgs args) + { + if (!args.SenderSession.AttachedEntity.HasValue) + { + return; } - public bool Down(EntityUid uid, bool playSound = true, bool dropHeldItems = true, - StandingStateComponent? standingState = null, - AppearanceComponent? appearance = null, - HandsComponent? hands = null) + var uid = args.SenderSession.AttachedEntity.Value; + + if (IsDown(uid)) { - // TODO: This should actually log missing comps... - if (!Resolve(uid, ref standingState, false)) - return false; + TryStandUp(uid); + } + else + { + TryLieDown(uid); + } + } - // Optional component. - Resolve(uid, ref appearance, ref hands, false); + /// + /// Send an update event when player pressed keybind. + /// + private void ChangeLyingState(ICommonSession? session) + { + if (session?.AttachedEntity == null || + !TryComp(session.AttachedEntity, out StandingStateComponent? standing) || + !standing.CanLieDown) + { + return; + } - if (!standingState.Standing) - return true; + RaiseNetworkEvent(new ChangeStandingStateEvent()); + } + + public bool TryStandUp(EntityUid uid, StandingStateComponent? standingState = null) + { + if (!Resolve(uid, ref standingState, false)) + return false; - // This is just to avoid most callers doing this manually saving boilerplate - // 99% of the time you'll want to drop items but in some scenarios (e.g. buckling) you don't want to. - // We do this BEFORE downing because something like buckle may be blocking downing but we want to drop hand items anyway - // and ultimately this is just to avoid boilerplate in Down callers + keep their behavior consistent. - if (dropHeldItems && hands != null) + if (standingState.CurrentState is not StandingState.Lying) + return false; + + standingState.CurrentState = StandingState.GettingUp; + var doargs = new DoAfterArgs(EntityManager, uid, standingState.StandingUpTime, + new StandingUpDoAfterEvent(), uid) + { + BreakOnUserMove = false, + BreakOnDamage = false, + BreakOnHandChange = false + }; + + _doAfter.TryStartDoAfter(doargs); + return true; + } + + public bool TryLieDown(EntityUid uid, StandingStateComponent? standingState = null) + { + if (!Resolve(uid, ref standingState, false)) + return false; + + if (standingState.CurrentState is not StandingState.Standing) + { + return false; + } + + Down(uid, true, false, standingState); + _movement.RefreshMovementSpeedModifiers(uid); + return true; + } + + // WD EDIT END + + public bool IsDown(EntityUid uid, StandingStateComponent? standingState = null) + { + if (!Resolve(uid, ref standingState, false)) + return false; + + return standingState.CurrentState is StandingState.Lying or StandingState.GettingUp; + } + + public bool Down( + EntityUid uid, + bool playSound = true, + bool dropHeldItems = true, + StandingStateComponent? standingState = null, + AppearanceComponent? appearance = null, + HandsComponent? hands = null) + { + // TODO: This should actually log missing comps... + if (!Resolve(uid, ref standingState, false)) + return false; + + // Optional component. + Resolve(uid, ref appearance, ref hands, false); + + if (standingState.CurrentState is StandingState.Lying or StandingState.GettingUp) + return true; + + // This is just to avoid most callers doing this manually saving boilerplate + // 99% of the time you'll want to drop items but in some scenarios (e.g. buckling) you don't want to. + // We do this BEFORE downing because something like buckle may be blocking downing but we want to drop hand items anyway + // and ultimately this is just to avoid boilerplate in Down callers + keep their behavior consistent. + if (dropHeldItems && hands != null) + { + RaiseLocalEvent(uid, new DropHandItemsEvent()); + } + + var msg = new DownAttemptEvent(); + RaiseLocalEvent(uid, msg); + + if (msg.Cancelled) + return false; + + standingState.CurrentState = StandingState.Lying; + Dirty(uid, standingState); + RaiseLocalEvent(uid, new DownedEvent()); + + // Seemed like the best place to put it + _appearance.SetData(uid, RotationVisuals.RotationState, RotationState.Horizontal, appearance); + + // Change collision masks to allow going under certain entities like flaps and tables + if (TryComp(uid, out FixturesComponent? fixtureComponent)) + { + foreach (var (key, fixture) in fixtureComponent.Fixtures) { - RaiseLocalEvent(uid, new DropHandItemsEvent(), false); - } + if ((fixture.CollisionMask & StandingCollisionLayer) == 0) + continue; - var msg = new DownAttemptEvent(); - RaiseLocalEvent(uid, msg, false); + standingState.ChangedFixtures.Add(key); + _physics.SetCollisionMask(uid, key, fixture, fixture.CollisionMask & ~StandingCollisionLayer, + manager: fixtureComponent); + } + } + + // check if component was just added or streamed to client + // if true, no need to play sound - mob was down before player could seen that + if (standingState.LifeStage <= ComponentLifeStage.Starting) + return true; + + if (playSound) + { + _audio.PlayPredicted(standingState.DownSound, uid, uid); + } + + return true; + } + + public bool Stand( + EntityUid uid, + StandingStateComponent? standingState = null, + AppearanceComponent? appearance = null, + bool force = false) + { + // TODO: This should actually log missing comps... + if (!Resolve(uid, ref standingState, false)) + return false; + + // Optional component. + Resolve(uid, ref appearance, false); + + if (standingState.CurrentState is StandingState.Standing) + return true; + + if (!force) + { + var msg = new StandAttemptEvent(); + RaiseLocalEvent(uid, msg); if (msg.Cancelled) return false; - - standingState.Standing = false; - Dirty(standingState); - RaiseLocalEvent(uid, new DownedEvent(), false); - - // Seemed like the best place to put it - _appearance.SetData(uid, RotationVisuals.RotationState, RotationState.Horizontal, appearance); - - // Change collision masks to allow going under certain entities like flaps and tables - if (TryComp(uid, out FixturesComponent? fixtureComponent)) - { - foreach (var (key, fixture) in fixtureComponent.Fixtures) - { - if ((fixture.CollisionMask & StandingCollisionLayer) == 0) - continue; - - standingState.ChangedFixtures.Add(key); - _physics.SetCollisionMask(uid, key, fixture, fixture.CollisionMask & ~StandingCollisionLayer, manager: fixtureComponent); - } - } - - // check if component was just added or streamed to client - // if true, no need to play sound - mob was down before player could seen that - if (standingState.LifeStage <= ComponentLifeStage.Starting) - return true; - - if (playSound) - { - _audio.PlayPredicted(standingState.DownSound, uid, uid); - } - - return true; } - public bool Stand(EntityUid uid, - StandingStateComponent? standingState = null, - AppearanceComponent? appearance = null, - bool force = false) + standingState.CurrentState = StandingState.Standing; + Dirty(uid, standingState); + RaiseLocalEvent(uid, new StoodEvent()); + + _appearance.SetData(uid, RotationVisuals.RotationState, RotationState.Vertical, appearance); + + if (TryComp(uid, out FixturesComponent? fixtureComponent)) { - // TODO: This should actually log missing comps... - if (!Resolve(uid, ref standingState, false)) - return false; - - // Optional component. - Resolve(uid, ref appearance, false); - - if (standingState.Standing) - return true; - - if (!force) + foreach (var key in standingState.ChangedFixtures) { - var msg = new StandAttemptEvent(); - RaiseLocalEvent(uid, msg, false); - - if (msg.Cancelled) - return false; - } - - standingState.Standing = true; - Dirty(uid, standingState); - RaiseLocalEvent(uid, new StoodEvent(), false); - - _appearance.SetData(uid, RotationVisuals.RotationState, RotationState.Vertical, appearance); - - if (TryComp(uid, out FixturesComponent? fixtureComponent)) - { - foreach (var key in standingState.ChangedFixtures) + if (fixtureComponent.Fixtures.TryGetValue(key, out var fixture)) { - if (fixtureComponent.Fixtures.TryGetValue(key, out var fixture)) - _physics.SetCollisionMask(uid, key, fixture, fixture.CollisionMask | StandingCollisionLayer, fixtureComponent); + _physics.SetCollisionMask(uid, key, fixture, fixture.CollisionMask | StandingCollisionLayer, + fixtureComponent); } } - standingState.ChangedFixtures.Clear(); - - return true; } - } - public sealed class DropHandItemsEvent : EventArgs - { - } + standingState.ChangedFixtures.Clear(); - /// - /// Subscribe if you can potentially block a down attempt. - /// - public sealed class DownAttemptEvent : CancellableEntityEventArgs - { - } - - /// - /// Subscribe if you can potentially block a stand attempt. - /// - public sealed class StandAttemptEvent : CancellableEntityEventArgs - { - } - - /// - /// Raised when an entity becomes standing - /// - public sealed class StoodEvent : EntityEventArgs - { - } - - /// - /// Raised when an entity is not standing - /// - public sealed class DownedEvent : EntityEventArgs - { + return true; } } + +public sealed class DropHandItemsEvent : EventArgs +{ +} + +/// +/// Subscribe if you can potentially block a down attempt. +/// +public sealed class DownAttemptEvent : CancellableEntityEventArgs +{ +} + +/// +/// Subscribe if you can potentially block a stand attempt. +/// +public sealed class StandAttemptEvent : CancellableEntityEventArgs +{ +} + +/// +/// Raised when an entity becomes standing +/// +public sealed class StoodEvent : EntityEventArgs +{ +} + +/// +/// Raised when an entity is not standing +/// +public sealed class DownedEvent : EntityEventArgs +{ +} + +// WD EDIT +[Serializable, NetSerializable] +public sealed partial class StandingUpDoAfterEvent : SimpleDoAfterEvent +{ +} \ No newline at end of file diff --git a/Content.Shared/Weapons/Ranged/Components/GunComponent.cs b/Content.Shared/Weapons/Ranged/Components/GunComponent.cs index 752232475a..919d7f4d06 100644 --- a/Content.Shared/Weapons/Ranged/Components/GunComponent.cs +++ b/Content.Shared/Weapons/Ranged/Components/GunComponent.cs @@ -229,6 +229,9 @@ public sealed partial class GunComponent : Component public bool ClumsyProof = false; // WD START + + public EntityUid? Target; + [DataField("forceThrowingAngle")] [ViewVariables(VVAccess.ReadWrite)] public bool ForceThrowingAngle; diff --git a/Content.Shared/Weapons/Ranged/Events/RequestShootEvent.cs b/Content.Shared/Weapons/Ranged/Events/RequestShootEvent.cs index 21e90b2108..d13cd19e92 100644 --- a/Content.Shared/Weapons/Ranged/Events/RequestShootEvent.cs +++ b/Content.Shared/Weapons/Ranged/Events/RequestShootEvent.cs @@ -9,6 +9,7 @@ namespace Content.Shared.Weapons.Ranged.Events; [Serializable, NetSerializable] public sealed class RequestShootEvent : EntityEventArgs { + public NetEntity? Target; public NetEntity Gun; public NetCoordinates Coordinates; } diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs index 67a6b66cbb..1150392110 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs @@ -144,6 +144,7 @@ public abstract partial class SharedGunSystem : EntitySystem return; gun.ShootCoordinates = GetCoordinates(msg.Coordinates); + gun.Target = GetEntity(msg.Target); Log.Debug($"Set shoot coordinates to {gun.ShootCoordinates}"); AttemptShoot(user.Value, ent, gun); } @@ -558,6 +559,12 @@ public abstract partial class SharedGunSystem : EntitySystem [ByRefEvent] public record struct AttemptShootEvent(EntityUid User, string? Message, bool Cancelled = false, bool ThrowItems = false); +/// +/// Raised when an entity is just about to be hit with a hitscan +/// +[ByRefEvent] +public record struct HitscanHitAttemptEvent(EntityUid HitEntity, EntityUid? Target, bool Cancelled = false); + /// /// Raised directed on the gun after firing. /// diff --git a/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl b/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl index b8253c6929..64d56683e5 100644 --- a/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl +++ b/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl @@ -134,6 +134,7 @@ ui-options-function-examine-entity = Examine ui-options-function-swap-hands = Swap hands ui-options-function-move-stored-item = Move stored item ui-options-function-rotate-stored-item = Rotate stored item +ui-options-function-lie-down = Lie down/Get up ui-options-static-storage-ui = Lock storage window to hotbar ui-options-function-smart-equip-backpack = Smart-equip to backpack diff --git a/Resources/Locale/ru-RU/escape-menu/ui/options-menu.ftl b/Resources/Locale/ru-RU/escape-menu/ui/options-menu.ftl index d53745370a..feb7279859 100644 --- a/Resources/Locale/ru-RU/escape-menu/ui/options-menu.ftl +++ b/Resources/Locale/ru-RU/escape-menu/ui/options-menu.ftl @@ -136,6 +136,7 @@ ui-options-function-examine-entity = Изучить ui-options-function-swap-hands = Поменять руки ui-options-function-move-stored-item = Переместить хранящийся объект ui-options-function-rotate-stored-item = Повернуть хранящийся объект +ui-options-function-lie-down = Лечь/встать ui-options-static-storage-ui = Закрепить интерфейс хранилища на хотбаре ui-options-function-smart-equip-backpack = Умная экипировка в рюкзак diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml index 6782f9f9ad..34cba6da71 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/base.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml @@ -177,6 +177,7 @@ - type: SleepEmitSound - type: SSDIndicator - type: StandingState + canLieDown: true - type: Fingerprint - type: Dna - type: MindContainer diff --git a/Resources/keybinds.yml b/Resources/keybinds.yml index d78a73a22a..8fe5897d4e 100644 --- a/Resources/keybinds.yml +++ b/Resources/keybinds.yml @@ -527,3 +527,6 @@ binds: - function: Hotbar9 type: State key: Num9 +- function: LieDown + type: State + key: U \ No newline at end of file