Add a LOT more dakka (#1033)

* Start adding flashy flash

* Change slop

Might give a smoother decline

* flashy flash

* Add flashbang and flash projectiles

Bang bang bang pull my flash trigger

* Add collision check to area flash

* Flash cleanupo

* flash.ogg mixed to mono
* Adjusted flash curve again

* Enhancing flashes with unshaded and lights and shit

Still a WIP

* Add the other ballistic gun types

Re-organised some of the gun stuff so the powercell guns share the shooting code with the ballistic guns.

* Re-merging branch with master

Also fixed some visualizer bugs

* Last cleanup

Fixed some crashes
Fixed Deckard sprite
Fixed Hitscan effects
Re-applied master changes
Re-factor to using soundsystem
Add some more audio effects

* Cleanup flashes for merge

Can put flashbangs in lockers so you don't get blinded

Fix some bugs

* Fix shotties

Also removed some redundant code

* Bulldoze some legacycode

brrrrrrrrt

* Fix clientignore warnings

* Add the other Stunnable types to StunnableProjectile

* Some gun refactoring

* Removed extra visualizers
* All casing ejections use the same code
* Speed loaders can have their ammo pulled out
* Bolt sound less loud

* Stop ThrowController from throwing

* Fix speed loader visuals

* Update hitscan collision mask and fix typo

* Cleanup

* Fit hitscan and flashbang collisions
* Use the new flags support

* Update taser placeholder description

* Update protonames per style guide

* Add yaml flag support for gun firerates

* Cleanup crew

* Fix Audio up (components, audio file, + remove global sounds)
* Add server-side recoil back-in (forgot that I was testing this client-side)
* Add Flag support for fire-rate selectors

* Wrong int you dolt

* Fix AI conflicts

Haha ranged bulldozer go BRR
(I'll rewrite it after the other AI systems are done).

* Mix bang.ogg from stereo to mono

* Make sure serializer's reading for guns

Fixes integration test

* Change EntitySystem calls to use the static function

Also removed the Pumpbarrel commented-out code

* Change StunnableProjectile defaults to 0

* Fix taser paralyse

Apparently removing defaults means you have to specify the values, whodathunkit

* Add slowdown to stunnableprojectiles and fix tasers

* Remove FlagsFor from gun components

Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
Co-authored-by: Víctor Aguilera Puerto <6766154+Zumorica@users.noreply.github.com>
This commit is contained in:
metalgearsloth
2020-06-22 05:47:15 +10:00
committed by GitHub
parent ac19ad7eac
commit 95995b6232
1977 changed files with 13600 additions and 11229 deletions

View File

@@ -0,0 +1,149 @@
using System;
using System.Threading;
using Content.Shared.GameObjects.Components.Weapons;
using Robust.Client.Graphics.Drawing;
using Robust.Client.Graphics.Overlays;
using Robust.Client.Interfaces.Graphics;
using Robust.Client.Interfaces.Graphics.Overlays;
using Robust.Client.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.Timing;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using Timer = Robust.Shared.Timers.Timer;
namespace Content.Client.GameObjects.Components.Weapons
{
[RegisterComponent]
public sealed class ClientFlashableComponent : SharedFlashableComponent
{
private CancellationTokenSource _cancelToken;
private TimeSpan _startTime;
private double _duration;
private FlashOverlay _overlay;
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
{
if (curState == null)
{
return;
}
var playerManager = IoCManager.Resolve<IPlayerManager>();
if (playerManager.LocalPlayer.ControlledEntity != Owner)
{
return;
}
var newState = (FlashComponentState) curState;
if (newState.Time == default)
{
return;
}
// Few things here:
// 1. If a shorter duration flash is applied then don't do anything
// 2. If the client-side time is later than when the flash should've ended don't do anything
var currentTime = IoCManager.Resolve<IGameTiming>().CurTime.TotalSeconds;
var newEndTime = newState.Time.TotalSeconds + newState.Duration;
var currentEndTime = _startTime.TotalSeconds + _duration;
if (currentEndTime > newEndTime)
{
return;
}
if (currentTime > newEndTime)
{
DisableOverlay();
return;
}
_startTime = newState.Time;
_duration = newState.Duration;
EnableOverlay(newEndTime - currentTime);
}
private void EnableOverlay(double duration)
{
// If the timer gets reset
if (_overlay != null)
{
_overlay.Duration = _duration;
_overlay.StartTime = _startTime;
_cancelToken.Cancel();
}
else
{
var overlayManager = IoCManager.Resolve<IOverlayManager>();
_overlay = new FlashOverlay(_duration);
overlayManager.AddOverlay(_overlay);
}
_cancelToken = new CancellationTokenSource();
Timer.Spawn((int) duration * 1000, DisableOverlay, _cancelToken.Token);
}
private void DisableOverlay()
{
if (_overlay == null)
{
return;
}
var overlayManager = IoCManager.Resolve<IOverlayManager>();
overlayManager.RemoveOverlay(_overlay.ID);
_overlay = null;
_cancelToken.Cancel();
_cancelToken = null;
}
}
public sealed class FlashOverlay : Overlay
{
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
private readonly IGameTiming _timer;
private readonly IClyde _displayManager;
public TimeSpan StartTime { get; set; }
public double Duration { get; set; }
public FlashOverlay(double duration) : base(nameof(FlashOverlay))
{
_timer = IoCManager.Resolve<IGameTiming>();
_displayManager = IoCManager.Resolve<IClyde>();
StartTime = _timer.CurTime;
Duration = duration;
}
protected override void Draw(DrawingHandleBase handle)
{
var elapsedTime = (_timer.CurTime - StartTime).TotalSeconds;
if (elapsedTime > Duration)
{
return;
}
var screenHandle = (DrawingHandleScreen) handle;
screenHandle.DrawRect(
new UIBox2(0.0f, 0.0f, _displayManager.ScreenSize.X, _displayManager.ScreenSize.Y),
Color.White.WithAlpha(GetAlpha(elapsedTime / Duration))
);
}
private float GetAlpha(double ratio)
{
// Ideally you just want a smooth slope to finish it so it's not jarring at the end
// By all means put in a better curve
const float slope = -9.0f;
const float exponent = 0.1f;
const float yOffset = 9.0f;
const float xOffset = 0.0f;
// Overkill but easy to adjust if you want to mess around with the design
var result = (float) Math.Clamp(slope * (float) Math.Pow(ratio - xOffset, exponent) + yOffset, 0.0, 1.0);
DebugTools.Assert(!float.IsNaN(result));
return result;
}
}
}

View File

@@ -1,41 +0,0 @@
using Content.Shared.GameObjects.Components.Weapons.Ranged;
using Content.Shared.Utility;
using Robust.Client.GameObjects;
using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Shared.Utility;
using YamlDotNet.RepresentationModel;
namespace Content.Client.GameObjects.Components.Weapons.Ranged
{
public sealed class BallisticMagazineVisualizer2D : AppearanceVisualizer
{
private string _baseState;
private int _steps;
public override void LoadData(YamlMappingNode node)
{
base.LoadData(node);
_baseState = node.GetNode("base_state").AsString();
_steps = node.GetNode("steps").AsInt();
}
public override void OnChangeData(AppearanceComponent component)
{
var sprite = component.Owner.GetComponent<ISpriteComponent>();
if (!component.TryGetData(BallisticMagazineVisuals.AmmoCapacity, out int capacity))
{
return;
}
if (!component.TryGetData(BallisticMagazineVisuals.AmmoLeft, out int current))
{
return;
}
var step = ContentHelpers.RoundToLevels(current, capacity, _steps);
sprite.LayerSetState(0, $"{_baseState}-{step}");
}
}
}

View File

@@ -1,50 +0,0 @@
using Content.Shared.GameObjects.Components.Weapons.Ranged;
using Content.Shared.Utility;
using Robust.Client.GameObjects;
using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Shared.Utility;
using YamlDotNet.RepresentationModel;
namespace Content.Client.GameObjects.Components.Weapons.Ranged
{
public sealed class BallisticMagazineWeaponVisualizer2D : AppearanceVisualizer
{
private string _baseState;
private int _steps;
public override void LoadData(YamlMappingNode node)
{
base.LoadData(node);
_baseState = node.GetNode("base_state").AsString();
_steps = node.GetNode("steps").AsInt();
}
public override void OnChangeData(AppearanceComponent component)
{
var sprite = component.Owner.GetComponent<ISpriteComponent>();
component.TryGetData(BallisticMagazineWeaponVisuals.MagazineLoaded, out bool loaded);
if (loaded)
{
if (!component.TryGetData(BallisticMagazineWeaponVisuals.AmmoCapacity, out int capacity))
{
return;
}
if (!component.TryGetData(BallisticMagazineWeaponVisuals.AmmoLeft, out int current))
{
return;
}
// capacity is - 1 as normally a bullet is chambered so max state is virtually never hit.
var step = ContentHelpers.RoundToLevels(current, capacity - 1, _steps);
sprite.LayerSetState(0, $"{_baseState}-{step}");
}
else
{
sprite.LayerSetState(0, _baseState);
}
}
}
}

View File

@@ -1,28 +1,25 @@
using System;
using System;
using Content.Client.Animations;
using Content.Client.UserInterface;
using Content.Client.UserInterface.Stylesheets;
using Content.Client.Utility;
using Content.Shared.GameObjects;
using Content.Shared.GameObjects.Components.Weapons.Ranged;
using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels;
using Robust.Client.Animations;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Animations;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Network;
using Robust.Shared.Maths;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
using static Content.Client.StaticIoC;
namespace Content.Client.GameObjects.Components.Weapons.Ranged
namespace Content.Client.GameObjects.Components.Weapons.Ranged.Barrels
{
[RegisterComponent]
public class BallisticMagazineWeaponComponent : Component, IItemStatus
public class ClientMagazineBarrelComponent : Component, IItemStatus
{
private static readonly Animation AlarmAnimationSmg = new Animation
{
@@ -70,8 +67,8 @@ namespace Content.Client.GameObjects.Components.Weapons.Ranged
}
};
public override string Name => "BallisticMagazineWeapon";
public override uint? NetID => ContentNetIDs.BALLISTIC_MAGAZINE_WEAPON;
public override string Name => "MagazineBarrel";
public override uint? NetID => ContentNetIDs.MAGAZINE_BARREL;
private StatusControl _statusControl;
@@ -101,11 +98,11 @@ namespace Content.Client.GameObjects.Components.Weapons.Ranged
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
{
if (!(curState is BallisticMagazineWeaponComponentState cast))
if (!(curState is MagazineBarrelComponentState cast))
return;
Chambered = cast.Chambered;
MagazineCount = cast.MagazineCount;
MagazineCount = cast.Magazine;
_statusControl?.Update();
}
@@ -115,9 +112,10 @@ namespace Content.Client.GameObjects.Components.Weapons.Ranged
switch (message)
{
/*
case BmwComponentAutoEjectedMessage _:
_statusControl?.PlayAlarmAnimation();
return;
return;*/
}
}
@@ -138,13 +136,13 @@ namespace Content.Client.GameObjects.Components.Weapons.Ranged
private sealed class StatusControl : Control
{
private readonly BallisticMagazineWeaponComponent _parent;
private readonly ClientMagazineBarrelComponent _parent;
private readonly HBoxContainer _bulletsListTop;
private readonly HBoxContainer _bulletsListBottom;
private readonly TextureRect _chamberedBullet;
private readonly Label _noMagazineLabel;
public StatusControl(BallisticMagazineWeaponComponent parent)
public StatusControl(ClientMagazineBarrelComponent parent)
{
_parent = parent;
SizeFlagsHorizontal = SizeFlags.FillExpand;
@@ -181,7 +179,7 @@ namespace Content.Client.GameObjects.Components.Weapons.Ranged
},
(_chamberedBullet = new TextureRect
{
Texture = ResC.GetTexture("/Textures/UserInterface/status/bullets/chambered.png"),
Texture = StaticIoC.ResC.GetTexture("/Textures/UserInterface/status/bullets/chambered.png"),
SizeFlagsVertical = SizeFlags.ShrinkCenter,
SizeFlagsHorizontal = SizeFlags.ShrinkEnd | SizeFlags.Fill,
})
@@ -223,7 +221,7 @@ namespace Content.Client.GameObjects.Components.Weapons.Ranged
texturePath = "/Textures/UserInterface/status/bullets/tiny.png";
}
var texture = ResC.GetTexture(texturePath);
var texture = StaticIoC.ResC.GetTexture(texturePath);
const int tinyMaxRow = 60;
@@ -282,4 +280,4 @@ namespace Content.Client.GameObjects.Components.Weapons.Ranged
}
}
}
}
}

