[Feat] Система лежания и снаряды теперь проходят через лежащих людей, если не целиться в них (#195)

* add: система лежания и вставания

* add: теперь по-умолчанию по лежачим не попадают снаряды

* add: только гуманоиды могут ложиться
This commit is contained in:
Remuchi
2024-03-21 09:54:11 +07:00
committed by GitHub
parent 1b2b313804
commit 673d26f918
17 changed files with 411 additions and 158 deletions

View File

@@ -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);

View File

@@ -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);

View File

@@ -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<EntityPrototype>]
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),
});

View File

@@ -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);

View File

@@ -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<ProjectileComponent>(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<ThrowingAngleComponent>(uid);
angle.Angle = gun.Angle;
}
ThrowingSystem.TryThrow(uid, mapDirection.Normalized() * 7f * coefficient, gun.ProjectileSpeedModified, user);
if (gun.ForceThrowingAngle)
RemComp<ThrowingAngleComponent>(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<ThrowingAngleComponent>(uid);
angle.Angle = gun.Angle;
}
ThrowingSystem.TryThrow(uid, mapDirection.Normalized() * 7f * coefficient, gun.ProjectileSpeedModified,
user);
if (gun.ForceThrowingAngle)
RemComp<ThrowingAngleComponent>(uid);
// WD EDIT END
}
/// <summary>

View File

@@ -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";

View File

@@ -26,6 +26,12 @@ public sealed partial class ProjectileComponent : Component
[DataField, AutoNetworkedField]
public EntityUid? Weapon;
/// <summary>
/// Entity being targeted by crosshair.
/// </summary>
[AutoNetworkedField]
public EntityUid? Target;
/// <summary>
/// The projectile spawns inside the shooter most of the time, this prevents entities from shooting themselves.
/// </summary>

View File

@@ -334,6 +334,12 @@ public sealed class ImpactEffectEvent : EntityEventArgs
}
}
/// <summary>
/// Raised when an entity is just about to be hit with a projectile
/// </summary>
[ByRefEvent]
public record struct ProjectileCollideAttemptEvent(EntityUid ProjUid, ProjectileComponent Component, bool Cancelled);
/// <summary>
/// Raised when an entity is just about to be hit with a projectile but can reflect it
/// </summary>
@@ -350,4 +356,4 @@ public record struct ProjectileHitEvent(DamageSpecifier Damage, EntityUid Target
/// Raised after a projectile has dealt it's damage.
/// </summary>
[ByRefEvent]
public record struct AfterProjectileHitEvent(DamageSpecifier Damage, EntityUid Target, Fixture Fixture);
public record struct AfterProjectileHitEvent(DamageSpecifier Damage, EntityUid Target, Fixture Fixture);

View File

@@ -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
/// <summary>
/// Time required to get up.
/// </summary>
[DataField, AutoNetworkedField]
public TimeSpan StandingUpTime { get; set; } = TimeSpan.FromSeconds(1); // WD EDIT
// WD EDIT
[DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)]
public bool CanLieDown = false;
/// <summary>
/// List of fixtures that had their collision mask changed when the entity was downed.
/// Required for re-adding the collision mask.
/// </summary>
[DataField, AutoNetworkedField]
public List<string> ChangedFixtures = new();
}
}
[Serializable, NetSerializable]
public sealed class ChangeStandingStateEvent : EntityEventArgs
{
}
// WD EDIT
public enum StandingState
{
Lying,
GettingUp,
Standing
}

View File

@@ -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
/// <inheritdoc />
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<StandingStateComponent, StandingUpDoAfterEvent>(OnStandingUpDoAfter);
SubscribeLocalEvent<StandingStateComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshMovementSpeed);
SubscribeLocalEvent<StandingStateComponent, ProjectileCollideAttemptEvent>(OnProjectileCollideAttempt);
SubscribeLocalEvent<StandingStateComponent, HitscanHitAttemptEvent>(OnHitscanHitAttempt);
public bool IsDown(EntityUid uid, StandingStateComponent? standingState = null)
SubscribeNetworkEvent<ChangeStandingStateEvent>(OnChangeState);
CommandBinds.Builder
.Bind(ContentKeyFunctions.LieDown, InputCmdHandler.FromDelegate(ChangeLyingState))
.Register<StandingStateSystem>();
}
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);
/// <summary>
/// Send an update event when player pressed keybind.
/// </summary>
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();
/// <summary>
/// Subscribe if you can potentially block a down attempt.
/// </summary>
public sealed class DownAttemptEvent : CancellableEntityEventArgs
{
}
/// <summary>
/// Subscribe if you can potentially block a stand attempt.
/// </summary>
public sealed class StandAttemptEvent : CancellableEntityEventArgs
{
}
/// <summary>
/// Raised when an entity becomes standing
/// </summary>
public sealed class StoodEvent : EntityEventArgs
{
}
/// <summary>
/// Raised when an entity is not standing
/// </summary>
public sealed class DownedEvent : EntityEventArgs
{
return true;
}
}
public sealed class DropHandItemsEvent : EventArgs
{
}
/// <summary>
/// Subscribe if you can potentially block a down attempt.
/// </summary>
public sealed class DownAttemptEvent : CancellableEntityEventArgs
{
}
/// <summary>
/// Subscribe if you can potentially block a stand attempt.
/// </summary>
public sealed class StandAttemptEvent : CancellableEntityEventArgs
{
}
/// <summary>
/// Raised when an entity becomes standing
/// </summary>
public sealed class StoodEvent : EntityEventArgs
{
}
/// <summary>
/// Raised when an entity is not standing
/// </summary>
public sealed class DownedEvent : EntityEventArgs
{
}
// WD EDIT
[Serializable, NetSerializable]
public sealed partial class StandingUpDoAfterEvent : SimpleDoAfterEvent
{
}

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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);
/// <summary>
/// Raised when an entity is just about to be hit with a hitscan
/// </summary>
[ByRefEvent]
public record struct HitscanHitAttemptEvent(EntityUid HitEntity, EntityUid? Target, bool Cancelled = false);
/// <summary>
/// Raised directed on the gun after firing.
/// </summary>

View File

@@ -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

View File

@@ -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 = Умная экипировка в рюкзак

View File

@@ -177,6 +177,7 @@
- type: SleepEmitSound
- type: SSDIndicator
- type: StandingState
canLieDown: true
- type: Fingerprint
- type: Dna
- type: MindContainer

View File

@@ -527,3 +527,6 @@ binds:
- function: Hotbar9
type: State
key: Num9
- function: LieDown
type: State
key: U