Gun stuff (#132)
* Guns can now be fully automatic. Take that BYOND. * Improve delay handling * Bullet spread * Fix firing guns on pickup
This commit is contained in:
committed by
GitHub
parent
69946c79d8
commit
7ca90d11b3
@@ -82,7 +82,9 @@
|
||||
<Compile Include="GameObjects\Components\Power\ApcBoundUserInterface.cs" />
|
||||
<Compile Include="GameObjects\Components\Power\PowerCellVisualizer2D.cs" />
|
||||
<Compile Include="GameObjects\Components\Storage\ClientStorageComponent.cs" />
|
||||
<Compile Include="GameObjects\Components\Weapons\Ranged\ClientRangedWeaponComponent.cs" />
|
||||
<Compile Include="GameObjects\EntitySystems\ClientNotifySystem.cs" />
|
||||
<Compile Include="GameObjects\EntitySystems\RangedWeaponSystem.cs" />
|
||||
<Compile Include="GameObjects\EntitySystems\VerbSystem.cs" />
|
||||
<Compile Include="GameTicking\ClientGameTicker.cs" />
|
||||
<Compile Include="Graphics\Overlays\CircleMaskOverlay.cs" />
|
||||
|
||||
@@ -5,12 +5,14 @@ using Content.Client.GameObjects.Components.Construction;
|
||||
using Content.Client.GameObjects.Components.Power;
|
||||
using Content.Client.GameObjects.Components.SmoothWalling;
|
||||
using Content.Client.GameObjects.Components.Storage;
|
||||
using Content.Client.GameObjects.Components.Weapons.Ranged;
|
||||
using Content.Client.GameTicking;
|
||||
using Content.Client.Input;
|
||||
using Content.Client.Interfaces;
|
||||
using Content.Client.Interfaces.GameObjects;
|
||||
using Content.Client.Interfaces.Parallax;
|
||||
using Content.Client.Parallax;
|
||||
using Content.Shared.GameObjects.Components.Weapons.Ranged;
|
||||
using Content.Shared.Interfaces;
|
||||
using SS14.Client;
|
||||
using SS14.Client.Interfaces;
|
||||
@@ -52,6 +54,7 @@ namespace Content.Client
|
||||
factory.RegisterIgnore("Welder");
|
||||
factory.RegisterIgnore("Wrench");
|
||||
factory.RegisterIgnore("Crowbar");
|
||||
factory.Register<ClientRangedWeaponComponent>();
|
||||
factory.RegisterIgnore("HitscanWeapon");
|
||||
factory.RegisterIgnore("ProjectileWeapon");
|
||||
factory.RegisterIgnore("Projectile");
|
||||
|
||||
@@ -27,6 +27,8 @@ namespace Content.Client.GameObjects
|
||||
|
||||
[ViewVariables] private ISpriteComponent _sprite;
|
||||
|
||||
[ViewVariables] public IEntity ActiveHand => GetEntity(ActiveIndex);
|
||||
|
||||
public override void OnAdd()
|
||||
{
|
||||
base.OnAdd();
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using Content.Shared.GameObjects.Components.Weapons.Ranged;
|
||||
using SS14.Shared.Interfaces.Timing;
|
||||
using SS14.Shared.IoC;
|
||||
using SS14.Shared.Log;
|
||||
using SS14.Shared.Map;
|
||||
|
||||
namespace Content.Client.GameObjects.Components.Weapons.Ranged
|
||||
{
|
||||
public sealed class ClientRangedWeaponComponent : SharedRangedWeaponComponent
|
||||
{
|
||||
private TimeSpan _lastFireTime;
|
||||
private int _tick;
|
||||
|
||||
public void TryFire(GridLocalCoordinates worldPos)
|
||||
{
|
||||
var curTime = IoCManager.Resolve<IGameTiming>().CurTime;
|
||||
var span = curTime - _lastFireTime;
|
||||
if (span.TotalSeconds < 1 / FireRate)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.Debug("Delay: {0}", span.TotalSeconds);
|
||||
_lastFireTime = curTime;
|
||||
SendNetworkMessage(new FireMessage(worldPos, _tick++));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
using Content.Client.GameObjects.Components.Weapons.Ranged;
|
||||
using Content.Client.Interfaces.GameObjects;
|
||||
using Content.Shared.Input;
|
||||
using SS14.Client.GameObjects.EntitySystems;
|
||||
using SS14.Client.Interfaces.Graphics.ClientEye;
|
||||
using SS14.Client.Interfaces.Input;
|
||||
using SS14.Client.Player;
|
||||
using SS14.Shared.GameObjects.Systems;
|
||||
using SS14.Shared.Input;
|
||||
using SS14.Shared.IoC;
|
||||
|
||||
namespace Content.Client.GameObjects.EntitySystems
|
||||
{
|
||||
public class RangedWeaponSystem : EntitySystem
|
||||
{
|
||||
|
||||
#pragma warning disable 649
|
||||
[Dependency] private readonly IPlayerManager _playerManager;
|
||||
[Dependency] private readonly IEyeManager _eyeManager;
|
||||
[Dependency] private readonly IInputManager _inputManager;
|
||||
#pragma warning restore 649
|
||||
|
||||
private InputSystem _inputSystem;
|
||||
private bool _isFirstShot;
|
||||
private bool _blocked;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
IoCManager.InjectDependencies(this);
|
||||
_inputSystem = EntitySystemManager.GetEntitySystem<InputSystem>();
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var canFireSemi = _isFirstShot;
|
||||
var state = _inputSystem.CmdStates.GetState(ContentKeyFunctions.UseItemInHand);
|
||||
if (state != BoundKeyState.Down)
|
||||
{
|
||||
_isFirstShot = true;
|
||||
_blocked = false;
|
||||
return;
|
||||
}
|
||||
|
||||
_isFirstShot = false;
|
||||
|
||||
var entity = _playerManager.LocalPlayer.ControlledEntity;
|
||||
if (entity == null || !entity.TryGetComponent(out IHandsComponent hands))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var held = hands.ActiveHand;
|
||||
if (held == null || !held.TryGetComponent(out ClientRangedWeaponComponent weapon))
|
||||
{
|
||||
_blocked = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_blocked)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var worldPos = _eyeManager.ScreenToWorld(_inputManager.MouseScreenPosition);
|
||||
|
||||
if (weapon.Automatic || canFireSemi)
|
||||
{
|
||||
weapon.TryFire(worldPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ namespace Content.Client.Interfaces.GameObjects
|
||||
{
|
||||
IEntity GetEntity(string index);
|
||||
string ActiveIndex { get; }
|
||||
IEntity ActiveHand { get; }
|
||||
|
||||
void SendChangeHand(string index);
|
||||
void AttackByInHand(string index);
|
||||
|
||||
@@ -34,6 +34,7 @@ using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Server.Mobs;
|
||||
using Content.Server.Players;
|
||||
using Content.Server.GameObjects.Components.Interactable;
|
||||
using Content.Server.GameObjects.Components.Weapon.Ranged;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Interfaces;
|
||||
using Content.Server.Interfaces.GameTicking;
|
||||
@@ -93,6 +94,7 @@ namespace Content.Server
|
||||
factory.Register<CrowbarComponent>();
|
||||
|
||||
factory.Register<HitscanWeaponComponent>();
|
||||
factory.Register<RangedWeaponComponent>();
|
||||
factory.Register<ProjectileWeaponComponent>();
|
||||
factory.Register<ProjectileComponent>();
|
||||
factory.Register<ThrownItemComponent>();
|
||||
|
||||
@@ -12,10 +12,11 @@ using SS14.Shared.Maths;
|
||||
using SS14.Shared.Physics;
|
||||
using SS14.Shared.Serialization;
|
||||
using System;
|
||||
using SS14.Shared.GameObjects;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Weapon.Ranged.Hitscan
|
||||
{
|
||||
public class HitscanWeaponComponent : RangedWeaponComponent
|
||||
public class HitscanWeaponComponent : Component
|
||||
{
|
||||
private const float MaxLength = 20;
|
||||
public override string Name => "HitscanWeapon";
|
||||
@@ -31,10 +32,18 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Hitscan
|
||||
serializer.DataField(ref Damage, "damage", 10);
|
||||
}
|
||||
|
||||
protected override void Fire(IEntity user, GridLocalCoordinates clicklocation)
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
var rangedWeapon = Owner.GetComponent<RangedWeaponComponent>();
|
||||
rangedWeapon.FireHandler = Fire;
|
||||
}
|
||||
|
||||
private void Fire(IEntity user, GridLocalCoordinates clickLocation)
|
||||
{
|
||||
var userPosition = user.Transform.WorldPosition; //Remember world positions are ephemeral and can only be used instantaneously
|
||||
var angle = new Angle(clicklocation.Position - userPosition);
|
||||
var angle = new Angle(clickLocation.Position - userPosition);
|
||||
|
||||
var ray = new Ray(userPosition, angle.ToVec());
|
||||
var rayCastResults = IoCManager.Resolve<IPhysicsManager>().IntersectRay(ray, MaxLength,
|
||||
|
||||
@@ -1,33 +1,76 @@
|
||||
using Content.Server.GameObjects.Components.Projectiles;
|
||||
using System;
|
||||
using Content.Server.GameObjects.Components.Projectiles;
|
||||
using SS14.Server.GameObjects;
|
||||
using SS14.Server.GameObjects.EntitySystems;
|
||||
using SS14.Server.Interfaces.GameObjects;
|
||||
using SS14.Shared.GameObjects;
|
||||
using SS14.Shared.Interfaces.GameObjects;
|
||||
using SS14.Shared.Interfaces.GameObjects.Components;
|
||||
using SS14.Shared.IoC;
|
||||
using SS14.Shared.Log;
|
||||
using SS14.Shared.Map;
|
||||
using SS14.Shared.Maths;
|
||||
using SS14.Shared.Serialization;
|
||||
using SS14.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile
|
||||
{
|
||||
public class ProjectileWeaponComponent : RangedWeaponComponent
|
||||
public class ProjectileWeaponComponent : Component
|
||||
{
|
||||
public override string Name => "ProjectileWeapon";
|
||||
|
||||
private string _ProjectilePrototype = "ProjectileBullet";
|
||||
|
||||
private float _velocity = 20f;
|
||||
private float _spreadStdDev = 3;
|
||||
private bool _spread = true;
|
||||
|
||||
protected override void Fire(IEntity user, GridLocalCoordinates clicklocation)
|
||||
private Random _spreadRandom;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool Spread
|
||||
{
|
||||
var userposition = user.GetComponent<ITransformComponent>().LocalPosition; //Remember world positions are ephemeral and can only be used instantaneously
|
||||
var angle = new Angle(clicklocation.Position - userposition.Position);
|
||||
get => _spread;
|
||||
set => _spread = value;
|
||||
}
|
||||
|
||||
var theta = angle.Theta;
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float SpreadStdDev
|
||||
{
|
||||
get => _spreadStdDev;
|
||||
set => _spreadStdDev = value;
|
||||
}
|
||||
|
||||
//Spawn the projectileprototype
|
||||
IEntity projectile = IoCManager.Resolve<IServerEntityManager>().ForceSpawnEntityAt(_ProjectilePrototype, userposition);
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
var rangedWeapon = Owner.GetComponent<RangedWeaponComponent>();
|
||||
rangedWeapon.FireHandler = Fire;
|
||||
|
||||
_spreadRandom = new Random(Owner.Uid.GetHashCode() ^ DateTime.Now.GetHashCode());
|
||||
}
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataField(ref _spread, "spread", true);
|
||||
serializer.DataField(ref _spreadStdDev, "spreadstddev", 3);
|
||||
}
|
||||
|
||||
private void Fire(IEntity user, GridLocalCoordinates clickLocation)
|
||||
{
|
||||
var userPosition = user.Transform.LocalPosition; //Remember world positions are ephemeral and can only be used instantaneously
|
||||
var angle = new Angle(clickLocation.Position - userPosition.Position);
|
||||
|
||||
if (Spread)
|
||||
{
|
||||
angle += Angle.FromDegrees(_spreadRandom.NextGaussian(0, SpreadStdDev));
|
||||
}
|
||||
|
||||
//Spawn the projectilePrototype
|
||||
var projectile = IoCManager.Resolve<IServerEntityManager>().ForceSpawnEntityAt(_ProjectilePrototype, userPosition);
|
||||
|
||||
//Give it the velocity we fire from this weapon, and make sure it doesn't shoot our character
|
||||
projectile.GetComponent<ProjectileComponent>().IgnoreEntity(user);
|
||||
@@ -36,7 +79,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile
|
||||
projectile.GetComponent<PhysicsComponent>().LinearVelocity = angle.ToVec() * _velocity;
|
||||
|
||||
//Rotate the bullets sprite to the correct direction, from north facing I guess
|
||||
projectile.GetComponent<ITransformComponent>().LocalRotation = angle.Theta;
|
||||
projectile.Transform.LocalRotation = angle.Theta;
|
||||
|
||||
// Sound!
|
||||
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<AudioSystem>().Play("/Audio/gunshot_c20.ogg");
|
||||
|
||||
@@ -1,35 +1,96 @@
|
||||
using SS14.Shared.GameObjects;
|
||||
using System;
|
||||
using SS14.Shared.GameObjects;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.Components.Weapons.Ranged;
|
||||
using SS14.Server.Interfaces.Player;
|
||||
using SS14.Shared.Interfaces.GameObjects;
|
||||
using SS14.Shared.Interfaces.Network;
|
||||
using SS14.Shared.Interfaces.Timing;
|
||||
using SS14.Shared.IoC;
|
||||
using SS14.Shared.Log;
|
||||
using SS14.Shared.Map;
|
||||
using SS14.Shared.Timers;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Weapon.Ranged
|
||||
{
|
||||
public class RangedWeaponComponent : Component, IAfterAttack
|
||||
public sealed class RangedWeaponComponent : SharedRangedWeaponComponent
|
||||
{
|
||||
public override string Name => "RangedWeapon";
|
||||
private TimeSpan _lastFireTime;
|
||||
|
||||
void IAfterAttack.Afterattack(IEntity user, GridLocalCoordinates clicklocation, IEntity attacked)
|
||||
public Func<bool> WeaponCanFireHandler;
|
||||
public Func<IEntity, bool> UserCanFireHandler;
|
||||
public Action<IEntity, GridLocalCoordinates> FireHandler;
|
||||
|
||||
private const int MaxFireDelayAttempts = 2;
|
||||
|
||||
private bool WeaponCanFire()
|
||||
{
|
||||
if (UserCanFire(user) && WeaponCanFire())
|
||||
{
|
||||
Fire(user, clicklocation);
|
||||
}
|
||||
return WeaponCanFireHandler == null || WeaponCanFireHandler();
|
||||
}
|
||||
|
||||
protected virtual bool WeaponCanFire()
|
||||
private bool UserCanFire(IEntity user)
|
||||
{
|
||||
return true;
|
||||
return UserCanFireHandler == null || UserCanFireHandler(user);
|
||||
}
|
||||
|
||||
protected virtual bool UserCanFire(IEntity user)
|
||||
private void Fire(IEntity user, GridLocalCoordinates clickLocation)
|
||||
{
|
||||
return true;
|
||||
_lastFireTime = IoCManager.Resolve<IGameTiming>().CurTime;
|
||||
FireHandler?.Invoke(user, clickLocation);
|
||||
}
|
||||
|
||||
protected virtual void Fire(IEntity user, GridLocalCoordinates clicklocation)
|
||||
public override void HandleMessage(ComponentMessage message, INetChannel netChannel = null,
|
||||
IComponent component = null)
|
||||
{
|
||||
base.HandleMessage(message, netChannel, component);
|
||||
|
||||
switch (message)
|
||||
{
|
||||
case FireMessage msg:
|
||||
var playerMgr = IoCManager.Resolve<IPlayerManager>();
|
||||
var session = playerMgr.GetSessionByChannel(netChannel);
|
||||
var user = session.AttachedEntity;
|
||||
if (user == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_tryFire(user, msg.Target, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void _tryFire(IEntity user, GridLocalCoordinates coordinates, int attemptCount)
|
||||
{
|
||||
if (!user.TryGetComponent(out HandsComponent hands) || hands.GetActiveHand.Owner != Owner)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!UserCanFire(user) || !WeaponCanFire())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Firing delays are quite complicated.
|
||||
// Sometimes the client's fire messages come in just too early.
|
||||
// Generally this is a frame or two of being early.
|
||||
// In that case we try them a few times the next frames to avoid having to drop them.
|
||||
var curTime = IoCManager.Resolve<IGameTiming>().CurTime;
|
||||
var span = curTime - _lastFireTime;
|
||||
if (span.TotalSeconds < 1 / FireRate)
|
||||
{
|
||||
if (attemptCount >= MaxFireDelayAttempts)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Timer.Spawn(TimeSpan.FromSeconds(1 / FireRate) - span,
|
||||
() => _tryFire(user, coordinates, attemptCount + 1));
|
||||
return;
|
||||
}
|
||||
|
||||
Fire(user, coordinates);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
<Compile Include="GameObjects\Components\Power\PowerShared.cs" />
|
||||
<Compile Include="GameObjects\Components\Power\SharedPowerCellComponent.cs" />
|
||||
<Compile Include="GameObjects\Components\Storage\SharedStorageComponent.cs" />
|
||||
<Compile Include="GameObjects\Components\Weapons\Ranged\SharedRangedWeaponComponent.cs" />
|
||||
<Compile Include="GameObjects\ContentNetIDs.cs" />
|
||||
<Compile Include="GameObjects\EntitySystemMessages\VerbSystemMessages.cs" />
|
||||
<Compile Include="GameObjects\Messages\Mob\HealthHud.cs" />
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using SS14.Shared.GameObjects;
|
||||
using SS14.Shared.Map;
|
||||
using SS14.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.GameObjects.Components.Weapons.Ranged
|
||||
{
|
||||
public class SharedRangedWeaponComponent : Component
|
||||
{
|
||||
private float _fireRate;
|
||||
private bool _automatic;
|
||||
public override string Name => "RangedWeapon";
|
||||
public override uint? NetID => ContentNetIDs.RANGED_WEAPON;
|
||||
|
||||
/// <summary>
|
||||
/// If true, this weapon is fully automatic, holding down left mouse button will keep firing it.
|
||||
/// </summary>
|
||||
public bool Automatic => _automatic;
|
||||
|
||||
/// <summary>
|
||||
/// If the weapon is automatic, controls how many shots can be fired per second.
|
||||
/// </summary>
|
||||
public float FireRate => _fireRate;
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataField(ref _fireRate, "firerate", 4);
|
||||
serializer.DataField(ref _automatic, "automatic", false);
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
protected class FireMessage : ComponentMessage
|
||||
{
|
||||
public readonly GridLocalCoordinates Target;
|
||||
public readonly int Tick;
|
||||
|
||||
public FireMessage(GridLocalCoordinates target, int tick)
|
||||
{
|
||||
Target = target;
|
||||
Tick = tick;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@
|
||||
public const uint INVENTORY = 1006;
|
||||
public const uint POWER_DEBUG_TOOL = 1007;
|
||||
public const uint CONSTRUCTOR = 1008;
|
||||
public const uint RANGED_WEAPON = 1010;
|
||||
public const uint SPECIES = 1009;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
- type: Icon
|
||||
sprite: Objects/laser_retro.rsi
|
||||
state: 100
|
||||
- type: RangedWeapon
|
||||
- type: HitscanWeapon
|
||||
damage: 30
|
||||
sprite: "Objects/laser.png"
|
||||
@@ -31,6 +32,9 @@
|
||||
- type: Icon
|
||||
sprite: Objects/c20r.rsi
|
||||
state: c20r-20
|
||||
- type: RangedWeapon
|
||||
automatic: true
|
||||
firerate: 8
|
||||
- type: ProjectileWeapon
|
||||
- type: Item
|
||||
Size: 24
|
||||
|
||||
Reference in New Issue
Block a user