View File

@@ -0,0 +1,38 @@
using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Shared.Interfaces.GameObjects;
namespace Content.Client.GameObjects.Components.Weapons.Ranged.Barrels.Visualizers
{
[UsedImplicitly]
public sealed class BarrelBoltVisualizer2D : AppearanceVisualizer
{
public override void InitializeEntity(IEntity entity)
{
base.InitializeEntity(entity);
var sprite = entity.GetComponent<ISpriteComponent>();
sprite.LayerSetState(RangedBarrelVisualLayers.Bolt, $"bolt-open");
}
public override void OnChangeData(AppearanceComponent component)
{
var sprite = component.Owner.GetComponent<ISpriteComponent>();
if (!component.TryGetData(BarrelBoltVisuals.BoltOpen, out bool boltOpen))
{
return;
}
if (boltOpen)
{
sprite.LayerSetState(RangedBarrelVisualLayers.Bolt, "bolt-open");
}
else
{
sprite.LayerSetState(RangedBarrelVisualLayers.Bolt, "bolt-closed");
}
}
}
}

View File

@@ -0,0 +1,110 @@
using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels;
using Content.Shared.Utility;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Utility;
using YamlDotNet.RepresentationModel;
namespace Content.Client.GameObjects.Components.Weapons.Ranged.Barrels.Visualizers
{
[UsedImplicitly]
public sealed class MagVisualizer2D : AppearanceVisualizer
{
private bool _magLoaded;
private string _magState;
private int _magSteps;
private bool _zeroVisible;
public override void LoadData(YamlMappingNode node)
{
base.LoadData(node);
_magState = node.GetNode("magState").AsString();
_magSteps = node.GetNode("steps").AsInt();
_zeroVisible = node.GetNode("zeroVisible").AsBool();
}
public override void InitializeEntity(IEntity entity)
{
base.InitializeEntity(entity);
var sprite = entity.GetComponent<ISpriteComponent>();
if (sprite.LayerMapTryGet(RangedBarrelVisualLayers.Mag, out _))
{
sprite.LayerSetState(RangedBarrelVisualLayers.Mag, $"{_magState}-{_magSteps-1}");
sprite.LayerSetVisible(RangedBarrelVisualLayers.Mag, false);
}
if (sprite.LayerMapTryGet(RangedBarrelVisualLayers.MagUnshaded, out _))
{
sprite.LayerSetState(RangedBarrelVisualLayers.MagUnshaded, $"{_magState}-unshaded-{_magSteps-1}");
sprite.LayerSetVisible(RangedBarrelVisualLayers.MagUnshaded, false);
}
}
public override void OnChangeData(AppearanceComponent component)
{
// tl;dr
// 1.If no mag then hide it OR
// 2. If step 0 isn't visible then hide it (mag or unshaded)
// 3. Otherwise just do mag / unshaded as is
var sprite = component.Owner.GetComponent<ISpriteComponent>();
component.TryGetData(MagazineBarrelVisuals.MagLoaded, out _magLoaded);
if (_magLoaded)
{
if (!component.TryGetData(AmmoVisuals.AmmoMax, out int capacity))
{
return;
}
if (!component.TryGetData(AmmoVisuals.AmmoCount, out int current))
{
return;
}
var step = ContentHelpers.RoundToLevels(current, capacity, _magSteps);
if (step == 0 && !_zeroVisible)
{
if (sprite.LayerMapTryGet(RangedBarrelVisualLayers.Mag, out _))
{
sprite.LayerSetVisible(RangedBarrelVisualLayers.Mag, false);
}
if (sprite.LayerMapTryGet(RangedBarrelVisualLayers.MagUnshaded, out _))
{
sprite.LayerSetVisible(RangedBarrelVisualLayers.MagUnshaded, false);
}
return;
}
if (sprite.LayerMapTryGet(RangedBarrelVisualLayers.Mag, out _))
{
sprite.LayerSetVisible(RangedBarrelVisualLayers.Mag, true);
sprite.LayerSetState(RangedBarrelVisualLayers.Mag, $"{_magState}-{step}");
}
if (sprite.LayerMapTryGet(RangedBarrelVisualLayers.MagUnshaded, out _))
{
sprite.LayerSetVisible(RangedBarrelVisualLayers.MagUnshaded, true);
sprite.LayerSetState(RangedBarrelVisualLayers.MagUnshaded, $"{_magState}-unshaded-{step}");
}
}
else
{
if (sprite.LayerMapTryGet(RangedBarrelVisualLayers.Mag, out _))
{
sprite.LayerSetVisible(RangedBarrelVisualLayers.Mag, false);
}
if (sprite.LayerMapTryGet(RangedBarrelVisualLayers.MagUnshaded, out _))
{
sprite.LayerSetVisible(RangedBarrelVisualLayers.MagUnshaded, false);
}
}
}
}
}

View File

@@ -0,0 +1,29 @@
using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.Interfaces.GameObjects.Components;
namespace Content.Client.GameObjects.Components.Weapons.Ranged.Barrels.Visualizers
{
[UsedImplicitly]
public sealed class SpentAmmoVisualizer2D : AppearanceVisualizer
{
public override void OnChangeData(AppearanceComponent component)
{
base.OnChangeData(component);
var sprite = component.Owner.GetComponent<ISpriteComponent>();
if (!component.TryGetData(AmmoVisuals.Spent, out bool spent))
{
return;
}
sprite.LayerSetState(AmmoVisualLayers.Base, spent ? "spent" : "base");
}
}
public enum AmmoVisualLayers
{
Base,
}
}

View File

@@ -4,12 +4,35 @@ using Robust.Shared.Map;
namespace Content.Client.GameObjects.Components.Weapons.Ranged
{
// Yeah I put it all in the same enum, don't judge me
public enum RangedBarrelVisualLayers
{
Base,
BaseUnshaded,
Bolt,
Mag,
MagUnshaded,
}
[RegisterComponent]
public sealed class ClientRangedWeaponComponent : SharedRangedWeaponComponent
{
public FireRateSelector FireRateSelector { get; private set; } = FireRateSelector.Safety;
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
{
base.HandleComponentState(curState, nextState);
if (!(curState is RangedWeaponComponentState rangedState))
{
return;
}
FireRateSelector = rangedState.FireRateSelector;
}
public void SyncFirePos(GridCoordinates worldPos)
{
SendNetworkMessage(new SyncFirePosMessage(worldPos));
SendNetworkMessage(new FirePosComponentMessage(worldPos));
}
}
}
}

View File

@@ -1,32 +0,0 @@
using Content.Shared.GameObjects.Components.Power;
using Content.Shared.Utility;
using Robust.Client.GameObjects;
using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Shared.Utility;
using YamlDotNet.RepresentationModel;
namespace Content.Client.GameObjects.Components.Power
{
public class HitscanWeaponVisualizer2D : AppearanceVisualizer
{
private string _prefix;
public override void LoadData(YamlMappingNode node)
{
base.LoadData(node);
_prefix = node.GetNode("prefix").AsString();
}
public override void OnChangeData(AppearanceComponent component)
{
base.OnChangeData(component);
var sprite = component.Owner.GetComponent<ISpriteComponent>();
if (component.TryGetData(PowerCellVisuals.ChargeLevel, out float fraction))
{
sprite.LayerSetState(0, $"{_prefix}_{ContentHelpers.RoundToLevels(fraction, 1, 5) * 25}");
}
}
}
}

View File

@@ -1,5 +1,7 @@
using Content.Client.GameObjects.Components.Weapons.Ranged;
using System;
using Content.Client.GameObjects.Components.Weapons.Ranged;
using Content.Client.Interfaces.GameObjects;
using Content.Shared.GameObjects.Components.Weapons.Ranged;
using Robust.Client.GameObjects.EntitySystems;
using Robust.Client.Interfaces.Graphics.ClientEye;
using Robust.Client.Interfaces.Input;
@@ -26,8 +28,8 @@ namespace Content.Client.GameObjects.EntitySystems
private InputSystem _inputSystem;
private CombatModeSystem _combatModeSystem;
private bool _isFirstShot;
private bool _blocked;
private int _shotCounter;
public override void Initialize()
{
@@ -46,18 +48,15 @@ namespace Content.Client.GameObjects.EntitySystems
{
return;
}
var canFireSemi = _isFirstShot;
var state = _inputSystem.CmdStates.GetState(EngineKeyFunctions.Use);
if (!_combatModeSystem.IsInCombatMode() || state != BoundKeyState.Down)
{
_isFirstShot = true;
_shotCounter = 0;
_blocked = false;
return;
}
_isFirstShot = false;
var entity = _playerManager.LocalPlayer.ControlledEntity;
if (entity == null || !entity.TryGetComponent(out IHandsComponent hands))
{
@@ -71,6 +70,25 @@ namespace Content.Client.GameObjects.EntitySystems
return;
}
switch (weapon.FireRateSelector)
{
case FireRateSelector.Safety:
_blocked = true;
return;
case FireRateSelector.Single:
if (_shotCounter >= 1)
{
_blocked = true;
return;
}
break;
case FireRateSelector.Automatic:
break;
default:
throw new ArgumentOutOfRangeException();
}
if (_blocked)
{
return;
@@ -81,10 +99,7 @@ namespace Content.Client.GameObjects.EntitySystems
if (!_mapManager.TryFindGridAt(worldPos, out var grid))
grid = _mapManager.GetDefaultGrid(worldPos.MapId);
if (weapon.Automatic || canFireSemi)
{
weapon.SyncFirePos(grid.MapToGrid(worldPos));
}
weapon.SyncFirePos(grid.MapToGrid(worldPos));
}
}
}