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:
@@ -85,8 +85,6 @@ namespace Content.Client
|
|||||||
"Multitool",
|
"Multitool",
|
||||||
"Wrench",
|
"Wrench",
|
||||||
"Crowbar",
|
"Crowbar",
|
||||||
"HitscanWeapon",
|
|
||||||
"ProjectileWeapon",
|
|
||||||
"Projectile",
|
"Projectile",
|
||||||
"MeleeWeapon",
|
"MeleeWeapon",
|
||||||
"Storeable",
|
"Storeable",
|
||||||
@@ -100,8 +98,8 @@ namespace Content.Client
|
|||||||
"LightBulb",
|
"LightBulb",
|
||||||
"Healing",
|
"Healing",
|
||||||
"Catwalk",
|
"Catwalk",
|
||||||
"BallisticMagazine",
|
"RangedMagazine",
|
||||||
"BallisticBullet",
|
"Ammo",
|
||||||
"HitscanWeaponCapacitor",
|
"HitscanWeaponCapacitor",
|
||||||
"PowerCell",
|
"PowerCell",
|
||||||
"WeaponCapacitorCharger",
|
"WeaponCapacitorCharger",
|
||||||
@@ -148,6 +146,13 @@ namespace Content.Client
|
|||||||
"Bucket",
|
"Bucket",
|
||||||
"Puddle",
|
"Puddle",
|
||||||
"CanSpill",
|
"CanSpill",
|
||||||
|
"SpeedLoader",
|
||||||
|
"Hitscan",
|
||||||
|
"BoltActionBarrel",
|
||||||
|
"PumpBarrel",
|
||||||
|
"RevolverBarrel",
|
||||||
|
"ExplosiveProjectile",
|
||||||
|
"StunnableProjectile",
|
||||||
"RandomPottedPlant",
|
"RandomPottedPlant",
|
||||||
"CommunicationsConsole",
|
"CommunicationsConsole",
|
||||||
"BarSign",
|
"BarSign",
|
||||||
@@ -167,6 +172,9 @@ namespace Content.Client
|
|||||||
"SecureEntityStorage",
|
"SecureEntityStorage",
|
||||||
"PresetIdCard",
|
"PresetIdCard",
|
||||||
"SolarControlConsole",
|
"SolarControlConsole",
|
||||||
|
"BatteryBarrel",
|
||||||
|
"FlashExplosive",
|
||||||
|
"FlashProjectile",
|
||||||
"Utensil",
|
"Utensil",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +1,25 @@
|
|||||||
using System;
|
using System;
|
||||||
using Content.Client.Animations;
|
using Content.Client.Animations;
|
||||||
using Content.Client.UserInterface;
|
|
||||||
using Content.Client.UserInterface.Stylesheets;
|
using Content.Client.UserInterface.Stylesheets;
|
||||||
using Content.Client.Utility;
|
using Content.Client.Utility;
|
||||||
using Content.Shared.GameObjects;
|
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.Animations;
|
||||||
using Robust.Client.Graphics;
|
using Robust.Client.Graphics;
|
||||||
using Robust.Client.UserInterface;
|
using Robust.Client.UserInterface;
|
||||||
using Robust.Client.UserInterface.Controls;
|
using Robust.Client.UserInterface.Controls;
|
||||||
using Robust.Shared.Animations;
|
using Robust.Shared.Animations;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
|
||||||
using Robust.Shared.Interfaces.Network;
|
using Robust.Shared.Interfaces.Network;
|
||||||
using Robust.Shared.Maths;
|
using Robust.Shared.Maths;
|
||||||
using Robust.Shared.Players;
|
using Robust.Shared.Players;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
using Robust.Shared.ViewVariables;
|
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]
|
[RegisterComponent]
|
||||||
public class BallisticMagazineWeaponComponent : Component, IItemStatus
|
public class ClientMagazineBarrelComponent : Component, IItemStatus
|
||||||
{
|
{
|
||||||
private static readonly Animation AlarmAnimationSmg = new Animation
|
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 string Name => "MagazineBarrel";
|
||||||
public override uint? NetID => ContentNetIDs.BALLISTIC_MAGAZINE_WEAPON;
|
public override uint? NetID => ContentNetIDs.MAGAZINE_BARREL;
|
||||||
|
|
||||||
private StatusControl _statusControl;
|
private StatusControl _statusControl;
|
||||||
|
|
||||||
@@ -101,11 +98,11 @@ namespace Content.Client.GameObjects.Components.Weapons.Ranged
|
|||||||
|
|
||||||
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
|
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
|
||||||
{
|
{
|
||||||
if (!(curState is BallisticMagazineWeaponComponentState cast))
|
if (!(curState is MagazineBarrelComponentState cast))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Chambered = cast.Chambered;
|
Chambered = cast.Chambered;
|
||||||
MagazineCount = cast.MagazineCount;
|
MagazineCount = cast.Magazine;
|
||||||
_statusControl?.Update();
|
_statusControl?.Update();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,9 +112,10 @@ namespace Content.Client.GameObjects.Components.Weapons.Ranged
|
|||||||
|
|
||||||
switch (message)
|
switch (message)
|
||||||
{
|
{
|
||||||
|
/*
|
||||||
case BmwComponentAutoEjectedMessage _:
|
case BmwComponentAutoEjectedMessage _:
|
||||||
_statusControl?.PlayAlarmAnimation();
|
_statusControl?.PlayAlarmAnimation();
|
||||||
return;
|
return;*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,13 +136,13 @@ namespace Content.Client.GameObjects.Components.Weapons.Ranged
|
|||||||
|
|
||||||
private sealed class StatusControl : Control
|
private sealed class StatusControl : Control
|
||||||
{
|
{
|
||||||
private readonly BallisticMagazineWeaponComponent _parent;
|
private readonly ClientMagazineBarrelComponent _parent;
|
||||||
private readonly HBoxContainer _bulletsListTop;
|
private readonly HBoxContainer _bulletsListTop;
|
||||||
private readonly HBoxContainer _bulletsListBottom;
|
private readonly HBoxContainer _bulletsListBottom;
|
||||||
private readonly TextureRect _chamberedBullet;
|
private readonly TextureRect _chamberedBullet;
|
||||||
private readonly Label _noMagazineLabel;
|
private readonly Label _noMagazineLabel;
|
||||||
|
|
||||||
public StatusControl(BallisticMagazineWeaponComponent parent)
|
public StatusControl(ClientMagazineBarrelComponent parent)
|
||||||
{
|
{
|
||||||
_parent = parent;
|
_parent = parent;
|
||||||
SizeFlagsHorizontal = SizeFlags.FillExpand;
|
SizeFlagsHorizontal = SizeFlags.FillExpand;
|
||||||
@@ -181,7 +179,7 @@ namespace Content.Client.GameObjects.Components.Weapons.Ranged
|
|||||||
},
|
},
|
||||||
(_chamberedBullet = new TextureRect
|
(_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,
|
SizeFlagsVertical = SizeFlags.ShrinkCenter,
|
||||||
SizeFlagsHorizontal = SizeFlags.ShrinkEnd | SizeFlags.Fill,
|
SizeFlagsHorizontal = SizeFlags.ShrinkEnd | SizeFlags.Fill,
|
||||||
})
|
})
|
||||||
@@ -223,7 +221,7 @@ namespace Content.Client.GameObjects.Components.Weapons.Ranged
|
|||||||
texturePath = "/Textures/UserInterface/status/bullets/tiny.png";
|
texturePath = "/Textures/UserInterface/status/bullets/tiny.png";
|
||||||
}
|
}
|
||||||
|
|
||||||
var texture = ResC.GetTexture(texturePath);
|
var texture = StaticIoC.ResC.GetTexture(texturePath);
|
||||||
|
|
||||||
const int tinyMaxRow = 60;
|
const int tinyMaxRow = 60;
|
||||||
|
|
||||||
@@ -282,4 +280,4 @@ namespace Content.Client.GameObjects.Components.Weapons.Ranged
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,12 +4,35 @@ using Robust.Shared.Map;
|
|||||||
|
|
||||||
namespace Content.Client.GameObjects.Components.Weapons.Ranged
|
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]
|
[RegisterComponent]
|
||||||
public sealed class ClientRangedWeaponComponent : SharedRangedWeaponComponent
|
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)
|
public void SyncFirePos(GridCoordinates worldPos)
|
||||||
{
|
{
|
||||||
SendNetworkMessage(new SyncFirePosMessage(worldPos));
|
SendNetworkMessage(new FirePosComponentMessage(worldPos));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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.Client.Interfaces.GameObjects;
|
||||||
|
using Content.Shared.GameObjects.Components.Weapons.Ranged;
|
||||||
using Robust.Client.GameObjects.EntitySystems;
|
using Robust.Client.GameObjects.EntitySystems;
|
||||||
using Robust.Client.Interfaces.Graphics.ClientEye;
|
using Robust.Client.Interfaces.Graphics.ClientEye;
|
||||||
using Robust.Client.Interfaces.Input;
|
using Robust.Client.Interfaces.Input;
|
||||||
@@ -26,8 +28,8 @@ namespace Content.Client.GameObjects.EntitySystems
|
|||||||
|
|
||||||
private InputSystem _inputSystem;
|
private InputSystem _inputSystem;
|
||||||
private CombatModeSystem _combatModeSystem;
|
private CombatModeSystem _combatModeSystem;
|
||||||
private bool _isFirstShot;
|
|
||||||
private bool _blocked;
|
private bool _blocked;
|
||||||
|
private int _shotCounter;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
@@ -46,18 +48,15 @@ namespace Content.Client.GameObjects.EntitySystems
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var canFireSemi = _isFirstShot;
|
|
||||||
var state = _inputSystem.CmdStates.GetState(EngineKeyFunctions.Use);
|
var state = _inputSystem.CmdStates.GetState(EngineKeyFunctions.Use);
|
||||||
if (!_combatModeSystem.IsInCombatMode() || state != BoundKeyState.Down)
|
if (!_combatModeSystem.IsInCombatMode() || state != BoundKeyState.Down)
|
||||||
{
|
{
|
||||||
_isFirstShot = true;
|
_shotCounter = 0;
|
||||||
_blocked = false;
|
_blocked = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_isFirstShot = false;
|
|
||||||
|
|
||||||
var entity = _playerManager.LocalPlayer.ControlledEntity;
|
var entity = _playerManager.LocalPlayer.ControlledEntity;
|
||||||
if (entity == null || !entity.TryGetComponent(out IHandsComponent hands))
|
if (entity == null || !entity.TryGetComponent(out IHandsComponent hands))
|
||||||
{
|
{
|
||||||
@@ -71,6 +70,25 @@ namespace Content.Client.GameObjects.EntitySystems
|
|||||||
return;
|
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)
|
if (_blocked)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -81,10 +99,7 @@ namespace Content.Client.GameObjects.EntitySystems
|
|||||||
if (!_mapManager.TryFindGridAt(worldPos, out var grid))
|
if (!_mapManager.TryFindGridAt(worldPos, out var grid))
|
||||||
grid = _mapManager.GetDefaultGrid(worldPos.MapId);
|
grid = _mapManager.GetDefaultGrid(worldPos.MapId);
|
||||||
|
|
||||||
if (weapon.Automatic || canFireSemi)
|
weapon.SyncFirePos(grid.MapToGrid(worldPos));
|
||||||
{
|
|
||||||
weapon.SyncFirePos(grid.MapToGrid(worldPos));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,100 +0,0 @@
|
|||||||
using Content.Server.GameObjects;
|
|
||||||
using Content.Server.GameObjects.Components.Mobs;
|
|
||||||
using Content.Server.GameObjects.Components.Movement;
|
|
||||||
using Content.Server.GameObjects.Components.Weapon.Ranged;
|
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
|
||||||
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Operators.Combat.Ranged
|
|
||||||
{
|
|
||||||
public class ShootAtEntityOperator : AiOperator
|
|
||||||
{
|
|
||||||
private IEntity _owner;
|
|
||||||
private IEntity _target;
|
|
||||||
private float _accuracy;
|
|
||||||
|
|
||||||
private float _burstTime;
|
|
||||||
|
|
||||||
private float _elapsedTime;
|
|
||||||
|
|
||||||
public ShootAtEntityOperator(IEntity owner, IEntity target, float accuracy, float burstTime = 0.5f)
|
|
||||||
{
|
|
||||||
_owner = owner;
|
|
||||||
_target = target;
|
|
||||||
_accuracy = accuracy;
|
|
||||||
_burstTime = burstTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool TryStartup()
|
|
||||||
{
|
|
||||||
if (!base.TryStartup())
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_owner.TryGetComponent(out CombatModeComponent combatModeComponent))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!combatModeComponent.IsInCombatMode)
|
|
||||||
{
|
|
||||||
combatModeComponent.IsInCombatMode = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Shutdown(Outcome outcome)
|
|
||||||
{
|
|
||||||
base.Shutdown(outcome);
|
|
||||||
if (_owner.TryGetComponent(out CombatModeComponent combatModeComponent))
|
|
||||||
{
|
|
||||||
combatModeComponent.IsInCombatMode = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Outcome Execute(float frameTime)
|
|
||||||
{
|
|
||||||
// TODO: Probably just do all the checks on first try and then after that repeat the fire.
|
|
||||||
if (_burstTime <= _elapsedTime)
|
|
||||||
{
|
|
||||||
return Outcome.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
_elapsedTime += frameTime;
|
|
||||||
|
|
||||||
if (_target.TryGetComponent(out DamageableComponent damageableComponent))
|
|
||||||
{
|
|
||||||
if (damageableComponent.IsDead())
|
|
||||||
{
|
|
||||||
return Outcome.Success;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_owner.TryGetComponent(out HandsComponent hands) || hands.GetActiveHand == null)
|
|
||||||
{
|
|
||||||
return Outcome.Failed;
|
|
||||||
}
|
|
||||||
|
|
||||||
var equippedWeapon = hands.GetActiveHand.Owner;
|
|
||||||
|
|
||||||
if ((_target.Transform.GridPosition.Position - _owner.Transform.GridPosition.Position).Length >
|
|
||||||
_owner.GetComponent<AiControllerComponent>().VisionRadius)
|
|
||||||
{
|
|
||||||
// Not necessarily a hard fail, more of a soft fail
|
|
||||||
return Outcome.Failed;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unless RangedWeaponComponent is removed from hitscan weapons this shouldn't happen
|
|
||||||
if (!equippedWeapon.TryGetComponent(out RangedWeaponComponent rangedWeaponComponent))
|
|
||||||
{
|
|
||||||
return Outcome.Failed;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Accuracy
|
|
||||||
rangedWeaponComponent.AiFire(_owner, _target.Transform.GridPosition);
|
|
||||||
return Outcome.Continuing;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Content.Server.GameObjects.Components.Weapon.Ranged.Hitscan;
|
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Operators.Combat.Ranged
|
|
||||||
{
|
|
||||||
public class WaitForHitscanChargeOperator : AiOperator
|
|
||||||
{
|
|
||||||
private float _lastCharge = 0.0f;
|
|
||||||
private float _lastFill = 0.0f;
|
|
||||||
private HitscanWeaponComponent _hitscan;
|
|
||||||
|
|
||||||
public WaitForHitscanChargeOperator(IEntity entity)
|
|
||||||
{
|
|
||||||
if (!entity.TryGetComponent(out HitscanWeaponComponent hitscanWeaponComponent))
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
_hitscan = hitscanWeaponComponent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Outcome Execute(float frameTime)
|
|
||||||
{
|
|
||||||
if (_hitscan.CapacitorComponent.Capacity - _hitscan.CapacitorComponent.Charge < 0.01f)
|
|
||||||
{
|
|
||||||
return Outcome.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we're not charging then just stop
|
|
||||||
_lastFill = _hitscan.CapacitorComponent.Charge - _lastCharge;
|
|
||||||
_lastCharge = _hitscan.CapacitorComponent.Charge;
|
|
||||||
|
|
||||||
if (_lastFill == 0.0f)
|
|
||||||
{
|
|
||||||
return Outcome.Failed;
|
|
||||||
}
|
|
||||||
return Outcome.Continuing;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,6 @@ using Content.Server.AI.Operators;
|
|||||||
using Content.Server.AI.Operators.Inventory;
|
using Content.Server.AI.Operators.Inventory;
|
||||||
using Content.Server.AI.Utility.Considerations;
|
using Content.Server.AI.Utility.Considerations;
|
||||||
using Content.Server.AI.Utility.Considerations.Combat.Melee;
|
using Content.Server.AI.Utility.Considerations.Combat.Melee;
|
||||||
using Content.Server.AI.Utility.Considerations.Combat.Ranged;
|
|
||||||
using Content.Server.AI.Utility.Considerations.Inventory;
|
using Content.Server.AI.Utility.Considerations.Inventory;
|
||||||
using Content.Server.AI.Utility.Curves;
|
using Content.Server.AI.Utility.Curves;
|
||||||
using Content.Server.AI.WorldState;
|
using Content.Server.AI.WorldState;
|
||||||
@@ -41,9 +40,6 @@ namespace Content.Server.AI.Utility.Actions.Combat.Melee
|
|||||||
protected override Consideration[] Considerations { get; } = {
|
protected override Consideration[] Considerations { get; } = {
|
||||||
new MeleeWeaponEquippedCon(
|
new MeleeWeaponEquippedCon(
|
||||||
new InverseBoolCurve()),
|
new InverseBoolCurve()),
|
||||||
// We'll prioritise equipping ranged weapons; If we try and score this then it'll just keep swapping between ranged and melee
|
|
||||||
new RangedWeaponEquippedCon(
|
|
||||||
new InverseBoolCurve()),
|
|
||||||
new CanPutTargetInHandsCon(
|
new CanPutTargetInHandsCon(
|
||||||
new BoolCurve()),
|
new BoolCurve()),
|
||||||
new MeleeWeaponSpeedCon(
|
new MeleeWeaponSpeedCon(
|
||||||
|
|||||||
@@ -1,97 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using Content.Server.AI.Operators;
|
|
||||||
using Content.Server.AI.Operators.Combat.Ranged;
|
|
||||||
using Content.Server.AI.Operators.Movement;
|
|
||||||
using Content.Server.AI.Utility.Considerations;
|
|
||||||
using Content.Server.AI.Utility.Considerations.Combat;
|
|
||||||
using Content.Server.AI.Utility.Considerations.Combat.Ranged.Ballistic;
|
|
||||||
using Content.Server.AI.Utility.Considerations.Movement;
|
|
||||||
using Content.Server.AI.Utility.Curves;
|
|
||||||
using Content.Server.AI.Utils;
|
|
||||||
using Content.Server.AI.WorldState;
|
|
||||||
using Content.Server.AI.WorldState.States;
|
|
||||||
using Content.Server.AI.WorldState.States.Combat;
|
|
||||||
using Content.Server.AI.WorldState.States.Inventory;
|
|
||||||
using Content.Server.AI.WorldState.States.Movement;
|
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.Actions.Combat.Ranged.Ballistic
|
|
||||||
{
|
|
||||||
public sealed class BallisticAttackEntity : UtilityAction
|
|
||||||
{
|
|
||||||
private IEntity _entity;
|
|
||||||
private MoveToEntityOperator _moveOperator;
|
|
||||||
|
|
||||||
public BallisticAttackEntity(IEntity owner, IEntity entity, float weight) : base(owner)
|
|
||||||
{
|
|
||||||
_entity = entity;
|
|
||||||
Bonus = weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Shutdown()
|
|
||||||
{
|
|
||||||
base.Shutdown();
|
|
||||||
if (_moveOperator != null)
|
|
||||||
{
|
|
||||||
_moveOperator.MovedATile -= InLos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void SetupOperators(Blackboard context)
|
|
||||||
{
|
|
||||||
_moveOperator = new MoveToEntityOperator(Owner, _entity);
|
|
||||||
_moveOperator.MovedATile += InLos;
|
|
||||||
|
|
||||||
// TODO: Accuracy in blackboard
|
|
||||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
|
||||||
{
|
|
||||||
_moveOperator,
|
|
||||||
new ShootAtEntityOperator(Owner, _entity, 0.7f),
|
|
||||||
});
|
|
||||||
|
|
||||||
// We will do a quick check now to see if we even need to move which also saves a pathfind
|
|
||||||
InLos();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void UpdateBlackboard(Blackboard context)
|
|
||||||
{
|
|
||||||
base.UpdateBlackboard(context);
|
|
||||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
|
||||||
context.GetState<MoveTargetState>().SetValue(_entity);
|
|
||||||
var equipped = context.GetState<EquippedEntityState>().GetValue();
|
|
||||||
context.GetState<WeaponEntityState>().SetValue(equipped);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Consideration[] Considerations { get; } = {
|
|
||||||
// Check if we have a weapon; easy-out
|
|
||||||
new BallisticWeaponEquippedCon(
|
|
||||||
new BoolCurve()),
|
|
||||||
new BallisticAmmoCon(
|
|
||||||
new QuadraticCurve(1.0f, 0.15f, 0.0f, 0.0f)),
|
|
||||||
// Don't attack a dead target
|
|
||||||
new TargetIsDeadCon(
|
|
||||||
new InverseBoolCurve()),
|
|
||||||
// Deprioritise a target in crit
|
|
||||||
new TargetIsCritCon(
|
|
||||||
new QuadraticCurve(-0.8f, 1.0f, 1.0f, 0.0f)),
|
|
||||||
// Somewhat prioritise distance
|
|
||||||
new DistanceCon(
|
|
||||||
new QuadraticCurve(1.0f, 1.0f, 0.07f, 0.0f)),
|
|
||||||
// Prefer weaker targets
|
|
||||||
new TargetHealthCon(
|
|
||||||
new QuadraticCurve(1.0f, 0.4f, 0.0f, -0.02f)),
|
|
||||||
};
|
|
||||||
|
|
||||||
private void InLos()
|
|
||||||
{
|
|
||||||
// This should only be called if the movement operator is the current one;
|
|
||||||
// if that turns out not to be the case we can just add a check here.
|
|
||||||
if (Visibility.InLineOfSight(Owner, _entity))
|
|
||||||
{
|
|
||||||
_moveOperator.HaveArrived();
|
|
||||||
var mover = ActionOperators.Dequeue();
|
|
||||||
mover.Shutdown(Outcome.Success);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using Content.Server.AI.Operators;
|
|
||||||
using Content.Server.AI.Operators.Inventory;
|
|
||||||
using Content.Server.AI.Utility.Considerations;
|
|
||||||
using Content.Server.AI.Utility.Considerations.Combat.Ranged.Ballistic;
|
|
||||||
using Content.Server.AI.Utility.Considerations.Inventory;
|
|
||||||
using Content.Server.AI.Utility.Curves;
|
|
||||||
using Content.Server.AI.WorldState;
|
|
||||||
using Content.Server.AI.WorldState.States;
|
|
||||||
using Content.Server.AI.WorldState.States.Combat;
|
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.Actions.Combat.Ranged.Ballistic
|
|
||||||
{
|
|
||||||
public sealed class DropEmptyBallistic : UtilityAction
|
|
||||||
{
|
|
||||||
public sealed override float Bonus => 20.0f;
|
|
||||||
private IEntity _entity;
|
|
||||||
|
|
||||||
public DropEmptyBallistic(IEntity owner, IEntity entity, float weight) : base(owner)
|
|
||||||
{
|
|
||||||
_entity = entity;
|
|
||||||
Bonus = weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void SetupOperators(Blackboard context)
|
|
||||||
{
|
|
||||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
|
||||||
{
|
|
||||||
new EquipEntityOperator(Owner, _entity),
|
|
||||||
new DropEntityOperator(Owner, _entity)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void UpdateBlackboard(Blackboard context)
|
|
||||||
{
|
|
||||||
base.UpdateBlackboard(context);
|
|
||||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
|
||||||
context.GetState<WeaponEntityState>().SetValue(_entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Consideration[] Considerations { get; } = {
|
|
||||||
new TargetInOurInventoryCon(
|
|
||||||
new BoolCurve()),
|
|
||||||
// Need to put in hands to drop
|
|
||||||
new CanPutTargetInHandsCon(
|
|
||||||
new BoolCurve()),
|
|
||||||
// Drop that sucker
|
|
||||||
new BallisticAmmoCon(
|
|
||||||
new InverseBoolCurve()),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using Content.Server.AI.Operators;
|
|
||||||
using Content.Server.AI.Operators.Inventory;
|
|
||||||
using Content.Server.AI.Utility.Considerations;
|
|
||||||
using Content.Server.AI.Utility.Considerations.Combat.Melee;
|
|
||||||
using Content.Server.AI.Utility.Considerations.Combat.Ranged;
|
|
||||||
using Content.Server.AI.Utility.Considerations.Combat.Ranged.Ballistic;
|
|
||||||
using Content.Server.AI.Utility.Considerations.Inventory;
|
|
||||||
using Content.Server.AI.Utility.Curves;
|
|
||||||
using Content.Server.AI.WorldState;
|
|
||||||
using Content.Server.AI.WorldState.States.Combat;
|
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.Actions.Combat.Ranged.Ballistic
|
|
||||||
{
|
|
||||||
public sealed class EquipBallistic : UtilityAction
|
|
||||||
{
|
|
||||||
private IEntity _entity;
|
|
||||||
|
|
||||||
public EquipBallistic(IEntity owner, IEntity entity, float weight) : base(owner)
|
|
||||||
{
|
|
||||||
_entity = entity;
|
|
||||||
Bonus = weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void SetupOperators(Blackboard context)
|
|
||||||
{
|
|
||||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
|
||||||
{
|
|
||||||
new EquipEntityOperator(Owner, _entity)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void UpdateBlackboard(Blackboard context)
|
|
||||||
{
|
|
||||||
base.UpdateBlackboard(context);
|
|
||||||
context.GetState<WeaponEntityState>().SetValue(_entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Consideration[] Considerations { get; } = {
|
|
||||||
new EquippedBallisticCon(
|
|
||||||
new InverseBoolCurve()),
|
|
||||||
new MeleeWeaponEquippedCon(
|
|
||||||
new QuadraticCurve(0.9f, 1.0f, 0.1f, 0.0f)),
|
|
||||||
new CanPutTargetInHandsCon(
|
|
||||||
new BoolCurve()),
|
|
||||||
new BallisticAmmoCon(
|
|
||||||
new QuadraticCurve(1.0f, 0.15f, 0.0f, 0.0f)),
|
|
||||||
new RangedWeaponFireRateCon(
|
|
||||||
new QuadraticCurve(1.0f, 0.5f, 0.0f, 0.0f)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
using Content.Server.AI.Operators.Sequences;
|
|
||||||
using Content.Server.AI.Utility.Considerations;
|
|
||||||
using Content.Server.AI.Utility.Considerations.Containers;
|
|
||||||
using Content.Server.AI.Utility.Considerations.Hands;
|
|
||||||
using Content.Server.AI.Utility.Considerations.Movement;
|
|
||||||
using Content.Server.AI.Utility.Curves;
|
|
||||||
using Content.Server.AI.WorldState;
|
|
||||||
using Content.Server.AI.WorldState.States;
|
|
||||||
using Content.Server.AI.WorldState.States.Movement;
|
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.Actions.Combat.Ranged.Ballistic
|
|
||||||
{
|
|
||||||
public sealed class PickUpAmmo : UtilityAction
|
|
||||||
{
|
|
||||||
private IEntity _entity;
|
|
||||||
|
|
||||||
public PickUpAmmo(IEntity owner, IEntity entity, float weight) : base(owner)
|
|
||||||
{
|
|
||||||
_entity = entity;
|
|
||||||
Bonus = weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void SetupOperators(Blackboard context)
|
|
||||||
{
|
|
||||||
ActionOperators = new GoPickupEntitySequence(Owner, _entity).Sequence;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void UpdateBlackboard(Blackboard context)
|
|
||||||
{
|
|
||||||
base.UpdateBlackboard(context);
|
|
||||||
context.GetState<MoveTargetState>().SetValue(_entity);
|
|
||||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Consideration[] Considerations { get; } = {
|
|
||||||
//TODO: Consider ammo's type and what guns we have
|
|
||||||
new TargetAccessibleCon(
|
|
||||||
new BoolCurve()),
|
|
||||||
new FreeHandCon(
|
|
||||||
new BoolCurve()),
|
|
||||||
new DistanceCon(
|
|
||||||
new QuadraticCurve(1.0f, 1.0f, 0.02f, 0.0f)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
using Content.Server.AI.Operators.Sequences;
|
|
||||||
using Content.Server.AI.Utility.Considerations;
|
|
||||||
using Content.Server.AI.Utility.Considerations.Combat.Ranged;
|
|
||||||
using Content.Server.AI.Utility.Considerations.Combat.Ranged.Ballistic;
|
|
||||||
using Content.Server.AI.Utility.Considerations.Containers;
|
|
||||||
using Content.Server.AI.Utility.Considerations.Hands;
|
|
||||||
using Content.Server.AI.Utility.Considerations.Movement;
|
|
||||||
using Content.Server.AI.Utility.Curves;
|
|
||||||
using Content.Server.AI.WorldState;
|
|
||||||
using Content.Server.AI.WorldState.States;
|
|
||||||
using Content.Server.AI.WorldState.States.Combat;
|
|
||||||
using Content.Server.AI.WorldState.States.Movement;
|
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.Actions.Combat.Ranged.Ballistic
|
|
||||||
{
|
|
||||||
public sealed class PickUpBallisticMagWeapon : UtilityAction
|
|
||||||
{
|
|
||||||
private IEntity _entity;
|
|
||||||
|
|
||||||
public PickUpBallisticMagWeapon(IEntity owner, IEntity entity, float weight) : base(owner)
|
|
||||||
{
|
|
||||||
_entity = entity;
|
|
||||||
Bonus = weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void SetupOperators(Blackboard context)
|
|
||||||
{
|
|
||||||
ActionOperators = new GoPickupEntitySequence(Owner, _entity).Sequence;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void UpdateBlackboard(Blackboard context)
|
|
||||||
{
|
|
||||||
base.UpdateBlackboard(context);
|
|
||||||
context.GetState<MoveTargetState>().SetValue(_entity);
|
|
||||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
|
||||||
context.GetState<WeaponEntityState>().SetValue(_entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Consideration[] Considerations { get; } = {
|
|
||||||
new HeldRangedWeaponsCon(
|
|
||||||
new QuadraticCurve(-1.0f, 1.0f, 1.0f, 0.0f)),
|
|
||||||
new TargetAccessibleCon(
|
|
||||||
new BoolCurve()),
|
|
||||||
new FreeHandCon(
|
|
||||||
new BoolCurve()),
|
|
||||||
// For now don't grab empty guns - at least until we can start storing stuff in inventory
|
|
||||||
new BallisticAmmoCon(
|
|
||||||
new BoolCurve()),
|
|
||||||
new DistanceCon(
|
|
||||||
new QuadraticCurve(1.0f, 1.0f, 0.02f, 0.0f)),
|
|
||||||
new RangedWeaponFireRateCon(
|
|
||||||
new QuadraticCurve(1.0f, 0.5f, 0.0f, 0.0f)),
|
|
||||||
// TODO: Ballistic accuracy? Depends how the design transitions
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using Content.Server.AI.Operators;
|
|
||||||
using Content.Server.AI.Operators.Inventory;
|
|
||||||
using Content.Server.AI.Operators.Movement;
|
|
||||||
using Content.Server.AI.Utility.Considerations;
|
|
||||||
using Content.Server.AI.Utility.Considerations.Combat.Ranged.Hitscan;
|
|
||||||
using Content.Server.AI.Utility.Considerations.Movement;
|
|
||||||
using Content.Server.AI.Utility.Curves;
|
|
||||||
using Content.Server.AI.WorldState;
|
|
||||||
using Content.Server.AI.WorldState.States;
|
|
||||||
using Content.Server.AI.WorldState.States.Inventory;
|
|
||||||
using Content.Server.AI.WorldState.States.Movement;
|
|
||||||
using Content.Server.GameObjects.Components.Power.Chargers;
|
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.Actions.Combat.Ranged.Hitscan
|
|
||||||
{
|
|
||||||
public sealed class PutHitscanInCharger : UtilityAction
|
|
||||||
{
|
|
||||||
// Maybe a bad idea to not allow override
|
|
||||||
public override bool CanOverride => false;
|
|
||||||
private readonly IEntity _charger;
|
|
||||||
|
|
||||||
public PutHitscanInCharger(IEntity owner, IEntity charger, float weight) : base(owner)
|
|
||||||
{
|
|
||||||
_charger = charger;
|
|
||||||
Bonus = weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void SetupOperators(Blackboard context)
|
|
||||||
{
|
|
||||||
var weapon = context.GetState<EquippedEntityState>().GetValue();
|
|
||||||
|
|
||||||
if (weapon == null || _charger.GetComponent<WeaponCapacitorChargerComponent>().HeldItem != null)
|
|
||||||
{
|
|
||||||
ActionOperators = new Queue<AiOperator>();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
|
||||||
{
|
|
||||||
new MoveToEntityOperator(Owner, _charger),
|
|
||||||
new InteractWithEntityOperator(Owner, _charger),
|
|
||||||
// Separate task will deal with picking it up
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void UpdateBlackboard(Blackboard context)
|
|
||||||
{
|
|
||||||
base.UpdateBlackboard(context);
|
|
||||||
context.GetState<MoveTargetState>().SetValue(_charger);
|
|
||||||
context.GetState<TargetEntityState>().SetValue(_charger);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Consideration[] Considerations { get; } =
|
|
||||||
{
|
|
||||||
new HitscanWeaponEquippedCon(
|
|
||||||
new BoolCurve()),
|
|
||||||
new HitscanChargerFullCon(
|
|
||||||
new InverseBoolCurve()),
|
|
||||||
new HitscanChargerRateCon(
|
|
||||||
new QuadraticCurve(1.0f, 0.5f, 0.0f, 0.0f)),
|
|
||||||
new DistanceCon(
|
|
||||||
new QuadraticCurve(1.0f, 1.0f, 0.02f, 0.0f)),
|
|
||||||
new HitscanChargeCon(
|
|
||||||
new QuadraticCurve(-1.2f, 2.0f, 1.2f, 0.0f)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using Content.Server.AI.Operators;
|
|
||||||
using Content.Server.AI.Operators.Inventory;
|
|
||||||
using Content.Server.AI.Utility.Considerations;
|
|
||||||
using Content.Server.AI.Utility.Considerations.Combat.Ranged.Hitscan;
|
|
||||||
using Content.Server.AI.Utility.Considerations.Inventory;
|
|
||||||
using Content.Server.AI.Utility.Curves;
|
|
||||||
using Content.Server.AI.WorldState;
|
|
||||||
using Content.Server.AI.WorldState.States;
|
|
||||||
using Content.Server.AI.WorldState.States.Combat;
|
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.Actions.Combat.Ranged.Hitscan
|
|
||||||
{
|
|
||||||
public sealed class DropEmptyHitscan : UtilityAction
|
|
||||||
{
|
|
||||||
private IEntity _entity;
|
|
||||||
|
|
||||||
public DropEmptyHitscan(IEntity owner, IEntity entity, float weight) : base(owner)
|
|
||||||
{
|
|
||||||
_entity = entity;
|
|
||||||
Bonus = weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void SetupOperators(Blackboard context)
|
|
||||||
{
|
|
||||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
|
||||||
{
|
|
||||||
new EquipEntityOperator(Owner, _entity),
|
|
||||||
new DropEntityOperator(Owner, _entity)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void UpdateBlackboard(Blackboard context)
|
|
||||||
{
|
|
||||||
base.UpdateBlackboard(context);
|
|
||||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
|
||||||
context.GetState<WeaponEntityState>().SetValue(_entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Consideration[] Considerations { get; } = {
|
|
||||||
new TargetInOurInventoryCon(
|
|
||||||
new BoolCurve()),
|
|
||||||
// Need to put in hands to drop
|
|
||||||
new CanPutTargetInHandsCon(
|
|
||||||
new BoolCurve()),
|
|
||||||
// If completely empty then drop that sucker
|
|
||||||
new HitscanChargeCon(
|
|
||||||
new InverseBoolCurve()),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using Content.Server.AI.Operators;
|
|
||||||
using Content.Server.AI.Operators.Inventory;
|
|
||||||
using Content.Server.AI.Utility.Considerations;
|
|
||||||
using Content.Server.AI.Utility.Considerations.Combat.Melee;
|
|
||||||
using Content.Server.AI.Utility.Considerations.Combat.Ranged;
|
|
||||||
using Content.Server.AI.Utility.Considerations.Combat.Ranged.Hitscan;
|
|
||||||
using Content.Server.AI.Utility.Considerations.Inventory;
|
|
||||||
using Content.Server.AI.Utility.Curves;
|
|
||||||
using Content.Server.AI.WorldState;
|
|
||||||
using Content.Server.AI.WorldState.States.Combat;
|
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.Actions.Combat.Ranged.Hitscan
|
|
||||||
{
|
|
||||||
public sealed class EquipHitscan : UtilityAction
|
|
||||||
{
|
|
||||||
private IEntity _entity;
|
|
||||||
|
|
||||||
public EquipHitscan(IEntity owner, IEntity entity, float weight) : base(owner)
|
|
||||||
{
|
|
||||||
_entity = entity;
|
|
||||||
Bonus = weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void SetupOperators(Blackboard context)
|
|
||||||
{
|
|
||||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
|
||||||
{
|
|
||||||
new EquipEntityOperator(Owner, _entity)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void UpdateBlackboard(Blackboard context)
|
|
||||||
{
|
|
||||||
base.UpdateBlackboard(context);
|
|
||||||
context.GetState<WeaponEntityState>().SetValue(_entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Consideration[] Considerations { get; } = {
|
|
||||||
new EquippedHitscanCon(
|
|
||||||
new InverseBoolCurve()),
|
|
||||||
new MeleeWeaponEquippedCon(
|
|
||||||
new QuadraticCurve(0.9f, 1.0f, 0.1f, 0.0f)),
|
|
||||||
new CanPutTargetInHandsCon(
|
|
||||||
new BoolCurve()),
|
|
||||||
new HitscanChargeCon(
|
|
||||||
new QuadraticCurve(1.0f, 1.0f, 0.0f, 0.0f)),
|
|
||||||
new RangedWeaponFireRateCon(
|
|
||||||
new QuadraticCurve(1.0f, 0.5f, 0.0f, 0.0f)),
|
|
||||||
new HitscanWeaponDamageCon(
|
|
||||||
new QuadraticCurve(1.0f, 0.25f, 0.0f, 0.0f)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using Content.Server.AI.Operators;
|
|
||||||
using Content.Server.AI.Operators.Combat.Ranged;
|
|
||||||
using Content.Server.AI.Operators.Movement;
|
|
||||||
using Content.Server.AI.Utility.Considerations;
|
|
||||||
using Content.Server.AI.Utility.Considerations.Combat;
|
|
||||||
using Content.Server.AI.Utility.Considerations.Combat.Ranged.Hitscan;
|
|
||||||
using Content.Server.AI.Utility.Considerations.Movement;
|
|
||||||
using Content.Server.AI.Utility.Curves;
|
|
||||||
using Content.Server.AI.Utils;
|
|
||||||
using Content.Server.AI.WorldState;
|
|
||||||
using Content.Server.AI.WorldState.States;
|
|
||||||
using Content.Server.AI.WorldState.States.Combat;
|
|
||||||
using Content.Server.AI.WorldState.States.Inventory;
|
|
||||||
using Content.Server.AI.WorldState.States.Movement;
|
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.Actions.Combat.Ranged.Hitscan
|
|
||||||
{
|
|
||||||
public sealed class HitscanAttackEntity : UtilityAction
|
|
||||||
{
|
|
||||||
private IEntity _entity;
|
|
||||||
private MoveToEntityOperator _moveOperator;
|
|
||||||
|
|
||||||
public HitscanAttackEntity(IEntity owner, IEntity entity, float weight) : base(owner)
|
|
||||||
{
|
|
||||||
_entity = entity;
|
|
||||||
Bonus = weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Shutdown()
|
|
||||||
{
|
|
||||||
base.Shutdown();
|
|
||||||
if (_moveOperator != null)
|
|
||||||
{
|
|
||||||
_moveOperator.MovedATile -= InLos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void SetupOperators(Blackboard context)
|
|
||||||
{
|
|
||||||
_moveOperator = new MoveToEntityOperator(Owner, _entity);
|
|
||||||
_moveOperator.MovedATile += InLos;
|
|
||||||
|
|
||||||
// TODO: Accuracy in blackboard
|
|
||||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
|
||||||
{
|
|
||||||
_moveOperator,
|
|
||||||
new ShootAtEntityOperator(Owner, _entity, 0.7f),
|
|
||||||
});
|
|
||||||
|
|
||||||
InLos();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void UpdateBlackboard(Blackboard context)
|
|
||||||
{
|
|
||||||
base.UpdateBlackboard(context);
|
|
||||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
|
||||||
context.GetState<MoveTargetState>().SetValue(_entity);
|
|
||||||
var equipped = context.GetState<EquippedEntityState>().GetValue();
|
|
||||||
context.GetState<WeaponEntityState>().SetValue(equipped);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Consideration[] Considerations { get; } = {
|
|
||||||
// Check if we have a weapon; easy-out
|
|
||||||
new HitscanWeaponEquippedCon(
|
|
||||||
new BoolCurve()),
|
|
||||||
new HitscanChargeCon(
|
|
||||||
new QuadraticCurve(1.0f, 0.1f, 0.0f, 0.0f)),
|
|
||||||
// Don't attack a dead target
|
|
||||||
new TargetIsDeadCon(
|
|
||||||
new InverseBoolCurve()),
|
|
||||||
// Deprioritise a target in crit
|
|
||||||
new TargetIsCritCon(
|
|
||||||
new QuadraticCurve(-0.8f, 1.0f, 1.0f, 0.0f)),
|
|
||||||
// Somewhat prioritise distance
|
|
||||||
new DistanceCon(
|
|
||||||
new QuadraticCurve(1.0f, 1.0f, 0.07f, 0.0f)),
|
|
||||||
// Prefer weaker targets
|
|
||||||
new TargetHealthCon(
|
|
||||||
new QuadraticCurve(1.0f, 0.4f, 0.0f, -0.02f)),
|
|
||||||
};
|
|
||||||
|
|
||||||
private void InLos()
|
|
||||||
{
|
|
||||||
// This should only be called if the movement operator is the current one;
|
|
||||||
// if that turns out not to be the case we can just add a check here.
|
|
||||||
if (Visibility.InLineOfSight(Owner, _entity))
|
|
||||||
{
|
|
||||||
_moveOperator.HaveArrived();
|
|
||||||
var mover = ActionOperators.Dequeue();
|
|
||||||
mover.Shutdown(Outcome.Success);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using Content.Server.AI.Operators;
|
|
||||||
using Content.Server.AI.Operators.Combat.Ranged;
|
|
||||||
using Content.Server.AI.Operators.Inventory;
|
|
||||||
using Content.Server.AI.Operators.Movement;
|
|
||||||
using Content.Server.AI.Utility.Considerations;
|
|
||||||
using Content.Server.AI.Utility.Considerations.Combat.Ranged;
|
|
||||||
using Content.Server.AI.Utility.Considerations.Combat.Ranged.Hitscan;
|
|
||||||
using Content.Server.AI.Utility.Considerations.Containers;
|
|
||||||
using Content.Server.AI.Utility.Considerations.Hands;
|
|
||||||
using Content.Server.AI.Utility.Considerations.Movement;
|
|
||||||
using Content.Server.AI.Utility.Curves;
|
|
||||||
using Content.Server.AI.WorldState;
|
|
||||||
using Content.Server.AI.WorldState.States;
|
|
||||||
using Content.Server.AI.WorldState.States.Movement;
|
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.Actions.Combat.Ranged.Hitscan
|
|
||||||
{
|
|
||||||
public sealed class PickUpHitscanFromCharger : UtilityAction
|
|
||||||
{
|
|
||||||
private IEntity _entity;
|
|
||||||
private IEntity _charger;
|
|
||||||
|
|
||||||
public PickUpHitscanFromCharger(IEntity owner, IEntity entity, IEntity charger, float weight) : base(owner)
|
|
||||||
{
|
|
||||||
_entity = entity;
|
|
||||||
_charger = charger;
|
|
||||||
Bonus = weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void SetupOperators(Blackboard context)
|
|
||||||
{
|
|
||||||
ActionOperators = new Queue<AiOperator>(new AiOperator[]
|
|
||||||
{
|
|
||||||
new MoveToEntityOperator(Owner, _charger),
|
|
||||||
new WaitForHitscanChargeOperator(_entity),
|
|
||||||
new PickupEntityOperator(Owner, _entity),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void UpdateBlackboard(Blackboard context)
|
|
||||||
{
|
|
||||||
base.UpdateBlackboard(context);
|
|
||||||
context.GetState<MoveTargetState>().SetValue(_entity);
|
|
||||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Consideration[] Considerations { get; } = {
|
|
||||||
new HeldRangedWeaponsCon(
|
|
||||||
new QuadraticCurve(-1.0f, 1.0f, 1.0f, 0.0f)),
|
|
||||||
new TargetAccessibleCon(
|
|
||||||
new BoolCurve()),
|
|
||||||
new FreeHandCon(
|
|
||||||
new BoolCurve()),
|
|
||||||
new DistanceCon(
|
|
||||||
new QuadraticCurve(1.0f, 1.0f, 0.02f, 0.0f)),
|
|
||||||
// TODO: ChargerHasPower
|
|
||||||
new RangedWeaponFireRateCon(
|
|
||||||
new QuadraticCurve(1.0f, 0.5f, 0.0f, 0.0f)),
|
|
||||||
new HitscanWeaponDamageCon(
|
|
||||||
new QuadraticCurve(1.0f, 0.25f, 0.0f, 0.0f)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
using Content.Server.AI.Operators.Sequences;
|
|
||||||
using Content.Server.AI.Utility.Considerations;
|
|
||||||
using Content.Server.AI.Utility.Considerations.Combat.Ranged;
|
|
||||||
using Content.Server.AI.Utility.Considerations.Combat.Ranged.Hitscan;
|
|
||||||
using Content.Server.AI.Utility.Considerations.Containers;
|
|
||||||
using Content.Server.AI.Utility.Considerations.Hands;
|
|
||||||
using Content.Server.AI.Utility.Considerations.Movement;
|
|
||||||
using Content.Server.AI.Utility.Curves;
|
|
||||||
using Content.Server.AI.WorldState;
|
|
||||||
using Content.Server.AI.WorldState.States;
|
|
||||||
using Content.Server.AI.WorldState.States.Combat;
|
|
||||||
using Content.Server.AI.WorldState.States.Movement;
|
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.Actions.Combat.Ranged.Hitscan
|
|
||||||
{
|
|
||||||
public sealed class PickUpHitscanWeapon : UtilityAction
|
|
||||||
{
|
|
||||||
private IEntity _entity;
|
|
||||||
|
|
||||||
public PickUpHitscanWeapon(IEntity owner, IEntity entity, float weight) : base(owner)
|
|
||||||
{
|
|
||||||
_entity = entity;
|
|
||||||
Bonus = weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void SetupOperators(Blackboard context)
|
|
||||||
{
|
|
||||||
ActionOperators = new GoPickupEntitySequence(Owner, _entity).Sequence;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void UpdateBlackboard(Blackboard context)
|
|
||||||
{
|
|
||||||
base.UpdateBlackboard(context);
|
|
||||||
context.GetState<MoveTargetState>().SetValue(_entity);
|
|
||||||
context.GetState<TargetEntityState>().SetValue(_entity);
|
|
||||||
context.GetState<WeaponEntityState>().SetValue(_entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Consideration[] Considerations { get; } = {
|
|
||||||
new HeldRangedWeaponsCon(
|
|
||||||
new QuadraticCurve(-1.0f, 1.0f, 1.0f, 0.0f)),
|
|
||||||
new TargetAccessibleCon(
|
|
||||||
new BoolCurve()),
|
|
||||||
new FreeHandCon(
|
|
||||||
new BoolCurve()),
|
|
||||||
// For now don't grab empty guns - at least until we can start storing stuff in inventory
|
|
||||||
new HitscanChargeCon(
|
|
||||||
new BoolCurve()),
|
|
||||||
new DistanceCon(
|
|
||||||
new QuadraticCurve(1.0f, 1.0f, 0.02f, 0.0f)),
|
|
||||||
// TODO: Weapon charge level
|
|
||||||
new RangedWeaponFireRateCon(
|
|
||||||
new QuadraticCurve(1.0f, 0.5f, 0.0f, 0.0f)),
|
|
||||||
new HitscanWeaponDamageCon(
|
|
||||||
new QuadraticCurve(1.0f, 0.25f, 0.0f, 0.0f)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using Content.Server.AI.Utility.Actions;
|
using Content.Server.AI.Utility.Actions;
|
||||||
using Content.Server.AI.Utility.Actions.Combat.Ranged;
|
|
||||||
using Content.Server.AI.Utility.Actions.Combat.Ranged.Ballistic;
|
|
||||||
using Content.Server.AI.Utility.Actions.Combat.Ranged.Hitscan;
|
|
||||||
using Content.Server.AI.Utility.ExpandableActions.Combat;
|
|
||||||
using Content.Server.AI.Utility.ExpandableActions.Combat.Melee;
|
using Content.Server.AI.Utility.ExpandableActions.Combat.Melee;
|
||||||
using Content.Server.AI.Utility.ExpandableActions.Combat.Ranged;
|
|
||||||
using Content.Server.AI.Utility.ExpandableActions.Combat.Ranged.Ballistic;
|
|
||||||
using Content.Server.AI.Utility.ExpandableActions.Combat.Ranged.Hitscan;
|
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.BehaviorSets
|
namespace Content.Server.AI.Utility.BehaviorSets
|
||||||
@@ -18,17 +10,9 @@ namespace Content.Server.AI.Utility.BehaviorSets
|
|||||||
{
|
{
|
||||||
Actions = new IAiUtility[]
|
Actions = new IAiUtility[]
|
||||||
{
|
{
|
||||||
new PickUpRangedExp(),
|
|
||||||
// TODO: Reload Ballistic
|
// TODO: Reload Ballistic
|
||||||
new DropEmptyBallisticExp(),
|
|
||||||
// TODO: Ideally long-term we should just store the weapons in backpack
|
// TODO: Ideally long-term we should just store the weapons in backpack
|
||||||
new DropEmptyHitscanExp(),
|
|
||||||
new EquipMeleeExp(),
|
new EquipMeleeExp(),
|
||||||
new EquipBallisticExp(),
|
|
||||||
new EquipHitscanExp(),
|
|
||||||
new PickUpHitscanFromChargersExp(),
|
|
||||||
new ChargeEquippedHitscanExp(),
|
|
||||||
new RangedAttackNearbySpeciesExp(),
|
|
||||||
new PickUpMeleeWeaponExp(),
|
new PickUpMeleeWeaponExp(),
|
||||||
new MeleeAttackNearbySpeciesExp(),
|
new MeleeAttackNearbySpeciesExp(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
using Content.Server.AI.Utility.Curves;
|
|
||||||
using Content.Server.AI.WorldState;
|
|
||||||
using Content.Server.AI.WorldState.States.Combat;
|
|
||||||
using Content.Server.GameObjects.Components.Weapon.Ranged.Projectile;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.Considerations.Combat.Ranged.Ballistic
|
|
||||||
{
|
|
||||||
public class BallisticAmmoCon : Consideration
|
|
||||||
{
|
|
||||||
public BallisticAmmoCon(IResponseCurve curve) : base(curve) {}
|
|
||||||
|
|
||||||
public override float GetScore(Blackboard context)
|
|
||||||
{
|
|
||||||
var weapon = context.GetState<WeaponEntityState>().GetValue();
|
|
||||||
|
|
||||||
if (weapon == null || !weapon.TryGetComponent(out BallisticMagazineWeaponComponent ballistic))
|
|
||||||
{
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
var contained = ballistic.MagazineSlot.ContainedEntity;
|
|
||||||
|
|
||||||
if (contained == null)
|
|
||||||
{
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
var mag = contained.GetComponent<BallisticMagazineComponent>();
|
|
||||||
|
|
||||||
if (mag.CountLoaded == 0)
|
|
||||||
{
|
|
||||||
// TODO: Do this better
|
|
||||||
return ballistic.GetChambered(0) != null ? 1.0f : 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (float) mag.CountLoaded / mag.Capacity;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
using Content.Server.AI.Utility.Curves;
|
|
||||||
using Content.Server.AI.WorldState;
|
|
||||||
using Content.Server.AI.WorldState.States.Inventory;
|
|
||||||
using Content.Server.GameObjects.Components.Weapon.Ranged.Projectile;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.Considerations.Combat.Ranged.Ballistic
|
|
||||||
{
|
|
||||||
public class BallisticWeaponEquippedCon : Consideration
|
|
||||||
{
|
|
||||||
public BallisticWeaponEquippedCon(IResponseCurve curve) : base(curve) {}
|
|
||||||
|
|
||||||
public override float GetScore(Blackboard context)
|
|
||||||
{
|
|
||||||
var equipped = context.GetState<EquippedEntityState>().GetValue();
|
|
||||||
|
|
||||||
if (equipped == null)
|
|
||||||
{
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Maybe change this to BallisticMagazineWeapon
|
|
||||||
return equipped.HasComponent<BallisticMagazineWeaponComponent>() ? 1.0f : 0.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
using Content.Server.AI.Utility.Curves;
|
|
||||||
using Content.Server.AI.WorldState;
|
|
||||||
using Content.Server.AI.WorldState.States.Inventory;
|
|
||||||
using Content.Server.GameObjects.Components.Weapon.Ranged.Projectile;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.Considerations.Combat.Ranged.Ballistic
|
|
||||||
{
|
|
||||||
public class EquippedBallisticCon : Consideration
|
|
||||||
{
|
|
||||||
public EquippedBallisticCon(IResponseCurve curve) : base(curve) {}
|
|
||||||
|
|
||||||
public override float GetScore(Blackboard context)
|
|
||||||
{
|
|
||||||
var equipped = context.GetState<EquippedEntityState>().GetValue();
|
|
||||||
|
|
||||||
if (equipped == null || !equipped.HasComponent<BallisticMagazineWeaponComponent>())
|
|
||||||
{
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
using Content.Server.AI.Utility.Curves;
|
|
||||||
using Content.Server.AI.Utils;
|
|
||||||
using Content.Server.AI.WorldState;
|
|
||||||
using Content.Server.AI.WorldState.States;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.Considerations.Combat.Ranged
|
|
||||||
{
|
|
||||||
public class HasTargetLosCon : Consideration
|
|
||||||
{
|
|
||||||
public HasTargetLosCon(IResponseCurve curve) : base(curve) {}
|
|
||||||
|
|
||||||
public override float GetScore(Blackboard context)
|
|
||||||
{
|
|
||||||
var owner = context.GetState<SelfState>().GetValue();
|
|
||||||
var target = context.GetState<TargetEntityState>().GetValue();
|
|
||||||
if (target == null)
|
|
||||||
{
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Visibility.InLineOfSight(owner, target) ? 1.0f : 0.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
using Content.Server.AI.Utility.Curves;
|
|
||||||
using Content.Server.AI.WorldState;
|
|
||||||
using Content.Server.AI.WorldState.States.Inventory;
|
|
||||||
using Content.Server.GameObjects.Components.Weapon.Melee;
|
|
||||||
using Content.Server.GameObjects.Components.Weapon.Ranged;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.Considerations.Combat.Ranged
|
|
||||||
{
|
|
||||||
public sealed class HeldRangedWeaponsCon : Consideration
|
|
||||||
{
|
|
||||||
public HeldRangedWeaponsCon(IResponseCurve curve) : base(curve) {}
|
|
||||||
|
|
||||||
public override float GetScore(Blackboard context)
|
|
||||||
{
|
|
||||||
var count = 0;
|
|
||||||
const int max = 3;
|
|
||||||
|
|
||||||
foreach (var item in context.GetState<InventoryState>().GetValue())
|
|
||||||
{
|
|
||||||
if (item.HasComponent<RangedWeaponComponent>())
|
|
||||||
{
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (float) count / max;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
using Content.Server.AI.Utility.Curves;
|
|
||||||
using Content.Server.AI.WorldState;
|
|
||||||
using Content.Server.AI.WorldState.States.Inventory;
|
|
||||||
using Content.Server.GameObjects.Components.Weapon.Ranged.Hitscan;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.Considerations.Combat.Ranged.Hitscan
|
|
||||||
{
|
|
||||||
public sealed class EquippedHitscanCon : Consideration
|
|
||||||
{
|
|
||||||
public EquippedHitscanCon(IResponseCurve curve) : base(curve) {}
|
|
||||||
|
|
||||||
public override float GetScore(Blackboard context)
|
|
||||||
{
|
|
||||||
var equipped = context.GetState<EquippedEntityState>().GetValue();
|
|
||||||
|
|
||||||
if (equipped == null || !equipped.HasComponent<HitscanWeaponComponent>())
|
|
||||||
{
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
using Content.Server.AI.Utility.Curves;
|
|
||||||
using Content.Server.AI.WorldState;
|
|
||||||
using Content.Server.AI.WorldState.States.Combat;
|
|
||||||
using Content.Server.GameObjects.Components.Weapon.Ranged.Hitscan;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.Considerations.Combat.Ranged.Hitscan
|
|
||||||
{
|
|
||||||
public sealed class HitscanChargeCon : Consideration
|
|
||||||
{
|
|
||||||
public HitscanChargeCon(IResponseCurve curve) : base(curve) {}
|
|
||||||
|
|
||||||
public override float GetScore(Blackboard context)
|
|
||||||
{
|
|
||||||
var weapon = context.GetState<WeaponEntityState>().GetValue();
|
|
||||||
|
|
||||||
if (weapon == null || !weapon.TryGetComponent(out HitscanWeaponComponent hitscanWeaponComponent))
|
|
||||||
{
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
return hitscanWeaponComponent.CapacitorComponent.Charge / hitscanWeaponComponent.CapacitorComponent.Capacity;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
using Content.Server.AI.Utility.Curves;
|
|
||||||
using Content.Server.AI.WorldState;
|
|
||||||
using Content.Server.AI.WorldState.States;
|
|
||||||
using Content.Server.GameObjects.Components.Power.Chargers;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.Considerations.Combat.Ranged.Hitscan
|
|
||||||
{
|
|
||||||
public sealed class HitscanChargerFullCon : Consideration
|
|
||||||
{
|
|
||||||
public HitscanChargerFullCon(IResponseCurve curve) : base(curve) {}
|
|
||||||
|
|
||||||
public override float GetScore(Blackboard context)
|
|
||||||
{
|
|
||||||
var target = context.GetState<TargetEntityState>().GetValue();
|
|
||||||
|
|
||||||
if (target == null ||
|
|
||||||
!target.TryGetComponent(out WeaponCapacitorChargerComponent chargerComponent) ||
|
|
||||||
chargerComponent.HeldItem != null)
|
|
||||||
{
|
|
||||||
return 1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
using Content.Server.AI.Utility.Curves;
|
|
||||||
using Content.Server.AI.WorldState;
|
|
||||||
using Content.Server.AI.WorldState.States;
|
|
||||||
using Content.Server.GameObjects.Components.Power.Chargers;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.Considerations.Combat.Ranged.Hitscan
|
|
||||||
{
|
|
||||||
public sealed class HitscanChargerRateCon : Consideration
|
|
||||||
{
|
|
||||||
public HitscanChargerRateCon(IResponseCurve curve) : base(curve) {}
|
|
||||||
|
|
||||||
public override float GetScore(Blackboard context)
|
|
||||||
{
|
|
||||||
var target = context.GetState<TargetEntityState>().GetValue();
|
|
||||||
if (target == null || !target.TryGetComponent(out WeaponCapacitorChargerComponent weaponCharger))
|
|
||||||
{
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
// AI don't care about efficiency, psfft!
|
|
||||||
return weaponCharger.TransferRatio;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
using Content.Server.AI.Utility.Curves;
|
|
||||||
using Content.Server.AI.WorldState;
|
|
||||||
using Content.Server.AI.WorldState.States.Combat;
|
|
||||||
using Content.Server.GameObjects.Components.Weapon.Ranged.Hitscan;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.Considerations.Combat.Ranged.Hitscan
|
|
||||||
{
|
|
||||||
public sealed class HitscanWeaponDamageCon : Consideration
|
|
||||||
{
|
|
||||||
public HitscanWeaponDamageCon(IResponseCurve curve) : base(curve) {}
|
|
||||||
|
|
||||||
public override float GetScore(Blackboard context)
|
|
||||||
{
|
|
||||||
var weapon = context.GetState<WeaponEntityState>().GetValue();
|
|
||||||
|
|
||||||
if (weapon == null || !weapon.TryGetComponent(out HitscanWeaponComponent hitscanWeaponComponent))
|
|
||||||
{
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Just went with max health
|
|
||||||
return hitscanWeaponComponent.Damage / 300.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
using Content.Server.AI.Utility.Curves;
|
|
||||||
using Content.Server.AI.WorldState;
|
|
||||||
using Content.Server.AI.WorldState.States.Inventory;
|
|
||||||
using Content.Server.GameObjects.Components.Weapon.Ranged.Hitscan;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.Considerations.Combat.Ranged.Hitscan
|
|
||||||
{
|
|
||||||
public sealed class HitscanWeaponEquippedCon : Consideration
|
|
||||||
{
|
|
||||||
public HitscanWeaponEquippedCon(IResponseCurve curve) : base(curve) {}
|
|
||||||
|
|
||||||
public override float GetScore(Blackboard context)
|
|
||||||
{
|
|
||||||
var equipped = context.GetState<EquippedEntityState>().GetValue();
|
|
||||||
|
|
||||||
if (equipped == null)
|
|
||||||
{
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
return equipped.HasComponent<HitscanWeaponComponent>() ? 1.0f : 0.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
using Content.Server.AI.Utility.Curves;
|
|
||||||
using Content.Server.AI.WorldState;
|
|
||||||
using Content.Server.AI.WorldState.States.Inventory;
|
|
||||||
using Content.Server.GameObjects.Components.Weapon.Ranged;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.Considerations.Combat.Ranged
|
|
||||||
{
|
|
||||||
public sealed class RangedWeaponEquippedCon : Consideration
|
|
||||||
{
|
|
||||||
public RangedWeaponEquippedCon(IResponseCurve curve) : base(curve) {}
|
|
||||||
|
|
||||||
public override float GetScore(Blackboard context)
|
|
||||||
{
|
|
||||||
var equipped = context.GetState<EquippedEntityState>().GetValue();
|
|
||||||
|
|
||||||
if (equipped == null || !equipped.HasComponent<RangedWeaponComponent>())
|
|
||||||
{
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
using Content.Server.AI.Utility.Curves;
|
|
||||||
using Content.Server.AI.WorldState;
|
|
||||||
using Content.Server.AI.WorldState.States.Combat;
|
|
||||||
using Content.Server.GameObjects.Components.Weapon.Ranged;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.Considerations.Combat.Ranged
|
|
||||||
{
|
|
||||||
public class RangedWeaponFireRateCon : Consideration
|
|
||||||
{
|
|
||||||
public RangedWeaponFireRateCon(IResponseCurve curve) : base(curve) {}
|
|
||||||
|
|
||||||
public override float GetScore(Blackboard context)
|
|
||||||
{
|
|
||||||
var weapon = context.GetState<WeaponEntityState>().GetValue();
|
|
||||||
|
|
||||||
if (weapon == null || !weapon.TryGetComponent(out RangedWeaponComponent ranged))
|
|
||||||
{
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ranged.FireRate / 100.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using Content.Server.AI.Utility.Actions;
|
|
||||||
using Content.Server.AI.Utility.Actions.Combat.Ranged.Ballistic;
|
|
||||||
using Content.Server.AI.WorldState;
|
|
||||||
using Content.Server.AI.WorldState.States;
|
|
||||||
using Content.Server.AI.WorldState.States.Inventory;
|
|
||||||
using Content.Server.GameObjects.Components.Weapon.Ranged.Projectile;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.ExpandableActions.Combat.Ranged.Ballistic
|
|
||||||
{
|
|
||||||
public sealed class DropEmptyBallisticExp : ExpandableUtilityAction
|
|
||||||
{
|
|
||||||
public override float Bonus => UtilityAction.CombatPrepBonus;
|
|
||||||
|
|
||||||
public override IEnumerable<UtilityAction> GetActions(Blackboard context)
|
|
||||||
{
|
|
||||||
var owner = context.GetState<SelfState>().GetValue();
|
|
||||||
|
|
||||||
foreach (var entity in context.GetState<InventoryState>().GetValue())
|
|
||||||
{
|
|
||||||
if (entity.HasComponent<BallisticMagazineWeaponComponent>())
|
|
||||||
{
|
|
||||||
yield return new DropEmptyBallistic(owner, entity, Bonus);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using Content.Server.AI.Utility.Actions;
|
|
||||||
using Content.Server.AI.Utility.Actions.Combat.Ranged.Ballistic;
|
|
||||||
using Content.Server.AI.WorldState;
|
|
||||||
using Content.Server.AI.WorldState.States;
|
|
||||||
using Content.Server.AI.WorldState.States.Inventory;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.ExpandableActions.Combat.Ranged.Ballistic
|
|
||||||
{
|
|
||||||
public sealed class EquipBallisticExp : ExpandableUtilityAction
|
|
||||||
{
|
|
||||||
public override float Bonus => UtilityAction.CombatPrepBonus;
|
|
||||||
|
|
||||||
public override IEnumerable<UtilityAction> GetActions(Blackboard context)
|
|
||||||
{
|
|
||||||
var owner = context.GetState<SelfState>().GetValue();
|
|
||||||
|
|
||||||
foreach (var entity in context.GetState<InventoryState>().GetValue())
|
|
||||||
{
|
|
||||||
yield return new EquipBallistic(owner, entity, Bonus);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Content.Server.AI.Utility.Actions;
|
|
||||||
using Content.Server.AI.Utility.Actions.Combat.Ranged.Ballistic;
|
|
||||||
using Content.Server.AI.Utils;
|
|
||||||
using Content.Server.AI.WorldState;
|
|
||||||
using Content.Server.AI.WorldState.States;
|
|
||||||
using Content.Server.GameObjects.Components.Movement;
|
|
||||||
using Content.Server.GameObjects.Components.Weapon.Ranged.Projectile;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.ExpandableActions.Combat.Ranged.Ballistic
|
|
||||||
{
|
|
||||||
public sealed class PickUpAmmoExp : ExpandableUtilityAction
|
|
||||||
{
|
|
||||||
public override float Bonus => UtilityAction.CombatPrepBonus;
|
|
||||||
|
|
||||||
public override IEnumerable<UtilityAction> GetActions(Blackboard context)
|
|
||||||
{
|
|
||||||
var owner = context.GetState<SelfState>().GetValue();
|
|
||||||
if (!owner.TryGetComponent(out AiControllerComponent controller))
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var entity in Visibility.GetEntitiesInRange(owner.Transform.GridPosition, typeof(BallisticMagazineComponent),
|
|
||||||
controller.VisionRadius))
|
|
||||||
{
|
|
||||||
yield return new PickUpAmmo(owner, entity, Bonus);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Content.Server.AI.Utility.Actions;
|
|
||||||
using Content.Server.AI.Utility.Actions.Combat.Ranged.Hitscan;
|
|
||||||
using Content.Server.AI.Utils;
|
|
||||||
using Content.Server.AI.WorldState;
|
|
||||||
using Content.Server.AI.WorldState.States;
|
|
||||||
using Content.Server.GameObjects.Components.Movement;
|
|
||||||
using Content.Server.GameObjects.Components.Power.Chargers;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.ExpandableActions.Combat.Ranged.Hitscan
|
|
||||||
{
|
|
||||||
public sealed class ChargeEquippedHitscanExp : ExpandableUtilityAction
|
|
||||||
{
|
|
||||||
public override float Bonus => UtilityAction.CombatPrepBonus;
|
|
||||||
|
|
||||||
public override IEnumerable<UtilityAction> GetActions(Blackboard context)
|
|
||||||
{
|
|
||||||
var owner = context.GetState<SelfState>().GetValue();
|
|
||||||
if (!owner.TryGetComponent(out AiControllerComponent controller))
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var entity in Visibility.GetEntitiesInRange(owner.Transform.GridPosition, typeof(WeaponCapacitorChargerComponent),
|
|
||||||
controller.VisionRadius))
|
|
||||||
{
|
|
||||||
yield return new PutHitscanInCharger(owner, entity, Bonus);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using Content.Server.AI.Utility.Actions;
|
|
||||||
using Content.Server.AI.Utility.Actions.Combat.Ranged.Hitscan;
|
|
||||||
using Content.Server.AI.WorldState;
|
|
||||||
using Content.Server.AI.WorldState.States;
|
|
||||||
using Content.Server.AI.WorldState.States.Inventory;
|
|
||||||
using Content.Server.GameObjects.Components.Weapon.Ranged.Hitscan;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.ExpandableActions.Combat.Ranged.Hitscan
|
|
||||||
{
|
|
||||||
public class DropEmptyHitscanExp : ExpandableUtilityAction
|
|
||||||
{
|
|
||||||
public override float Bonus => UtilityAction.CombatPrepBonus;
|
|
||||||
|
|
||||||
public override IEnumerable<UtilityAction> GetActions(Blackboard context)
|
|
||||||
{
|
|
||||||
var owner = context.GetState<SelfState>().GetValue();
|
|
||||||
|
|
||||||
foreach (var entity in context.GetState<InventoryState>().GetValue())
|
|
||||||
{
|
|
||||||
if (entity.HasComponent<HitscanWeaponComponent>())
|
|
||||||
{
|
|
||||||
yield return new DropEmptyHitscan(owner, entity, Bonus);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using Content.Server.AI.Utility.Actions;
|
|
||||||
using Content.Server.AI.Utility.Actions.Combat.Ranged.Hitscan;
|
|
||||||
using Content.Server.AI.WorldState;
|
|
||||||
using Content.Server.AI.WorldState.States;
|
|
||||||
using Content.Server.AI.WorldState.States.Inventory;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.ExpandableActions.Combat.Ranged.Hitscan
|
|
||||||
{
|
|
||||||
public sealed class EquipHitscanExp : ExpandableUtilityAction
|
|
||||||
{
|
|
||||||
public override float Bonus => UtilityAction.CombatPrepBonus;
|
|
||||||
|
|
||||||
public override IEnumerable<UtilityAction> GetActions(Blackboard context)
|
|
||||||
{
|
|
||||||
var owner = context.GetState<SelfState>().GetValue();
|
|
||||||
|
|
||||||
foreach (var entity in context.GetState<InventoryState>().GetValue())
|
|
||||||
{
|
|
||||||
yield return new EquipHitscan(owner, entity, Bonus);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Content.Server.AI.Utility.Actions;
|
|
||||||
using Content.Server.AI.Utility.Actions.Combat.Ranged.Hitscan;
|
|
||||||
using Content.Server.AI.Utils;
|
|
||||||
using Content.Server.AI.WorldState;
|
|
||||||
using Content.Server.AI.WorldState.States;
|
|
||||||
using Content.Server.GameObjects.Components.Movement;
|
|
||||||
using Content.Server.GameObjects.Components.Power.Chargers;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.ExpandableActions.Combat.Ranged.Hitscan
|
|
||||||
{
|
|
||||||
public sealed class PickUpHitscanFromChargersExp : ExpandableUtilityAction
|
|
||||||
{
|
|
||||||
public override float Bonus => UtilityAction.CombatPrepBonus;
|
|
||||||
|
|
||||||
public override IEnumerable<UtilityAction> GetActions(Blackboard context)
|
|
||||||
{
|
|
||||||
var owner = context.GetState<SelfState>().GetValue();
|
|
||||||
if (!owner.TryGetComponent(out AiControllerComponent controller))
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var entity in Visibility.GetEntitiesInRange(owner.Transform.GridPosition, typeof(WeaponCapacitorChargerComponent),
|
|
||||||
controller.VisionRadius))
|
|
||||||
{
|
|
||||||
var contained = entity.GetComponent<WeaponCapacitorChargerComponent>().HeldItem;
|
|
||||||
|
|
||||||
if (contained != null)
|
|
||||||
{
|
|
||||||
yield return new PickUpHitscanFromCharger(owner, entity, contained, Bonus);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using Content.Server.AI.Utility.Actions;
|
|
||||||
using Content.Server.AI.Utility.Actions.Combat.Ranged.Ballistic;
|
|
||||||
using Content.Server.AI.Utility.Actions.Combat.Ranged.Hitscan;
|
|
||||||
using Content.Server.AI.WorldState;
|
|
||||||
using Content.Server.AI.WorldState.States;
|
|
||||||
using Content.Server.AI.WorldState.States.Combat.Nearby;
|
|
||||||
using Content.Server.GameObjects.Components.Weapon.Ranged.Hitscan;
|
|
||||||
using Content.Server.GameObjects.Components.Weapon.Ranged.Projectile;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.ExpandableActions.Combat.Ranged
|
|
||||||
{
|
|
||||||
public sealed class PickUpRangedExp : ExpandableUtilityAction
|
|
||||||
{
|
|
||||||
public override float Bonus => UtilityAction.CombatPrepBonus;
|
|
||||||
|
|
||||||
public override IEnumerable<UtilityAction> GetActions(Blackboard context)
|
|
||||||
{
|
|
||||||
var owner = context.GetState<SelfState>().GetValue();
|
|
||||||
|
|
||||||
foreach (var entity in context.GetState<NearbyRangedWeapons>().GetValue())
|
|
||||||
{
|
|
||||||
if (entity.HasComponent<HitscanWeaponComponent>())
|
|
||||||
{
|
|
||||||
yield return new PickUpHitscanWeapon(owner, entity, Bonus);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entity.HasComponent<BallisticMagazineWeaponComponent>())
|
|
||||||
{
|
|
||||||
yield return new PickUpBallisticMagWeapon(owner, entity, Bonus);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using Content.Server.AI.Utility.Actions;
|
|
||||||
using Content.Server.AI.Utility.Actions.Combat.Ranged.Ballistic;
|
|
||||||
using Content.Server.AI.Utility.Actions.Combat.Ranged.Hitscan;
|
|
||||||
using Content.Server.AI.WorldState;
|
|
||||||
using Content.Server.AI.WorldState.States;
|
|
||||||
using Content.Server.AI.WorldState.States.Mobs;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.ExpandableActions.Combat.Ranged
|
|
||||||
{
|
|
||||||
public sealed class RangedAttackNearbySpeciesExp : ExpandableUtilityAction
|
|
||||||
{
|
|
||||||
public override float Bonus => UtilityAction.CombatBonus;
|
|
||||||
|
|
||||||
public override IEnumerable<UtilityAction> GetActions(Blackboard context)
|
|
||||||
{
|
|
||||||
var owner = context.GetState<SelfState>().GetValue();
|
|
||||||
|
|
||||||
foreach (var entity in context.GetState<NearbySpeciesState>().GetValue())
|
|
||||||
{
|
|
||||||
yield return new HitscanAttackEntity(owner, entity, Bonus);
|
|
||||||
yield return new BallisticAttackEntity(owner, entity, Bonus);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using Content.Server.AI.Utils;
|
|
||||||
using Content.Server.GameObjects.Components.Movement;
|
|
||||||
using Content.Server.GameObjects.Components.Power.Chargers;
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.WorldState.States.Combat.Nearby
|
|
||||||
{
|
|
||||||
[UsedImplicitly]
|
|
||||||
public sealed class NearbyLaserChargersState : StateData<List<IEntity>>
|
|
||||||
{
|
|
||||||
public override string Name => "NearbyLaserChargers";
|
|
||||||
|
|
||||||
public override List<IEntity> GetValue()
|
|
||||||
{
|
|
||||||
var nearby = new List<IEntity>();
|
|
||||||
|
|
||||||
if (!Owner.TryGetComponent(out AiControllerComponent controller))
|
|
||||||
{
|
|
||||||
return nearby;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var result in Visibility
|
|
||||||
.GetNearestEntities(Owner.Transform.GridPosition, typeof(WeaponCapacitorChargerComponent), controller.VisionRadius))
|
|
||||||
{
|
|
||||||
nearby.Add(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
return nearby;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using Content.Server.AI.Utils;
|
|
||||||
using Content.Server.GameObjects.Components.Movement;
|
|
||||||
using Content.Server.GameObjects.Components.Weapon.Ranged.Hitscan;
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.WorldState.States.Combat.Nearby
|
|
||||||
{
|
|
||||||
[UsedImplicitly]
|
|
||||||
public sealed class NearbyLaserWeapons : StateData<List<IEntity>>
|
|
||||||
{
|
|
||||||
public override string Name => "NearbyLaserWeapons";
|
|
||||||
|
|
||||||
public override List<IEntity> GetValue()
|
|
||||||
{
|
|
||||||
var result = new List<IEntity>();
|
|
||||||
|
|
||||||
if (!Owner.TryGetComponent(out AiControllerComponent controller))
|
|
||||||
{
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var entity in Visibility
|
|
||||||
.GetNearestEntities(Owner.Transform.GridPosition, typeof(HitscanWeaponComponent), controller.VisionRadius))
|
|
||||||
{
|
|
||||||
result.Add(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using Content.Server.AI.Utils;
|
|
||||||
using Content.Server.GameObjects.Components.Movement;
|
|
||||||
using Content.Server.GameObjects.Components.Weapon.Ranged;
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.WorldState.States.Combat.Nearby
|
|
||||||
{
|
|
||||||
[UsedImplicitly]
|
|
||||||
public sealed class NearbyRangedWeapons : CachedStateData<List<IEntity>>
|
|
||||||
{
|
|
||||||
public override string Name => "NearbyRangedWeapons";
|
|
||||||
|
|
||||||
protected override List<IEntity> GetTrueValue()
|
|
||||||
{
|
|
||||||
var result = new List<IEntity>();
|
|
||||||
|
|
||||||
if (!Owner.TryGetComponent(out AiControllerComponent controller))
|
|
||||||
{
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var entity in Visibility
|
|
||||||
.GetNearestEntities(Owner.Transform.GridPosition, typeof(RangedWeaponComponent), controller.VisionRadius))
|
|
||||||
{
|
|
||||||
result.Add(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
using JetBrains.Annotations;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.WorldState.States.Combat.Ranged
|
|
||||||
{
|
|
||||||
[UsedImplicitly]
|
|
||||||
public sealed class Accuracy : StateData<float>
|
|
||||||
{
|
|
||||||
public override string Name => "Accuracy";
|
|
||||||
|
|
||||||
public override float GetValue()
|
|
||||||
{
|
|
||||||
// TODO: Maybe just make it a SetValue (maybe make a third type besides sensor / daemon called settablestate)
|
|
||||||
return 1.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
using JetBrains.Annotations;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.WorldState.States.Combat.Ranged
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// How long to wait between bursts
|
|
||||||
/// </summary>
|
|
||||||
[UsedImplicitly]
|
|
||||||
public sealed class BurstCooldown : PlanningStateData<float>
|
|
||||||
{
|
|
||||||
public override string Name => "BurstCooldown";
|
|
||||||
public override void Reset()
|
|
||||||
{
|
|
||||||
Value = 0.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
using Content.Server.GameObjects;
|
|
||||||
using Content.Server.GameObjects.Components.Weapon.Ranged.Hitscan;
|
|
||||||
using Content.Server.GameObjects.Components.Weapon.Ranged.Projectile;
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.WorldState.States.Combat.Ranged
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the discrete ammo count
|
|
||||||
/// </summary>
|
|
||||||
[UsedImplicitly]
|
|
||||||
public sealed class EquippedRangedWeaponAmmo : StateData<int?>
|
|
||||||
{
|
|
||||||
public override string Name => "EquippedRangedWeaponAmmo";
|
|
||||||
|
|
||||||
public override int? GetValue()
|
|
||||||
{
|
|
||||||
if (!Owner.TryGetComponent(out HandsComponent handsComponent))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var equippedItem = handsComponent.GetActiveHand?.Owner;
|
|
||||||
if (equippedItem == null) return null;
|
|
||||||
|
|
||||||
if (equippedItem.TryGetComponent(out HitscanWeaponComponent hitscanWeaponComponent))
|
|
||||||
{
|
|
||||||
return (int) hitscanWeaponComponent.CapacitorComponent.Charge / hitscanWeaponComponent.BaseFireCost;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (equippedItem.TryGetComponent(out BallisticMagazineWeaponComponent ballisticComponent))
|
|
||||||
{
|
|
||||||
return ballisticComponent.MagazineSlot.ContainedEntities.Count;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
using JetBrains.Annotations;
|
|
||||||
|
|
||||||
namespace Content.Server.AI.WorldState.States.Combat.Ranged
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// How many shots to take before cooling down
|
|
||||||
/// </summary>
|
|
||||||
[UsedImplicitly]
|
|
||||||
public sealed class MaxBurstCount : PlanningStateData<int>
|
|
||||||
{
|
|
||||||
public override string Name => "BurstCount";
|
|
||||||
public override void Reset()
|
|
||||||
{
|
|
||||||
Value = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
using Content.Server.GameObjects.Components.Weapon;
|
||||||
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
|
using Robust.Server.GameObjects.EntitySystems;
|
||||||
|
using Robust.Shared.Containers;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects.Systems;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Server.GameObjects.Components.Explosion
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// When triggered will flash in an area around the object and destroy itself
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public class FlashExplosiveComponent : Component, ITimerTrigger, IDestroyAct
|
||||||
|
{
|
||||||
|
public override string Name => "FlashExplosive";
|
||||||
|
|
||||||
|
private float _range;
|
||||||
|
private double _duration;
|
||||||
|
private string _sound;
|
||||||
|
private bool _deleteOnFlash;
|
||||||
|
|
||||||
|
public override void ExposeData(ObjectSerializer serializer)
|
||||||
|
{
|
||||||
|
base.ExposeData(serializer);
|
||||||
|
|
||||||
|
serializer.DataField(ref _range, "range", 7.0f);
|
||||||
|
serializer.DataField(ref _duration, "duration", 8.0);
|
||||||
|
serializer.DataField(ref _sound, "sound", "/Audio/effects/flash_bang.ogg");
|
||||||
|
serializer.DataField(ref _deleteOnFlash, "deleteOnFlash", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Explode()
|
||||||
|
{
|
||||||
|
// If we're in a locker or whatever then can't flash anything
|
||||||
|
ContainerHelpers.TryGetContainer(Owner, out var container);
|
||||||
|
if (container == null || !container.Owner.HasComponent<EntityStorageComponent>())
|
||||||
|
{
|
||||||
|
ServerFlashableComponent.FlashAreaHelper(Owner, _range, _duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_sound != null)
|
||||||
|
{
|
||||||
|
EntitySystem.Get<AudioSystem>().PlayAtCoords(_sound, Owner.Transform.GridPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_deleteOnFlash && !Owner.Deleted)
|
||||||
|
{
|
||||||
|
Owner.Delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ITimerTrigger.Trigger(TimerTriggerEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
return Explode();
|
||||||
|
}
|
||||||
|
|
||||||
|
void IDestroyAct.OnDestroy(DestructionEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
Explode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using Content.Server.GameObjects.Components.Weapon.Ranged.Barrels;
|
||||||
using Content.Shared.GameObjects.Components.Power;
|
using Content.Shared.GameObjects.Components.Power;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Server.GameObjects.Components.Container;
|
using Robust.Server.GameObjects.Components.Container;
|
||||||
@@ -13,7 +14,7 @@ namespace Content.Server.GameObjects.Components.Power.Chargers
|
|||||||
public abstract class BaseCharger : Component
|
public abstract class BaseCharger : Component
|
||||||
{
|
{
|
||||||
|
|
||||||
public IEntity HeldItem { get; protected set; }
|
protected IEntity _heldItem;
|
||||||
protected ContainerSlot _container;
|
protected ContainerSlot _container;
|
||||||
protected PowerDeviceComponent _powerDevice;
|
protected PowerDeviceComponent _powerDevice;
|
||||||
public CellChargerStatus Status => _status;
|
public CellChargerStatus Status => _status;
|
||||||
@@ -58,37 +59,28 @@ namespace Content.Server.GameObjects.Components.Power.Chargers
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This will remove the item directly into the user's hand rather than the floor
|
/// This will remove the item directly into the user's hand / floor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="user"></param>
|
/// <param name="user"></param>
|
||||||
public void RemoveItemToHand(IEntity user)
|
public void RemoveItem(IEntity user)
|
||||||
{
|
{
|
||||||
var heldItem = _container.ContainedEntity;
|
var heldItem = _container.ContainedEntity;
|
||||||
if (heldItem == null)
|
if (heldItem == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
RemoveItem();
|
|
||||||
|
|
||||||
if (user.TryGetComponent(out HandsComponent handsComponent) &&
|
_container.Remove(heldItem);
|
||||||
heldItem.TryGetComponent(out ItemComponent itemComponent))
|
if (user.TryGetComponent(out HandsComponent handsComponent))
|
||||||
{
|
{
|
||||||
handsComponent.PutInHand(itemComponent);
|
handsComponent.PutInHandOrDrop(heldItem.GetComponent<ItemComponent>());
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Will put the charger's item on the floor if available
|
|
||||||
/// </summary>
|
|
||||||
public void RemoveItem()
|
|
||||||
{
|
|
||||||
if (_container.ContainedEntity == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_container.Remove(HeldItem);
|
if (heldItem.TryGetComponent(out ServerBatteryBarrelComponent batteryBarrelComponent))
|
||||||
HeldItem = null;
|
{
|
||||||
|
batteryBarrelComponent.UpdateAppearance();
|
||||||
|
}
|
||||||
|
|
||||||
UpdateStatus();
|
UpdateStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,8 +127,6 @@ namespace Content.Server.GameObjects.Components.Power.Chargers
|
|||||||
}
|
}
|
||||||
|
|
||||||
_appearanceComponent?.SetData(CellVisual.Occupied, _container.ContainedEntity != null);
|
_appearanceComponent?.SetData(CellVisual.Occupied, _container.ContainedEntity != null);
|
||||||
|
|
||||||
_status = status;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnUpdate(float frameTime)
|
public void OnUpdate(float frameTime)
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ namespace Content.Server.GameObjects.Components.Power.Chargers
|
|||||||
|
|
||||||
void IActivate.Activate(ActivateEventArgs eventArgs)
|
void IActivate.Activate(ActivateEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
RemoveItemToHand(eventArgs.User);
|
RemoveItem(eventArgs.User);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Verb]
|
[Verb]
|
||||||
@@ -111,7 +111,7 @@ namespace Content.Server.GameObjects.Components.Power.Chargers
|
|||||||
|
|
||||||
protected override void Activate(IEntity user, PowerCellChargerComponent component)
|
protected override void Activate(IEntity user, PowerCellChargerComponent component)
|
||||||
{
|
{
|
||||||
component.RemoveItem();
|
component.RemoveItem(user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,9 +122,8 @@ namespace Content.Server.GameObjects.Components.Power.Chargers
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
HeldItem = entity;
|
if (!_container.Insert(entity))
|
||||||
if (!_container.Insert(HeldItem))
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -157,7 +156,7 @@ namespace Content.Server.GameObjects.Components.Power.Chargers
|
|||||||
{
|
{
|
||||||
// Two numbers: One for how much power actually goes into the device (chargeAmount) and
|
// Two numbers: One for how much power actually goes into the device (chargeAmount) and
|
||||||
// chargeLoss which is how much is drawn from the powernet
|
// chargeLoss which is how much is drawn from the powernet
|
||||||
_container.ContainedEntity.TryGetComponent(out PowerCellComponent cellComponent);
|
var cellComponent = _container.ContainedEntity.GetComponent<PowerCellComponent>();
|
||||||
var chargeLoss = cellComponent.RequestCharge(frameTime) * _transferRatio;
|
var chargeLoss = cellComponent.RequestCharge(frameTime) * _transferRatio;
|
||||||
_powerDevice.Load = chargeLoss;
|
_powerDevice.Load = chargeLoss;
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,13 @@
|
|||||||
using System;
|
using System;
|
||||||
using Content.Server.GameObjects.Components.Weapon.Ranged.Hitscan;
|
using Content.Server.GameObjects.Components.Weapon.Ranged.Barrels;
|
||||||
using Content.Server.GameObjects.EntitySystems;
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
using Content.Server.Utility;
|
|
||||||
using Content.Shared.GameObjects;
|
using Content.Shared.GameObjects;
|
||||||
using Content.Shared.GameObjects.Components.Power;
|
using Content.Shared.GameObjects.Components.Power;
|
||||||
using Content.Shared.Interfaces;
|
using Content.Shared.Interfaces;
|
||||||
using Robust.Server.GameObjects;
|
|
||||||
using Robust.Server.GameObjects.Components.Container;
|
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Localization;
|
using Robust.Shared.Localization;
|
||||||
using Robust.Shared.Serialization;
|
|
||||||
using Robust.Shared.ViewVariables;
|
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.Components.Power.Chargers
|
namespace Content.Server.GameObjects.Components.Power.Chargers
|
||||||
{
|
{
|
||||||
@@ -26,8 +21,8 @@ namespace Content.Server.GameObjects.Components.Power.Chargers
|
|||||||
{
|
{
|
||||||
public override string Name => "WeaponCapacitorCharger";
|
public override string Name => "WeaponCapacitorCharger";
|
||||||
public override double CellChargePercent => _container.ContainedEntity != null ?
|
public override double CellChargePercent => _container.ContainedEntity != null ?
|
||||||
_container.ContainedEntity.GetComponent<HitscanWeaponCapacitorComponent>().Charge /
|
_container.ContainedEntity.GetComponent<ServerBatteryBarrelComponent>().PowerCell.Charge /
|
||||||
_container.ContainedEntity.GetComponent<HitscanWeaponCapacitorComponent>().Capacity * 100 : 0.0f;
|
_container.ContainedEntity.GetComponent<ServerBatteryBarrelComponent>().PowerCell.Capacity * 100 : 0.0f;
|
||||||
|
|
||||||
bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
@@ -43,7 +38,7 @@ namespace Content.Server.GameObjects.Components.Power.Chargers
|
|||||||
|
|
||||||
void IActivate.Activate(ActivateEventArgs eventArgs)
|
void IActivate.Activate(ActivateEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
RemoveItemToHand(eventArgs.User);
|
RemoveItem(eventArgs.User);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Verb]
|
[Verb]
|
||||||
@@ -106,21 +101,19 @@ namespace Content.Server.GameObjects.Components.Power.Chargers
|
|||||||
|
|
||||||
protected override void Activate(IEntity user, WeaponCapacitorChargerComponent component)
|
protected override void Activate(IEntity user, WeaponCapacitorChargerComponent component)
|
||||||
{
|
{
|
||||||
component.RemoveItem();
|
component.RemoveItem(user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryInsertItem(IEntity entity)
|
public bool TryInsertItem(IEntity entity)
|
||||||
{
|
{
|
||||||
if (!entity.HasComponent<HitscanWeaponCapacitorComponent>() ||
|
if (!entity.HasComponent<ServerBatteryBarrelComponent>() ||
|
||||||
_container.ContainedEntity != null)
|
_container.ContainedEntity != null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
HeldItem = entity;
|
if (!_container.Insert(entity))
|
||||||
|
|
||||||
if (!_container.Insert(HeldItem))
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -140,8 +133,8 @@ namespace Content.Server.GameObjects.Components.Power.Chargers
|
|||||||
return CellChargerStatus.Empty;
|
return CellChargerStatus.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_container.ContainedEntity.TryGetComponent(out HitscanWeaponCapacitorComponent component) &&
|
if (_container.ContainedEntity.TryGetComponent(out ServerBatteryBarrelComponent component) &&
|
||||||
Math.Abs(component.Capacity - component.Charge) < 0.01)
|
Math.Abs(component.PowerCell.Capacity - component.PowerCell.Charge) < 0.01)
|
||||||
{
|
{
|
||||||
return CellChargerStatus.Charged;
|
return CellChargerStatus.Charged;
|
||||||
}
|
}
|
||||||
@@ -153,8 +146,8 @@ namespace Content.Server.GameObjects.Components.Power.Chargers
|
|||||||
{
|
{
|
||||||
// Two numbers: One for how much power actually goes into the device (chargeAmount) and
|
// Two numbers: One for how much power actually goes into the device (chargeAmount) and
|
||||||
// chargeLoss which is how much is drawn from the powernet
|
// chargeLoss which is how much is drawn from the powernet
|
||||||
_container.ContainedEntity.TryGetComponent(out HitscanWeaponCapacitorComponent weaponCapacitorComponent);
|
var powerCell = _container.ContainedEntity.GetComponent<ServerBatteryBarrelComponent>().PowerCell;
|
||||||
var chargeLoss = weaponCapacitorComponent.RequestCharge(frameTime) * _transferRatio;
|
var chargeLoss = powerCell.RequestCharge(frameTime) * _transferRatio;
|
||||||
_powerDevice.Load = chargeLoss;
|
_powerDevice.Load = chargeLoss;
|
||||||
|
|
||||||
if (!_powerDevice.Powered)
|
if (!_powerDevice.Powered)
|
||||||
@@ -165,14 +158,13 @@ namespace Content.Server.GameObjects.Components.Power.Chargers
|
|||||||
|
|
||||||
var chargeAmount = chargeLoss * _transferEfficiency;
|
var chargeAmount = chargeLoss * _transferEfficiency;
|
||||||
|
|
||||||
weaponCapacitorComponent.AddCharge(chargeAmount);
|
powerCell.AddCharge(chargeAmount);
|
||||||
// Just so the sprite won't be set to 99.99999% visibility
|
// Just so the sprite won't be set to 99.99999% visibility
|
||||||
if (weaponCapacitorComponent.Capacity - weaponCapacitorComponent.Charge < 0.01)
|
if (powerCell.Capacity - powerCell.Charge < 0.01)
|
||||||
{
|
{
|
||||||
weaponCapacitorComponent.Charge = weaponCapacitorComponent.Capacity;
|
powerCell.Charge = powerCell.Capacity;
|
||||||
}
|
}
|
||||||
UpdateStatus();
|
UpdateStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
using System;
|
||||||
|
using Content.Server.GameObjects.Components.Explosion;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects.Components;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Log;
|
||||||
|
|
||||||
|
namespace Content.Server.GameObjects.Components.Projectiles
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
public class ExplosiveProjectileComponent : Component, ICollideBehavior
|
||||||
|
{
|
||||||
|
public override string Name => "ExplosiveProjectile";
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
if (!Owner.HasComponent<ExplosiveComponent>())
|
||||||
|
{
|
||||||
|
Logger.Error("ExplosiveProjectiles need an ExplosiveComponent");
|
||||||
|
throw new InvalidOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ICollideBehavior.CollideWith(IEntity entity)
|
||||||
|
{
|
||||||
|
var explosiveComponent = Owner.GetComponent<ExplosiveComponent>();
|
||||||
|
explosiveComponent.Explosion();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Projectile should handle the deleting
|
||||||
|
void ICollideBehavior.PostCollide(int collisionCount)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Server.GameObjects.Components.Weapon;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects.Components;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Physics;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Server.GameObjects.Components.Projectiles
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Upon colliding with an object this will flash in an area around it
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public class FlashProjectileComponent : Component, ICollideBehavior
|
||||||
|
{
|
||||||
|
public override string Name => "FlashProjectile";
|
||||||
|
|
||||||
|
private double _range;
|
||||||
|
private double _duration;
|
||||||
|
|
||||||
|
private bool _flashed;
|
||||||
|
|
||||||
|
public override void ExposeData(ObjectSerializer serializer)
|
||||||
|
{
|
||||||
|
base.ExposeData(serializer);
|
||||||
|
serializer.DataField(ref _range, "range", 1.0);
|
||||||
|
serializer.DataField(ref _duration, "duration", 8.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
// Shouldn't be using this without a ProjectileComponent because it will just immediately collide with thrower
|
||||||
|
if (!Owner.HasComponent<ProjectileComponent>())
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ICollideBehavior.CollideWith(IEntity entity)
|
||||||
|
{
|
||||||
|
if (_flashed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ServerFlashableComponent.FlashAreaHelper(Owner, _range, _duration);
|
||||||
|
_flashed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Projectile should handle the deleting
|
||||||
|
void ICollideBehavior.PostCollide(int collisionCount)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
using System;
|
||||||
|
using Content.Shared.GameObjects;
|
||||||
|
using Content.Shared.Physics;
|
||||||
|
using Robust.Server.GameObjects.EntitySystems;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects.EntitySystemMessages;
|
||||||
|
using Robust.Shared.GameObjects.Systems;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Serialization;
|
||||||
|
using Robust.Shared.Interfaces.Timing;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using Robust.Shared.Physics;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
using Timer = Robust.Shared.Timers.Timer;
|
||||||
|
|
||||||
|
namespace Content.Server.GameObjects.Components.Projectiles
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Lasers etc.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public class HitscanComponent : Component
|
||||||
|
{
|
||||||
|
public override string Name => "Hitscan";
|
||||||
|
public CollisionGroup CollisionMask => (CollisionGroup) _collisionMask;
|
||||||
|
private int _collisionMask;
|
||||||
|
|
||||||
|
public float Damage
|
||||||
|
{
|
||||||
|
get => _damage;
|
||||||
|
set => _damage = value;
|
||||||
|
}
|
||||||
|
private float _damage;
|
||||||
|
public DamageType DamageType => _damageType;
|
||||||
|
private DamageType _damageType;
|
||||||
|
public float MaxLength => 20.0f;
|
||||||
|
|
||||||
|
private TimeSpan _startTime;
|
||||||
|
private TimeSpan _deathTime;
|
||||||
|
|
||||||
|
public float ColorModifier { get; set; } = 1.0f;
|
||||||
|
private string _spriteName;
|
||||||
|
private string _muzzleFlash;
|
||||||
|
private string _impactFlash;
|
||||||
|
private string _soundHitWall;
|
||||||
|
|
||||||
|
public override void ExposeData(ObjectSerializer serializer)
|
||||||
|
{
|
||||||
|
base.ExposeData(serializer);
|
||||||
|
serializer.DataField(ref _collisionMask, "layers", (int) CollisionGroup.Opaque, WithFormat.Flags<CollisionLayer>());
|
||||||
|
serializer.DataField(ref _damage, "damage", 10.0f);
|
||||||
|
serializer.DataField(ref _damageType, "damageType", DamageType.Heat);
|
||||||
|
serializer.DataField(ref _spriteName, "spriteName", "Objects/Guns/Projectiles/laser.png");
|
||||||
|
serializer.DataField(ref _muzzleFlash, "muzzleFlash", null);
|
||||||
|
serializer.DataField(ref _impactFlash, "impactFlash", null);
|
||||||
|
serializer.DataField(ref _soundHitWall, "soundHitWall", "/Audio/Guns/Hits/laser_sear_wall.ogg");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FireEffects(IEntity user, float distance, Angle angle, IEntity hitEntity = null)
|
||||||
|
{
|
||||||
|
var effectSystem = EntitySystem.Get<EffectSystem>();
|
||||||
|
_startTime = IoCManager.Resolve<IGameTiming>().CurTime;
|
||||||
|
_deathTime = _startTime + TimeSpan.FromSeconds(1);
|
||||||
|
|
||||||
|
var afterEffect = AfterEffects(user.Transform.GridPosition, angle, distance, 1.0f);
|
||||||
|
if (afterEffect != null)
|
||||||
|
{
|
||||||
|
effectSystem.CreateParticle(afterEffect);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we're too close we'll stop the impact and muzzle / impact sprites from clipping
|
||||||
|
if (distance > 1.0f)
|
||||||
|
{
|
||||||
|
var impactEffect = ImpactFlash(distance, angle);
|
||||||
|
if (impactEffect != null)
|
||||||
|
{
|
||||||
|
effectSystem.CreateParticle(impactEffect);
|
||||||
|
}
|
||||||
|
|
||||||
|
var muzzleEffect = MuzzleFlash(user.Transform.GridPosition, angle);
|
||||||
|
if (muzzleEffect != null)
|
||||||
|
{
|
||||||
|
effectSystem.CreateParticle(muzzleEffect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hitEntity != null && _soundHitWall != null)
|
||||||
|
{
|
||||||
|
// TODO: No wall component so ?
|
||||||
|
var offset = angle.ToVec().Normalized / 2;
|
||||||
|
EntitySystem.Get<AudioSystem>().PlayAtCoords(_soundHitWall, user.Transform.GridPosition.Translated(offset));
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer.Spawn((int) _deathTime.TotalMilliseconds, () =>
|
||||||
|
{
|
||||||
|
if (!Owner.Deleted)
|
||||||
|
{
|
||||||
|
Owner.Delete();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private EffectSystemMessage MuzzleFlash(GridCoordinates grid, Angle angle)
|
||||||
|
{
|
||||||
|
if (_muzzleFlash == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var offset = angle.ToVec().Normalized / 2;
|
||||||
|
|
||||||
|
var message = new EffectSystemMessage
|
||||||
|
{
|
||||||
|
EffectSprite = _muzzleFlash,
|
||||||
|
Born = _startTime,
|
||||||
|
DeathTime = _deathTime,
|
||||||
|
Coordinates = grid.Translated(offset),
|
||||||
|
//Rotated from east facing
|
||||||
|
Rotation = (float) angle.Theta,
|
||||||
|
Color = Vector4.Multiply(new Vector4(255, 255, 255, 750), ColorModifier),
|
||||||
|
ColorDelta = new Vector4(0, 0, 0, -1500f),
|
||||||
|
Shaded = false
|
||||||
|
};
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
private EffectSystemMessage AfterEffects(GridCoordinates origin, Angle angle, float distance, float offset = 0.0f)
|
||||||
|
{
|
||||||
|
var midPointOffset = angle.ToVec() * distance / 2;
|
||||||
|
var message = new EffectSystemMessage
|
||||||
|
{
|
||||||
|
EffectSprite = _spriteName,
|
||||||
|
Born = _startTime,
|
||||||
|
DeathTime = _deathTime,
|
||||||
|
Size = new Vector2(distance - offset, 1f),
|
||||||
|
Coordinates = origin.Translated(midPointOffset),
|
||||||
|
//Rotated from east facing
|
||||||
|
Rotation = (float) angle.Theta,
|
||||||
|
Color = Vector4.Multiply(new Vector4(255, 255, 255, 750), ColorModifier),
|
||||||
|
ColorDelta = new Vector4(0, 0, 0, -1500f),
|
||||||
|
|
||||||
|
Shaded = false
|
||||||
|
};
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
private EffectSystemMessage ImpactFlash(float distance, Angle angle)
|
||||||
|
{
|
||||||
|
if (_impactFlash == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var message = new EffectSystemMessage
|
||||||
|
{
|
||||||
|
EffectSprite = _impactFlash,
|
||||||
|
Born = _startTime,
|
||||||
|
DeathTime = _deathTime,
|
||||||
|
Coordinates = Owner.Transform.GridPosition.Translated(angle.ToVec() * distance),
|
||||||
|
//Rotated from east facing
|
||||||
|
Rotation = (float) angle.FlipPositive(),
|
||||||
|
Color = Vector4.Multiply(new Vector4(255, 255, 255, 750), ColorModifier),
|
||||||
|
ColorDelta = new Vector4(0, 0, 0, -1500f),
|
||||||
|
Shaded = false
|
||||||
|
};
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,9 +2,13 @@
|
|||||||
using Content.Server.GameObjects.Components.Mobs;
|
using Content.Server.GameObjects.Components.Mobs;
|
||||||
using Content.Shared.GameObjects;
|
using Content.Shared.GameObjects;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Server.GameObjects.EntitySystems;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.GameObjects.Components;
|
using Robust.Shared.GameObjects.Components;
|
||||||
|
using Robust.Shared.GameObjects.Systems;
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Physics;
|
using Robust.Shared.Physics;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
using Robust.Shared.ViewVariables;
|
using Robust.Shared.ViewVariables;
|
||||||
@@ -18,24 +22,32 @@ namespace Content.Server.GameObjects.Components.Projectiles
|
|||||||
|
|
||||||
public bool IgnoreShooter = true;
|
public bool IgnoreShooter = true;
|
||||||
|
|
||||||
private EntityUid Shooter = EntityUid.Invalid;
|
private EntityUid _shooter = EntityUid.Invalid;
|
||||||
|
|
||||||
private Dictionary<DamageType, int> _damages;
|
private Dictionary<DamageType, int> _damages;
|
||||||
|
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
public Dictionary<DamageType, int> Damages => _damages;
|
public Dictionary<DamageType, int> Damages
|
||||||
private float _velocity;
|
|
||||||
public float Velocity
|
|
||||||
{
|
{
|
||||||
get => _velocity;
|
get => _damages;
|
||||||
set => _velocity = value;
|
set => _damages = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool DeleteOnCollide => _deleteOnCollide;
|
||||||
|
private bool _deleteOnCollide;
|
||||||
|
|
||||||
|
// Get that juicy FPS hit sound
|
||||||
|
private string _soundHit;
|
||||||
|
private string _soundHitSpecies;
|
||||||
|
|
||||||
public override void ExposeData(ObjectSerializer serializer)
|
public override void ExposeData(ObjectSerializer serializer)
|
||||||
{
|
{
|
||||||
base.ExposeData(serializer);
|
base.ExposeData(serializer);
|
||||||
|
serializer.DataField(ref _deleteOnCollide, "delete_on_collide", true);
|
||||||
// If not specified 0 damage
|
// If not specified 0 damage
|
||||||
serializer.DataField(ref _damages, "damages", new Dictionary<DamageType, int>());
|
serializer.DataField(ref _damages, "damages", new Dictionary<DamageType, int>());
|
||||||
serializer.DataField(ref _velocity, "velocity", 20f);
|
serializer.DataField(ref _soundHit, "soundHit", null);
|
||||||
|
serializer.DataField(ref _soundHitSpecies, "soundHitSpecies", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public float TimeLeft { get; set; } = 10;
|
public float TimeLeft { get; set; } = 10;
|
||||||
@@ -46,7 +58,7 @@ namespace Content.Server.GameObjects.Components.Projectiles
|
|||||||
/// <param name="shooter"></param>
|
/// <param name="shooter"></param>
|
||||||
public void IgnoreEntity(IEntity shooter)
|
public void IgnoreEntity(IEntity shooter)
|
||||||
{
|
{
|
||||||
Shooter = shooter.Uid;
|
_shooter = shooter.Uid;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -56,7 +68,7 @@ namespace Content.Server.GameObjects.Components.Projectiles
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
bool ICollideSpecial.PreventCollide(IPhysBody collidedwith)
|
bool ICollideSpecial.PreventCollide(IPhysBody collidedwith)
|
||||||
{
|
{
|
||||||
if (IgnoreShooter && collidedwith.Owner.Uid == Shooter)
|
if (IgnoreShooter && collidedwith.Owner.Uid == _shooter)
|
||||||
return true;
|
return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -67,9 +79,17 @@ namespace Content.Server.GameObjects.Components.Projectiles
|
|||||||
/// <param name="entity"></param>
|
/// <param name="entity"></param>
|
||||||
void ICollideBehavior.CollideWith(IEntity entity)
|
void ICollideBehavior.CollideWith(IEntity entity)
|
||||||
{
|
{
|
||||||
|
if (_soundHitSpecies != null && entity.HasComponent<SpeciesComponent>())
|
||||||
|
{
|
||||||
|
EntitySystem.Get<AudioSystem>().PlayAtCoords(_soundHitSpecies, entity.Transform.GridPosition);
|
||||||
|
} else if (_soundHit != null)
|
||||||
|
{
|
||||||
|
EntitySystem.Get<AudioSystem>().PlayAtCoords(_soundHit, entity.Transform.GridPosition);
|
||||||
|
}
|
||||||
|
|
||||||
if (entity.TryGetComponent(out DamageableComponent damage))
|
if (entity.TryGetComponent(out DamageableComponent damage))
|
||||||
{
|
{
|
||||||
Owner.EntityManager.TryGetEntity(Shooter, out var shooter);
|
Owner.EntityManager.TryGetEntity(_shooter, out var shooter);
|
||||||
|
|
||||||
foreach (var (damageType, amount) in _damages)
|
foreach (var (damageType, amount) in _damages)
|
||||||
{
|
{
|
||||||
@@ -87,7 +107,7 @@ namespace Content.Server.GameObjects.Components.Projectiles
|
|||||||
|
|
||||||
void ICollideBehavior.PostCollide(int collideCount)
|
void ICollideBehavior.PostCollide(int collideCount)
|
||||||
{
|
{
|
||||||
if (collideCount > 0) Owner.Delete();
|
if (collideCount > 0 && DeleteOnCollide) Owner.Delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
using System;
|
||||||
|
using Content.Server.GameObjects.Components.Mobs;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects.Components;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Log;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Server.GameObjects.Components.Projectiles
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Adds stun when it collides with an entity
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class StunnableProjectileComponent : Component, ICollideBehavior
|
||||||
|
{
|
||||||
|
public override string Name => "StunnableProjectile";
|
||||||
|
|
||||||
|
// See stunnable for what these do
|
||||||
|
private int _stunAmount;
|
||||||
|
private int _knockdownAmount;
|
||||||
|
private int _slowdownAmount;
|
||||||
|
|
||||||
|
public override void ExposeData(ObjectSerializer serializer)
|
||||||
|
{
|
||||||
|
base.ExposeData(serializer);
|
||||||
|
serializer.DataField(ref _stunAmount, "stunAmount", 0);
|
||||||
|
serializer.DataField(ref _knockdownAmount, "knockdownAmount", 0);
|
||||||
|
serializer.DataField(ref _slowdownAmount, "slowdownAmount", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
if (!Owner.HasComponent<ProjectileComponent>())
|
||||||
|
{
|
||||||
|
Logger.Error("StunProjectile entity must have a ProjectileComponent");
|
||||||
|
throw new InvalidOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ICollideBehavior.CollideWith(IEntity entity)
|
||||||
|
{
|
||||||
|
if (entity.TryGetComponent(out StunnableComponent stunnableComponent))
|
||||||
|
{
|
||||||
|
stunnableComponent.Stun(_stunAmount);
|
||||||
|
stunnableComponent.Knockdown(_knockdownAmount);
|
||||||
|
stunnableComponent.Slowdown(_slowdownAmount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ICollideBehavior.PostCollide(int collidedCount) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,220 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Server.GameObjects.Components.Weapon.Ranged.Barrels;
|
||||||
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
|
using Content.Shared.Audio;
|
||||||
|
using Content.Shared.GameObjects;
|
||||||
|
using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels;
|
||||||
|
using Content.Shared.Interfaces;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Server.GameObjects.Components.Container;
|
||||||
|
using Robust.Server.GameObjects.EntitySystems;
|
||||||
|
using Robust.Server.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Audio;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Random;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class AmmoBoxComponent : Component, IInteractUsing, IUse, IInteractHand, IMapInit
|
||||||
|
{
|
||||||
|
public override string Name => "AmmoBox";
|
||||||
|
|
||||||
|
private BallisticCaliber _caliber;
|
||||||
|
public int Capacity => _capacity;
|
||||||
|
private int _capacity;
|
||||||
|
|
||||||
|
public int AmmoLeft => _spawnedAmmo.Count + _unspawnedCount;
|
||||||
|
private Stack<IEntity> _spawnedAmmo;
|
||||||
|
private Container _ammoContainer;
|
||||||
|
private int _unspawnedCount;
|
||||||
|
|
||||||
|
private string _fillPrototype;
|
||||||
|
|
||||||
|
public override void ExposeData(ObjectSerializer serializer)
|
||||||
|
{
|
||||||
|
base.ExposeData(serializer);
|
||||||
|
serializer.DataField(ref _caliber, "caliber", BallisticCaliber.Unspecified);
|
||||||
|
serializer.DataField(ref _capacity, "capacity", 30);
|
||||||
|
serializer.DataField(ref _fillPrototype, "fillPrototype", null);
|
||||||
|
|
||||||
|
_spawnedAmmo = new Stack<IEntity>(_capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
_ammoContainer = ContainerManagerComponent.Ensure<Container>($"{Name}-container", Owner, out var existing);
|
||||||
|
|
||||||
|
if (existing)
|
||||||
|
{
|
||||||
|
foreach (var entity in _ammoContainer.ContainedEntities)
|
||||||
|
{
|
||||||
|
_unspawnedCount--;
|
||||||
|
_spawnedAmmo.Push(entity);
|
||||||
|
_ammoContainer.Insert(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void IMapInit.MapInit()
|
||||||
|
{
|
||||||
|
_unspawnedCount += _capacity;
|
||||||
|
UpdateAppearance();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateAppearance()
|
||||||
|
{
|
||||||
|
if (Owner.TryGetComponent(out AppearanceComponent appearanceComponent))
|
||||||
|
{
|
||||||
|
appearanceComponent.SetData(MagazineBarrelVisuals.MagLoaded, true);
|
||||||
|
appearanceComponent.SetData(AmmoVisuals.AmmoCount, AmmoLeft);
|
||||||
|
appearanceComponent.SetData(AmmoVisuals.AmmoMax, _capacity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEntity TakeAmmo()
|
||||||
|
{
|
||||||
|
if (_spawnedAmmo.TryPop(out IEntity ammo))
|
||||||
|
{
|
||||||
|
_ammoContainer.Remove(ammo);
|
||||||
|
return ammo;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_unspawnedCount > 0)
|
||||||
|
{
|
||||||
|
ammo = Owner.EntityManager.SpawnEntity(_fillPrototype, Owner.Transform.GridPosition);
|
||||||
|
_unspawnedCount--;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ammo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryInsertAmmo(IEntity user, IEntity entity)
|
||||||
|
{
|
||||||
|
if (!entity.TryGetComponent(out AmmoComponent ammoComponent))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ammoComponent.Caliber != _caliber)
|
||||||
|
{
|
||||||
|
Owner.PopupMessage(user, Loc.GetString("Wrong caliber"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AmmoLeft >= Capacity)
|
||||||
|
{
|
||||||
|
Owner.PopupMessage(user, Loc.GetString("No room"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_spawnedAmmo.Push(entity);
|
||||||
|
_ammoContainer.Insert(entity);
|
||||||
|
UpdateAppearance();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
if (eventArgs.Using.HasComponent<AmmoComponent>())
|
||||||
|
{
|
||||||
|
return TryInsertAmmo(eventArgs.User, eventArgs.Using);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eventArgs.Using.TryGetComponent(out RangedMagazineComponent rangedMagazine))
|
||||||
|
{
|
||||||
|
for (var i = 0; i < Math.Max(10, rangedMagazine.ShotsLeft); i++)
|
||||||
|
{
|
||||||
|
var ammo = rangedMagazine.TakeAmmo();
|
||||||
|
|
||||||
|
if (!TryInsertAmmo(eventArgs.User, ammo))
|
||||||
|
{
|
||||||
|
rangedMagazine.TryInsertAmmo(eventArgs.User, ammo);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryUse(IEntity user)
|
||||||
|
{
|
||||||
|
if (!user.TryGetComponent(out HandsComponent handsComponent))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ammo = TakeAmmo();
|
||||||
|
var itemComponent = ammo.GetComponent<ItemComponent>();
|
||||||
|
|
||||||
|
if (!handsComponent.CanPutInHand(itemComponent))
|
||||||
|
{
|
||||||
|
TryInsertAmmo(user, ammo);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
handsComponent.PutInHand(itemComponent);
|
||||||
|
UpdateAppearance();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EjectContents(int count)
|
||||||
|
{
|
||||||
|
var ejectCount = Math.Min(count, Capacity);
|
||||||
|
var ejectAmmo = new List<IEntity>(ejectCount);
|
||||||
|
|
||||||
|
for (var i = 0; i < Math.Min(count, Capacity); i++)
|
||||||
|
{
|
||||||
|
var ammo = TakeAmmo();
|
||||||
|
if (ammo == null)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ejectAmmo.Add(ammo);
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerRangedBarrelComponent.EjectCasings(ejectAmmo);
|
||||||
|
UpdateAppearance();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IUse.UseEntity(UseEntityEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
return TryUse(eventArgs.User);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IInteractHand.InteractHand(InteractHandEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
return TryUse(eventArgs.User);
|
||||||
|
}
|
||||||
|
|
||||||
|
// So if you have 200 rounds in a box and that suddenly creates 200 entities you're not having a fun time
|
||||||
|
[Verb]
|
||||||
|
private sealed class DumpVerb : Verb<AmmoBoxComponent>
|
||||||
|
{
|
||||||
|
protected override void GetData(IEntity user, AmmoBoxComponent component, VerbData data)
|
||||||
|
{
|
||||||
|
data.Text = Loc.GetString("Dump 10");
|
||||||
|
data.Visibility = component.AmmoLeft > 0 ? VerbVisibility.Visible : VerbVisibility.Disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Activate(IEntity user, AmmoBoxComponent component)
|
||||||
|
{
|
||||||
|
component.EjectContents(10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
using System;
|
||||||
|
using System.Timers;
|
||||||
|
using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Server.GameObjects.EntitySystems;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects.EntitySystemMessages;
|
||||||
|
using Robust.Shared.GameObjects.Systems;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Timing;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
using Logger = Robust.Shared.Log.Logger;
|
||||||
|
using Timer = Robust.Shared.Timers.Timer;
|
||||||
|
|
||||||
|
namespace Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Allows this entity to be loaded into a ranged weapon (if the caliber matches)
|
||||||
|
/// Generally used for bullets but can be used for other things like bananas
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public class AmmoComponent : Component
|
||||||
|
{
|
||||||
|
public override string Name => "Ammo";
|
||||||
|
public BallisticCaliber Caliber => _caliber;
|
||||||
|
private BallisticCaliber _caliber;
|
||||||
|
public bool Spent
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_ammoIsProjectile)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _spent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private bool _spent;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used for anything without a case that fires itself
|
||||||
|
/// </summary>
|
||||||
|
private bool _ammoIsProjectile;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used for something that is deleted when the projectile is retrieved
|
||||||
|
/// </summary>
|
||||||
|
public bool Caseless => _caseless;
|
||||||
|
private bool _caseless;
|
||||||
|
// Rather than managing bullet / case state seemed easier to just have 2 toggles
|
||||||
|
// ammoIsProjectile being for a beanbag for example and caseless being for ClRifle rounds
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// For shotguns where they might shoot multiple entities
|
||||||
|
/// </summary>
|
||||||
|
public int ProjectilesFired => _projectilesFired;
|
||||||
|
private int _projectilesFired;
|
||||||
|
private string _projectileId;
|
||||||
|
// How far apart each entity is if multiple are shot
|
||||||
|
public float EvenSpreadAngle => _evenSpreadAngle;
|
||||||
|
private float _evenSpreadAngle;
|
||||||
|
/// <summary>
|
||||||
|
/// How fast the shot entities travel
|
||||||
|
/// </summary>
|
||||||
|
public float Velocity => _velocity;
|
||||||
|
private float _velocity;
|
||||||
|
|
||||||
|
private string _muzzleFlashSprite;
|
||||||
|
|
||||||
|
public string SoundCollectionEject => _soundCollectionEject;
|
||||||
|
private string _soundCollectionEject;
|
||||||
|
|
||||||
|
public override void ExposeData(ObjectSerializer serializer)
|
||||||
|
{
|
||||||
|
base.ExposeData(serializer);
|
||||||
|
// For shotty of whatever as well
|
||||||
|
serializer.DataField(ref _projectileId, "projectile", null);
|
||||||
|
serializer.DataField(ref _caliber, "caliber", BallisticCaliber.Unspecified);
|
||||||
|
serializer.DataField(ref _projectilesFired, "projectilesFired", 1);
|
||||||
|
// Used for shotty to determine overall pellet spread
|
||||||
|
serializer.DataField(ref _evenSpreadAngle, "ammoSpread", 0);
|
||||||
|
serializer.DataField(ref _velocity, "ammoVelocity", 20.0f);
|
||||||
|
serializer.DataField(ref _ammoIsProjectile, "isProjectile", false);
|
||||||
|
serializer.DataField(ref _caseless, "caseless", false);
|
||||||
|
// Being both caseless and shooting yourself doesn't make sense
|
||||||
|
DebugTools.Assert(!(_ammoIsProjectile && _caseless));
|
||||||
|
serializer.DataField(ref _muzzleFlashSprite, "muzzleFlash", "Objects/Guns/Projectiles/bullet_muzzle.png");
|
||||||
|
serializer.DataField(ref _soundCollectionEject, "soundCollectionEject", "CasingEject");
|
||||||
|
|
||||||
|
if (_projectilesFired < 1)
|
||||||
|
{
|
||||||
|
Logger.Error("Ammo can't have less than 1 projectile");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_evenSpreadAngle > 0 && _projectilesFired == 1)
|
||||||
|
{
|
||||||
|
Logger.Error("Can't have an even spread if only 1 projectile is fired");
|
||||||
|
throw new InvalidOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEntity TakeBullet()
|
||||||
|
{
|
||||||
|
if (_ammoIsProjectile)
|
||||||
|
{
|
||||||
|
return Owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_spent)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_spent = true;
|
||||||
|
if (Owner.TryGetComponent(out AppearanceComponent appearanceComponent))
|
||||||
|
{
|
||||||
|
appearanceComponent.SetData(AmmoVisuals.Spent, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
var entity = Owner.EntityManager.SpawnEntity(_projectileId, Owner.Transform.GridPosition);
|
||||||
|
DebugTools.AssertNotNull(entity);
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MuzzleFlash(GridCoordinates grid, Angle angle)
|
||||||
|
{
|
||||||
|
if (_muzzleFlashSprite == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var time = IoCManager.Resolve<IGameTiming>().CurTime;
|
||||||
|
var deathTime = time + TimeSpan.FromMilliseconds(200);
|
||||||
|
// Offset the sprite so it actually looks like it's coming from the gun
|
||||||
|
var offset = angle.ToVec().Normalized / 2;
|
||||||
|
|
||||||
|
var message = new EffectSystemMessage
|
||||||
|
{
|
||||||
|
EffectSprite = _muzzleFlashSprite,
|
||||||
|
Born = time,
|
||||||
|
DeathTime = deathTime,
|
||||||
|
Coordinates = grid.Translated(offset),
|
||||||
|
//Rotated from east facing
|
||||||
|
Rotation = (float) angle.Theta,
|
||||||
|
Color = Vector4.Multiply(new Vector4(255, 255, 255, 255), 1.0f),
|
||||||
|
ColorDelta = new Vector4(0, 0, 0, -1500f),
|
||||||
|
Shaded = false
|
||||||
|
};
|
||||||
|
EntitySystem.Get<EffectSystem>().CreateParticle(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum BallisticCaliber
|
||||||
|
{
|
||||||
|
Unspecified = 0,
|
||||||
|
A357, // Placeholder?
|
||||||
|
ClRifle,
|
||||||
|
SRifle,
|
||||||
|
Pistol,
|
||||||
|
A35, // Placeholder?
|
||||||
|
LRifle,
|
||||||
|
Magnum,
|
||||||
|
AntiMaterial,
|
||||||
|
Shotgun,
|
||||||
|
Cap, // Placeholder
|
||||||
|
Rocket,
|
||||||
|
Dart, // Placeholder
|
||||||
|
Grenade,
|
||||||
|
Energy,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Server.GameObjects.Components.Weapon.Ranged.Barrels;
|
||||||
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
|
using Content.Shared.Audio;
|
||||||
|
using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels;
|
||||||
|
using Content.Shared.Interfaces;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Server.GameObjects.Components.Container;
|
||||||
|
using Robust.Server.GameObjects.EntitySystems;
|
||||||
|
using Robust.Server.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Audio;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Random;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
public class RangedMagazineComponent : Component, IMapInit, IInteractUsing, IUse
|
||||||
|
{
|
||||||
|
public override string Name => "RangedMagazine";
|
||||||
|
|
||||||
|
private Stack<IEntity> _spawnedAmmo = new Stack<IEntity>();
|
||||||
|
private Container _ammoContainer;
|
||||||
|
|
||||||
|
public int ShotsLeft => _spawnedAmmo.Count + _unspawnedCount;
|
||||||
|
public int Capacity => _capacity;
|
||||||
|
private int _capacity;
|
||||||
|
|
||||||
|
public MagazineType MagazineType => _magazineType;
|
||||||
|
private MagazineType _magazineType;
|
||||||
|
public BallisticCaliber Caliber => _caliber;
|
||||||
|
private BallisticCaliber _caliber;
|
||||||
|
|
||||||
|
private AppearanceComponent _appearanceComponent;
|
||||||
|
|
||||||
|
// If there's anything already in the magazine
|
||||||
|
private string _fillPrototype;
|
||||||
|
// By default the magazine won't spawn the entity until needed so we need to keep track of how many left we can spawn
|
||||||
|
// Generally you probablt don't want to use this
|
||||||
|
private int _unspawnedCount;
|
||||||
|
|
||||||
|
public override void ExposeData(ObjectSerializer serializer)
|
||||||
|
{
|
||||||
|
base.ExposeData(serializer);
|
||||||
|
serializer.DataField(ref _magazineType, "magazineType", MagazineType.Unspecified);
|
||||||
|
serializer.DataField(ref _caliber, "caliber", BallisticCaliber.Unspecified);
|
||||||
|
serializer.DataField(ref _fillPrototype, "fillPrototype", null);
|
||||||
|
serializer.DataField(ref _capacity, "capacity", 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IMapInit.MapInit()
|
||||||
|
{
|
||||||
|
if (_fillPrototype != null)
|
||||||
|
{
|
||||||
|
_unspawnedCount += Capacity;
|
||||||
|
}
|
||||||
|
UpdateAppearance();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
_ammoContainer = ContainerManagerComponent.Ensure<Container>($"{Name}-magazine", Owner, out var existing);
|
||||||
|
|
||||||
|
if (existing)
|
||||||
|
{
|
||||||
|
if (_ammoContainer.ContainedEntities.Count > Capacity)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Initialized capacity of magazine higher than its actual capacity");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var entity in _ammoContainer.ContainedEntities)
|
||||||
|
{
|
||||||
|
_spawnedAmmo.Push(entity);
|
||||||
|
_unspawnedCount--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Owner.TryGetComponent(out AppearanceComponent appearanceComponent))
|
||||||
|
{
|
||||||
|
_appearanceComponent = appearanceComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
_appearanceComponent?.SetData(MagazineBarrelVisuals.MagLoaded, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateAppearance()
|
||||||
|
{
|
||||||
|
_appearanceComponent?.SetData(AmmoVisuals.AmmoCount, ShotsLeft);
|
||||||
|
_appearanceComponent?.SetData(AmmoVisuals.AmmoMax, Capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryInsertAmmo(IEntity user, IEntity ammo)
|
||||||
|
{
|
||||||
|
if (!ammo.TryGetComponent(out AmmoComponent ammoComponent))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ammoComponent.Caliber != _caliber)
|
||||||
|
{
|
||||||
|
Owner.PopupMessage(user, Loc.GetString("Wrong caliber"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ShotsLeft >= Capacity)
|
||||||
|
{
|
||||||
|
Owner.PopupMessage(user, Loc.GetString("Magazine is full"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ammoContainer.Insert(ammo);
|
||||||
|
_spawnedAmmo.Push(ammo);
|
||||||
|
UpdateAppearance();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEntity TakeAmmo()
|
||||||
|
{
|
||||||
|
IEntity ammo = null;
|
||||||
|
// If anything's spawned use that first, otherwise use the fill prototype as a fallback (if we have spawn count left)
|
||||||
|
if (_spawnedAmmo.TryPop(out var entity))
|
||||||
|
{
|
||||||
|
ammo = entity;
|
||||||
|
_ammoContainer.Remove(entity);
|
||||||
|
}
|
||||||
|
else if (_unspawnedCount > 0)
|
||||||
|
{
|
||||||
|
_unspawnedCount--;
|
||||||
|
ammo = Owner.EntityManager.SpawnEntity(_fillPrototype, Owner.Transform.GridPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateAppearance();
|
||||||
|
return ammo;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
return TryInsertAmmo(eventArgs.User, eventArgs.Using);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IUse.UseEntity(UseEntityEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
if (!eventArgs.User.TryGetComponent(out HandsComponent handsComponent))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ammo = TakeAmmo();
|
||||||
|
if (ammo == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var itemComponent = ammo.GetComponent<ItemComponent>();
|
||||||
|
if (!handsComponent.CanPutInHand(itemComponent))
|
||||||
|
{
|
||||||
|
ammo.Transform.GridPosition = eventArgs.User.Transform.GridPosition;
|
||||||
|
ServerRangedBarrelComponent.EjectCasing(ammo);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
handsComponent.PutInHand(itemComponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,215 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Server.GameObjects.Components.Weapon.Ranged.Barrels;
|
||||||
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
|
using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels;
|
||||||
|
using Content.Shared.Interfaces;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Server.GameObjects.Components.Container;
|
||||||
|
using Robust.Server.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Used to load certain ranged weapons quickly
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public class SpeedLoaderComponent : Component, IAfterInteract, IInteractUsing, IMapInit, IUse
|
||||||
|
{
|
||||||
|
public override string Name => "SpeedLoader";
|
||||||
|
|
||||||
|
private BallisticCaliber _caliber;
|
||||||
|
public int Capacity => _capacity;
|
||||||
|
private int _capacity;
|
||||||
|
private Container _ammoContainer;
|
||||||
|
private Stack<IEntity> _spawnedAmmo;
|
||||||
|
private int _unspawnedCount;
|
||||||
|
|
||||||
|
public int AmmoLeft => _spawnedAmmo.Count + _unspawnedCount;
|
||||||
|
|
||||||
|
private string _fillPrototype;
|
||||||
|
|
||||||
|
public override void ExposeData(ObjectSerializer serializer)
|
||||||
|
{
|
||||||
|
base.ExposeData(serializer);
|
||||||
|
serializer.DataField(ref _caliber, "caliber", BallisticCaliber.Unspecified);
|
||||||
|
serializer.DataField(ref _capacity, "capacity", 6);
|
||||||
|
serializer.DataField(ref _fillPrototype, "fillPrototype", null);
|
||||||
|
|
||||||
|
_spawnedAmmo = new Stack<IEntity>(_capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
_ammoContainer = ContainerManagerComponent.Ensure<Container>($"{Name}-container", Owner, out var existing);
|
||||||
|
|
||||||
|
if (existing)
|
||||||
|
{
|
||||||
|
foreach (var ammo in _ammoContainer.ContainedEntities)
|
||||||
|
{
|
||||||
|
_unspawnedCount--;
|
||||||
|
_spawnedAmmo.Push(ammo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void IMapInit.MapInit()
|
||||||
|
{
|
||||||
|
_unspawnedCount += _capacity;
|
||||||
|
UpdateAppearance();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateAppearance()
|
||||||
|
{
|
||||||
|
if (Owner.TryGetComponent(out AppearanceComponent appearanceComponent))
|
||||||
|
{
|
||||||
|
appearanceComponent?.SetData(MagazineBarrelVisuals.MagLoaded, true);
|
||||||
|
appearanceComponent?.SetData(AmmoVisuals.AmmoCount, AmmoLeft);
|
||||||
|
appearanceComponent?.SetData(AmmoVisuals.AmmoMax, Capacity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryInsertAmmo(IEntity user, IEntity entity)
|
||||||
|
{
|
||||||
|
if (!entity.TryGetComponent(out AmmoComponent ammoComponent))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ammoComponent.Caliber != _caliber)
|
||||||
|
{
|
||||||
|
Owner.PopupMessage(user, Loc.GetString("Wrong caliber"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AmmoLeft >= Capacity)
|
||||||
|
{
|
||||||
|
Owner.PopupMessage(user, Loc.GetString("No room"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_spawnedAmmo.Push(entity);
|
||||||
|
_ammoContainer.Insert(entity);
|
||||||
|
UpdateAppearance();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool UseEntity(IEntity user)
|
||||||
|
{
|
||||||
|
if (!user.TryGetComponent(out HandsComponent handsComponent))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ammo = TakeAmmo();
|
||||||
|
if (ammo == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var itemComponent = ammo.GetComponent<ItemComponent>();
|
||||||
|
if (!handsComponent.CanPutInHand(itemComponent))
|
||||||
|
{
|
||||||
|
ServerRangedBarrelComponent.EjectCasing(ammo);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
handsComponent.PutInHand(itemComponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateAppearance();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEntity TakeAmmo()
|
||||||
|
{
|
||||||
|
if (_spawnedAmmo.TryPop(out var entity))
|
||||||
|
{
|
||||||
|
_ammoContainer.Remove(entity);
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_unspawnedCount > 0)
|
||||||
|
{
|
||||||
|
entity = Owner.EntityManager.SpawnEntity(_fillPrototype, Owner.Transform.GridPosition);
|
||||||
|
_unspawnedCount--;
|
||||||
|
}
|
||||||
|
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
if (eventArgs.Target == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This area is dirty but not sure of an easier way to do it besides add an interface or somethin
|
||||||
|
bool changed = false;
|
||||||
|
|
||||||
|
if (eventArgs.Target.TryGetComponent(out RevolverBarrelComponent revolverBarrel))
|
||||||
|
{
|
||||||
|
for (var i = 0; i < Capacity; i++)
|
||||||
|
{
|
||||||
|
var ammo = TakeAmmo();
|
||||||
|
if (ammo == null)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (revolverBarrel.TryInsertBullet(eventArgs.User, ammo))
|
||||||
|
{
|
||||||
|
changed = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Take the ammo back
|
||||||
|
TryInsertAmmo(eventArgs.User, ammo);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (eventArgs.Target.TryGetComponent(out BoltActionBarrelComponent boltActionBarrel))
|
||||||
|
{
|
||||||
|
for (var i = 0; i < Capacity; i++)
|
||||||
|
{
|
||||||
|
var ammo = TakeAmmo();
|
||||||
|
if (ammo == null)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (boltActionBarrel.TryInsertBullet(eventArgs.User, ammo))
|
||||||
|
{
|
||||||
|
changed = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Take the ammo back
|
||||||
|
TryInsertAmmo(eventArgs.User, ammo);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed)
|
||||||
|
{
|
||||||
|
UpdateAppearance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
return TryInsertAmmo(eventArgs.User, eventArgs.Using);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IUse.UseEntity(UseEntityEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
return UseEntity(eventArgs.User);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,322 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Server.GameObjects.Components.Sound;
|
||||||
|
using Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition;
|
||||||
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
|
using Content.Shared.GameObjects;
|
||||||
|
using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels;
|
||||||
|
using Content.Shared.Interfaces;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Server.GameObjects.Components.Container;
|
||||||
|
using Robust.Server.GameObjects.EntitySystems;
|
||||||
|
using Robust.Server.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Audio;
|
||||||
|
using Robust.Shared.Containers;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects.Systems;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Shotguns mostly
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class BoltActionBarrelComponent : ServerRangedBarrelComponent, IMapInit
|
||||||
|
{
|
||||||
|
// Originally I had this logic shared with PumpBarrel and used a couple of variables to control things
|
||||||
|
// but it felt a lot messier to play around with, especially when adding verbs
|
||||||
|
|
||||||
|
public override string Name => "BoltActionBarrel";
|
||||||
|
|
||||||
|
public override int ShotsLeft
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var chamberCount = _chamberContainer.ContainedEntity != null ? 1 : 0;
|
||||||
|
return chamberCount + _spawnedAmmo.Count + _unspawnedCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public override int Capacity => _capacity;
|
||||||
|
private int _capacity;
|
||||||
|
|
||||||
|
private ContainerSlot _chamberContainer;
|
||||||
|
private Stack<IEntity> _spawnedAmmo;
|
||||||
|
private Container _ammoContainer;
|
||||||
|
|
||||||
|
private BallisticCaliber _caliber;
|
||||||
|
|
||||||
|
private string _fillPrototype;
|
||||||
|
private int _unspawnedCount;
|
||||||
|
|
||||||
|
public bool BoltOpen
|
||||||
|
{
|
||||||
|
get => _boltOpen;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_boltOpen == value)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var soundSystem = EntitySystem.Get<AudioSystem>();
|
||||||
|
|
||||||
|
if (value)
|
||||||
|
{
|
||||||
|
if (_soundBoltOpen != null)
|
||||||
|
{
|
||||||
|
soundSystem.PlayAtCoords(_soundBoltOpen, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_soundBoltClosed != null)
|
||||||
|
{
|
||||||
|
soundSystem.PlayAtCoords(_soundBoltClosed, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_boltOpen = value;
|
||||||
|
UpdateAppearance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private bool _boltOpen;
|
||||||
|
private bool _autoCycle;
|
||||||
|
|
||||||
|
private AppearanceComponent _appearanceComponent;
|
||||||
|
// Sounds
|
||||||
|
private string _soundCycle;
|
||||||
|
private string _soundBoltOpen;
|
||||||
|
private string _soundBoltClosed;
|
||||||
|
private string _soundInsert;
|
||||||
|
|
||||||
|
public override void ExposeData(ObjectSerializer serializer)
|
||||||
|
{
|
||||||
|
base.ExposeData(serializer);
|
||||||
|
serializer.DataField(ref _caliber, "caliber", BallisticCaliber.Unspecified);
|
||||||
|
serializer.DataField(ref _capacity, "capacity", 6);
|
||||||
|
serializer.DataField(ref _fillPrototype, "fillPrototype", null);
|
||||||
|
serializer.DataField(ref _autoCycle, "autoCycle", false);
|
||||||
|
|
||||||
|
serializer.DataField(ref _soundCycle, "soundCycle", "/Audio/Guns/Cock/sf_rifle_cock.ogg");
|
||||||
|
serializer.DataField(ref _soundBoltOpen, "soundBoltOpen", "/Audio/Guns/Bolt/rifle_bolt_open.ogg");
|
||||||
|
serializer.DataField(ref _soundBoltClosed, "soundBoltClosed", "/Audio/Guns/Bolt/rifle_bolt_closed.ogg");
|
||||||
|
serializer.DataField(ref _soundInsert, "soundInsert", "/Audio/Guns/MagIn/bullet_insert.ogg");
|
||||||
|
}
|
||||||
|
|
||||||
|
void IMapInit.MapInit()
|
||||||
|
{
|
||||||
|
if (_fillPrototype != null)
|
||||||
|
{
|
||||||
|
_unspawnedCount += Capacity - 1;
|
||||||
|
}
|
||||||
|
UpdateAppearance();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
// TODO: Add existing ammo support on revolvers
|
||||||
|
base.Initialize();
|
||||||
|
_spawnedAmmo = new Stack<IEntity>(_capacity - 1);
|
||||||
|
_ammoContainer = ContainerManagerComponent.Ensure<Container>($"{Name}-ammo-container", Owner, out var existing);
|
||||||
|
|
||||||
|
if (existing)
|
||||||
|
{
|
||||||
|
foreach (var entity in _ammoContainer.ContainedEntities)
|
||||||
|
{
|
||||||
|
_spawnedAmmo.Push(entity);
|
||||||
|
_unspawnedCount--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_chamberContainer = ContainerManagerComponent.Ensure<ContainerSlot>($"{Name}-chamber-container", Owner);
|
||||||
|
|
||||||
|
if (Owner.TryGetComponent(out AppearanceComponent appearanceComponent))
|
||||||
|
{
|
||||||
|
_appearanceComponent = appearanceComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
_appearanceComponent?.SetData(MagazineBarrelVisuals.MagLoaded, true);
|
||||||
|
UpdateAppearance();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateAppearance()
|
||||||
|
{
|
||||||
|
_appearanceComponent?.SetData(BarrelBoltVisuals.BoltOpen, BoltOpen);
|
||||||
|
_appearanceComponent?.SetData(AmmoVisuals.AmmoCount, ShotsLeft);
|
||||||
|
_appearanceComponent?.SetData(AmmoVisuals.AmmoMax, Capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IEntity PeekAmmo()
|
||||||
|
{
|
||||||
|
return _chamberContainer.ContainedEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IEntity TakeProjectile()
|
||||||
|
{
|
||||||
|
var chamberEntity = _chamberContainer.ContainedEntity;
|
||||||
|
if (_autoCycle)
|
||||||
|
{
|
||||||
|
Cycle();
|
||||||
|
}
|
||||||
|
return chamberEntity?.GetComponent<AmmoComponent>().TakeBullet();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool WeaponCanFire()
|
||||||
|
{
|
||||||
|
if (!base.WeaponCanFire())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !BoltOpen && _chamberContainer.ContainedEntity != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Cycle(bool manual = false)
|
||||||
|
{
|
||||||
|
var chamberedEntity = _chamberContainer.ContainedEntity;
|
||||||
|
if (chamberedEntity != null)
|
||||||
|
{
|
||||||
|
_chamberContainer.Remove(chamberedEntity);
|
||||||
|
var ammoComponent = chamberedEntity.GetComponent<AmmoComponent>();
|
||||||
|
if (!ammoComponent.Caseless)
|
||||||
|
{
|
||||||
|
EjectCasing(chamberedEntity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_spawnedAmmo.TryPop(out var next))
|
||||||
|
{
|
||||||
|
_ammoContainer.Remove(next);
|
||||||
|
_chamberContainer.Insert(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_unspawnedCount > 0)
|
||||||
|
{
|
||||||
|
_unspawnedCount--;
|
||||||
|
var ammoEntity = Owner.EntityManager.SpawnEntity(_fillPrototype, Owner.Transform.GridPosition);
|
||||||
|
_chamberContainer.Insert(ammoEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_chamberContainer.ContainedEntity == null && manual)
|
||||||
|
{
|
||||||
|
BoltOpen = true;
|
||||||
|
if (ContainerHelpers.TryGetContainer(Owner, out var container))
|
||||||
|
{
|
||||||
|
Owner.PopupMessage(container.Owner, Loc.GetString("Bolt opened"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (manual)
|
||||||
|
{
|
||||||
|
if (_soundCycle != null)
|
||||||
|
{
|
||||||
|
EntitySystem.Get<AudioSystem>().PlayAtCoords(_soundCycle, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Dirty();
|
||||||
|
UpdateAppearance();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryInsertBullet(IEntity user, IEntity ammo)
|
||||||
|
{
|
||||||
|
if (!ammo.TryGetComponent(out AmmoComponent ammoComponent))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ammoComponent.Caliber != _caliber)
|
||||||
|
{
|
||||||
|
Owner.PopupMessage(user, Loc.GetString("Wrong caliber"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!BoltOpen)
|
||||||
|
{
|
||||||
|
Owner.PopupMessage(user, Loc.GetString("Bolt isn't open"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_chamberContainer.ContainedEntity == null)
|
||||||
|
{
|
||||||
|
_chamberContainer.Insert(ammo);
|
||||||
|
if (_soundInsert != null)
|
||||||
|
{
|
||||||
|
EntitySystem.Get<AudioSystem>().PlayAtCoords(_soundInsert, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-2));
|
||||||
|
}
|
||||||
|
// Dirty();
|
||||||
|
UpdateAppearance();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_ammoContainer.ContainedEntities.Count < Capacity - 1)
|
||||||
|
{
|
||||||
|
_ammoContainer.Insert(ammo);
|
||||||
|
_spawnedAmmo.Push(ammo);
|
||||||
|
if (_soundInsert != null)
|
||||||
|
{
|
||||||
|
EntitySystem.Get<AudioSystem>().PlayAtCoords(_soundInsert, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-2));
|
||||||
|
}
|
||||||
|
// Dirty();
|
||||||
|
UpdateAppearance();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Owner.PopupMessage(user, Loc.GetString("No room"));
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool UseEntity(UseEntityEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
if (BoltOpen)
|
||||||
|
{
|
||||||
|
BoltOpen = false;
|
||||||
|
Owner.PopupMessage(eventArgs.User, Loc.GetString("Bolt closed"));
|
||||||
|
// Dirty();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Cycle(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool InteractUsing(InteractUsingEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
return TryInsertBullet(eventArgs.User, eventArgs.Using);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Verb]
|
||||||
|
private sealed class OpenBoltVerb : Verb<BoltActionBarrelComponent>
|
||||||
|
{
|
||||||
|
protected override void GetData(IEntity user, BoltActionBarrelComponent component, VerbData data)
|
||||||
|
{
|
||||||
|
data.Text = Loc.GetString("Open bolt");
|
||||||
|
data.Visibility = component.BoltOpen ? VerbVisibility.Disabled : VerbVisibility.Visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Activate(IEntity user, BoltActionBarrelComponent component)
|
||||||
|
{
|
||||||
|
component.BoltOpen = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Verb]
|
||||||
|
private sealed class CloseBoltVerb : Verb<BoltActionBarrelComponent>
|
||||||
|
{
|
||||||
|
protected override void GetData(IEntity user, BoltActionBarrelComponent component, VerbData data)
|
||||||
|
{
|
||||||
|
data.Text = Loc.GetString("Close bolt");
|
||||||
|
data.Visibility = component.BoltOpen ? VerbVisibility.Visible : VerbVisibility.Disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Activate(IEntity user, BoltActionBarrelComponent component)
|
||||||
|
{
|
||||||
|
component.BoltOpen = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,215 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Server.GameObjects.Components.Sound;
|
||||||
|
using Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition;
|
||||||
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
|
using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels;
|
||||||
|
using Content.Shared.Interfaces;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Server.GameObjects.Components.Container;
|
||||||
|
using Robust.Server.GameObjects.EntitySystems;
|
||||||
|
using Robust.Server.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Audio;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects.Systems;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Bolt-action rifles
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class PumpBarrelComponent : ServerRangedBarrelComponent, IMapInit
|
||||||
|
{
|
||||||
|
public override string Name => "PumpBarrel";
|
||||||
|
|
||||||
|
public override int ShotsLeft
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var chamberCount = _chamberContainer.ContainedEntity != null ? 1 : 0;
|
||||||
|
return chamberCount + _spawnedAmmo.Count + _unspawnedCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int Capacity => _capacity;
|
||||||
|
private int _capacity;
|
||||||
|
|
||||||
|
// Even a point having a chamber? I guess it makes some of the below code cleaner
|
||||||
|
private ContainerSlot _chamberContainer;
|
||||||
|
private Stack<IEntity> _spawnedAmmo;
|
||||||
|
private Container _ammoContainer;
|
||||||
|
|
||||||
|
private BallisticCaliber _caliber;
|
||||||
|
|
||||||
|
private string _fillPrototype;
|
||||||
|
private int _unspawnedCount;
|
||||||
|
|
||||||
|
private bool _manualCycle;
|
||||||
|
|
||||||
|
private AppearanceComponent _appearanceComponent;
|
||||||
|
|
||||||
|
// Sounds
|
||||||
|
private string _soundCycle;
|
||||||
|
private string _soundInsert;
|
||||||
|
|
||||||
|
public override void ExposeData(ObjectSerializer serializer)
|
||||||
|
{
|
||||||
|
base.ExposeData(serializer);
|
||||||
|
serializer.DataField(ref _caliber, "caliber", BallisticCaliber.Unspecified);
|
||||||
|
serializer.DataField(ref _capacity, "capacity", 6);
|
||||||
|
serializer.DataField(ref _fillPrototype, "fillPrototype", null);
|
||||||
|
serializer.DataField(ref _manualCycle, "manualCycle", true);
|
||||||
|
|
||||||
|
serializer.DataField(ref _soundCycle, "soundCycle", "/Audio/Guns/Cock/sf_rifle_cock.ogg");
|
||||||
|
serializer.DataField(ref _soundInsert, "soundInsert", "/Audio/Guns/MagIn/bullet_insert.ogg");
|
||||||
|
|
||||||
|
_spawnedAmmo = new Stack<IEntity>(_capacity - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IMapInit.MapInit()
|
||||||
|
{
|
||||||
|
if (_fillPrototype != null)
|
||||||
|
{
|
||||||
|
_unspawnedCount += Capacity - 1;
|
||||||
|
}
|
||||||
|
UpdateAppearance();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
_ammoContainer =
|
||||||
|
ContainerManagerComponent.Ensure<Container>($"{Name}-ammo-container", Owner, out var existing);
|
||||||
|
|
||||||
|
if (existing)
|
||||||
|
{
|
||||||
|
foreach (var entity in _ammoContainer.ContainedEntities)
|
||||||
|
{
|
||||||
|
_spawnedAmmo.Push(entity);
|
||||||
|
_unspawnedCount--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_chamberContainer =
|
||||||
|
ContainerManagerComponent.Ensure<ContainerSlot>($"{Name}-chamber-container", Owner, out existing);
|
||||||
|
if (existing)
|
||||||
|
{
|
||||||
|
_unspawnedCount--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Owner.TryGetComponent(out AppearanceComponent appearanceComponent))
|
||||||
|
{
|
||||||
|
_appearanceComponent = appearanceComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
_appearanceComponent?.SetData(MagazineBarrelVisuals.MagLoaded, true);
|
||||||
|
UpdateAppearance();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateAppearance()
|
||||||
|
{
|
||||||
|
_appearanceComponent?.SetData(AmmoVisuals.AmmoCount, ShotsLeft);
|
||||||
|
_appearanceComponent?.SetData(AmmoVisuals.AmmoMax, Capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IEntity PeekAmmo()
|
||||||
|
{
|
||||||
|
return _chamberContainer.ContainedEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IEntity TakeProjectile()
|
||||||
|
{
|
||||||
|
var chamberEntity = _chamberContainer.ContainedEntity;
|
||||||
|
if (!_manualCycle)
|
||||||
|
{
|
||||||
|
Cycle();
|
||||||
|
}
|
||||||
|
return chamberEntity?.GetComponent<AmmoComponent>().TakeBullet();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Cycle(bool manual = false)
|
||||||
|
{
|
||||||
|
var chamberedEntity = _chamberContainer.ContainedEntity;
|
||||||
|
if (chamberedEntity != null)
|
||||||
|
{
|
||||||
|
_chamberContainer.Remove(chamberedEntity);
|
||||||
|
var ammoComponent = chamberedEntity.GetComponent<AmmoComponent>();
|
||||||
|
if (!ammoComponent.Caseless)
|
||||||
|
{
|
||||||
|
EjectCasing(chamberedEntity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_spawnedAmmo.TryPop(out var next))
|
||||||
|
{
|
||||||
|
_ammoContainer.Remove(next);
|
||||||
|
_chamberContainer.Insert(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_unspawnedCount > 0)
|
||||||
|
{
|
||||||
|
_unspawnedCount--;
|
||||||
|
var ammoEntity = Owner.EntityManager.SpawnEntity(_fillPrototype, Owner.Transform.GridPosition);
|
||||||
|
_chamberContainer.Insert(ammoEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (manual)
|
||||||
|
{
|
||||||
|
if (_soundCycle != null)
|
||||||
|
{
|
||||||
|
EntitySystem.Get<AudioSystem>().PlayAtCoords(_soundCycle, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dirty();
|
||||||
|
UpdateAppearance();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryInsertBullet(InteractUsingEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
if (!eventArgs.Using.TryGetComponent(out AmmoComponent ammoComponent))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ammoComponent.Caliber != _caliber)
|
||||||
|
{
|
||||||
|
Owner.PopupMessage(eventArgs.User, Loc.GetString("Wrong caliber"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_ammoContainer.ContainedEntities.Count < Capacity - 1)
|
||||||
|
{
|
||||||
|
_ammoContainer.Insert(eventArgs.Using);
|
||||||
|
_spawnedAmmo.Push(eventArgs.Using);
|
||||||
|
// Dirty();
|
||||||
|
UpdateAppearance();
|
||||||
|
if (_soundInsert != null)
|
||||||
|
{
|
||||||
|
EntitySystem.Get<AudioSystem>().PlayAtCoords(_soundInsert, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-2));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Owner.PopupMessage(eventArgs.User, Loc.GetString("No room"));
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool UseEntity(UseEntityEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
Cycle(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool InteractUsing(InteractUsingEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
return TryInsertBullet(eventArgs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,234 @@
|
|||||||
|
using System;
|
||||||
|
using Content.Server.GameObjects.Components.Sound;
|
||||||
|
using Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition;
|
||||||
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
|
using Content.Shared.GameObjects;
|
||||||
|
using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels;
|
||||||
|
using Content.Shared.Interfaces;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Server.GameObjects.Components.Container;
|
||||||
|
using Robust.Server.GameObjects.EntitySystems;
|
||||||
|
using Robust.Shared.Audio;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects.Systems;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Random;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class RevolverBarrelComponent : ServerRangedBarrelComponent
|
||||||
|
{
|
||||||
|
public override string Name => "RevolverBarrel";
|
||||||
|
private BallisticCaliber _caliber;
|
||||||
|
private Container _ammoContainer;
|
||||||
|
private int _currentSlot = 0;
|
||||||
|
public override int Capacity => _ammoSlots.Length;
|
||||||
|
private IEntity[] _ammoSlots;
|
||||||
|
|
||||||
|
public override int ShotsLeft => _ammoContainer.ContainedEntities.Count;
|
||||||
|
|
||||||
|
private AppearanceComponent _appearanceComponent;
|
||||||
|
|
||||||
|
// Sounds
|
||||||
|
private string _soundEject;
|
||||||
|
private string _soundInsert;
|
||||||
|
private string _soundSpin;
|
||||||
|
|
||||||
|
public override void ExposeData(ObjectSerializer serializer)
|
||||||
|
{
|
||||||
|
base.ExposeData(serializer);
|
||||||
|
serializer.DataField(ref _caliber, "caliber", BallisticCaliber.Unspecified);
|
||||||
|
var capacity = serializer.ReadDataField("capacity", 6);
|
||||||
|
_ammoSlots = new IEntity[capacity];
|
||||||
|
|
||||||
|
// Sounds
|
||||||
|
serializer.DataField(ref _soundEject, "soundEject", "/Audio/Guns/MagOut/revolver_magout.ogg");
|
||||||
|
serializer.DataField(ref _soundInsert, "soundInsert", "/Audio/Guns/MagIn/revolver_magin.ogg");
|
||||||
|
serializer.DataField(ref _soundSpin, "soundSpin", "/Audio/Guns/Misc/revolver_spin.ogg");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
_ammoContainer = ContainerManagerComponent.Ensure<Container>($"{Name}-ammoContainer", Owner);
|
||||||
|
|
||||||
|
if (Owner.TryGetComponent(out AppearanceComponent appearanceComponent))
|
||||||
|
{
|
||||||
|
_appearanceComponent = appearanceComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
_appearanceComponent?.SetData(MagazineBarrelVisuals.MagLoaded, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateAppearance()
|
||||||
|
{
|
||||||
|
// Placeholder, at this stage it's just here for the RPG
|
||||||
|
_appearanceComponent?.SetData(MagazineBarrelVisuals.MagLoaded, ShotsLeft > 0);
|
||||||
|
_appearanceComponent?.SetData(AmmoVisuals.AmmoCount, ShotsLeft);
|
||||||
|
_appearanceComponent?.SetData(AmmoVisuals.AmmoMax, Capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryInsertBullet(IEntity user, IEntity entity)
|
||||||
|
{
|
||||||
|
if (!entity.TryGetComponent(out AmmoComponent ammoComponent))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ammoComponent.Caliber != _caliber)
|
||||||
|
{
|
||||||
|
Owner.PopupMessage(user, Loc.GetString("Wrong caliber"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Functions like a stack
|
||||||
|
// These are inserted in reverse order but then when fired Cycle will go through in order
|
||||||
|
// The reason we don't just use an actual stack is because spin can select a random slot to point at
|
||||||
|
for (var i = _ammoSlots.Length - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
var slot = _ammoSlots[i];
|
||||||
|
if (slot == null)
|
||||||
|
{
|
||||||
|
_currentSlot = i;
|
||||||
|
_ammoSlots[i] = entity;
|
||||||
|
_ammoContainer.Insert(entity);
|
||||||
|
if (_soundInsert != null)
|
||||||
|
{
|
||||||
|
EntitySystem.Get<AudioSystem>().PlayAtCoords(_soundInsert, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-2));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dirty();
|
||||||
|
UpdateAppearance();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Owner.PopupMessage(user, Loc.GetString("Ammo full"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Cycle()
|
||||||
|
{
|
||||||
|
// Move up a slot
|
||||||
|
_currentSlot = (_currentSlot + 1) % _ammoSlots.Length;
|
||||||
|
// Dirty();
|
||||||
|
UpdateAppearance();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Russian Roulette
|
||||||
|
/// </summary>
|
||||||
|
public void Spin()
|
||||||
|
{
|
||||||
|
var random = IoCManager.Resolve<IRobustRandom>().Next(_ammoSlots.Length - 1);
|
||||||
|
_currentSlot = random;
|
||||||
|
if (_soundSpin != null)
|
||||||
|
{
|
||||||
|
EntitySystem.Get<AudioSystem>().PlayAtCoords(_soundSpin, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IEntity PeekAmmo()
|
||||||
|
{
|
||||||
|
return _ammoSlots[_currentSlot];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Takes a projectile out if possible
|
||||||
|
/// IEnumerable just to make supporting shotguns saner
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="NotImplementedException"></exception>
|
||||||
|
public override IEntity TakeProjectile()
|
||||||
|
{
|
||||||
|
var ammo = _ammoSlots[_currentSlot];
|
||||||
|
IEntity bullet = null;
|
||||||
|
if (ammo != null)
|
||||||
|
{
|
||||||
|
var ammoComponent = ammo.GetComponent<AmmoComponent>();
|
||||||
|
bullet = ammoComponent.TakeBullet();
|
||||||
|
if (ammoComponent.Caseless)
|
||||||
|
{
|
||||||
|
_ammoSlots[_currentSlot] = null;
|
||||||
|
_ammoContainer.Remove(ammo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Cycle();
|
||||||
|
UpdateAppearance();
|
||||||
|
return bullet;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EjectAllSlots()
|
||||||
|
{
|
||||||
|
for (var i = 0; i < _ammoSlots.Length; i++)
|
||||||
|
{
|
||||||
|
var entity = _ammoSlots[i];
|
||||||
|
if (entity == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ammoContainer.Remove(entity);
|
||||||
|
EjectCasing(entity);
|
||||||
|
_ammoSlots[i] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_ammoContainer.ContainedEntities.Count > 0)
|
||||||
|
{
|
||||||
|
if (_soundEject != null)
|
||||||
|
{
|
||||||
|
EntitySystem.Get<AudioSystem>().PlayAtCoords(_soundEject, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// May as well point back at the end?
|
||||||
|
_currentSlot = _ammoSlots.Length - 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Eject all casings
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="eventArgs"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="NotImplementedException"></exception>
|
||||||
|
public override bool UseEntity(UseEntityEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
EjectAllSlots();
|
||||||
|
//Dirty();
|
||||||
|
UpdateAppearance();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool InteractUsing(InteractUsingEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
return TryInsertBullet(eventArgs.User, eventArgs.Using);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Verb]
|
||||||
|
private sealed class SpinRevolverVerb : Verb<RevolverBarrelComponent>
|
||||||
|
{
|
||||||
|
protected override void GetData(IEntity user, RevolverBarrelComponent component, VerbData data)
|
||||||
|
{
|
||||||
|
data.Text = Loc.GetString("Spin");
|
||||||
|
if (component.Capacity <= 1)
|
||||||
|
{
|
||||||
|
data.Visibility = VerbVisibility.Invisible;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.Visibility = component.ShotsLeft > 0 ? VerbVisibility.Visible : VerbVisibility.Disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Activate(IEntity user, RevolverBarrelComponent component)
|
||||||
|
{
|
||||||
|
component.Spin();
|
||||||
|
component.Owner.PopupMessage(user, Loc.GetString("Spun the cylinder"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,274 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Server.GameObjects.Components.Power;
|
||||||
|
using Content.Server.GameObjects.Components.Projectiles;
|
||||||
|
using Content.Server.GameObjects.Components.Sound;
|
||||||
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
|
using Content.Shared.GameObjects;
|
||||||
|
using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Server.GameObjects.Components.Container;
|
||||||
|
using Robust.Server.GameObjects.EntitySystems;
|
||||||
|
using Robust.Shared.Audio;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects.Systems;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
using Logger = Robust.Shared.Log.Logger;
|
||||||
|
|
||||||
|
namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class ServerBatteryBarrelComponent : ServerRangedBarrelComponent
|
||||||
|
{
|
||||||
|
public override string Name => "BatteryBarrel";
|
||||||
|
|
||||||
|
// The minimum change we need before we can fire
|
||||||
|
[ViewVariables] private float _lowerChargeLimit;
|
||||||
|
[ViewVariables] private int _baseFireCost;
|
||||||
|
// What gets fired
|
||||||
|
[ViewVariables] private string _ammoPrototype;
|
||||||
|
|
||||||
|
[ViewVariables] public IEntity PowerCellEntity => _powerCellContainer.ContainedEntity;
|
||||||
|
public PowerCellComponent PowerCell => _powerCellContainer.ContainedEntity.GetComponent<PowerCellComponent>();
|
||||||
|
private ContainerSlot _powerCellContainer;
|
||||||
|
private ContainerSlot _ammoContainer;
|
||||||
|
private string _powerCellPrototype;
|
||||||
|
[ViewVariables] private bool _powerCellRemovable;
|
||||||
|
|
||||||
|
public override int ShotsLeft
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var powerCell = _powerCellContainer.ContainedEntity;
|
||||||
|
|
||||||
|
if (powerCell == null)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int) Math.Ceiling(powerCell.GetComponent<PowerCellComponent>().Charge / _baseFireCost);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int Capacity
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var powerCell = _powerCellContainer.ContainedEntity;
|
||||||
|
|
||||||
|
if (powerCell == null)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int) Math.Ceiling(powerCell.GetComponent<PowerCellComponent>().Capacity / _baseFireCost);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private AppearanceComponent _appearanceComponent;
|
||||||
|
|
||||||
|
// Sounds
|
||||||
|
private string _soundPowerCellInsert;
|
||||||
|
private string _soundPowerCellEject;
|
||||||
|
|
||||||
|
public override void ExposeData(ObjectSerializer serializer)
|
||||||
|
{
|
||||||
|
base.ExposeData(serializer);
|
||||||
|
if (serializer.Reading)
|
||||||
|
{
|
||||||
|
_powerCellPrototype = serializer.ReadDataField<string>("powerCellPrototype", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
serializer.DataField(ref _powerCellRemovable, "powerCellRemovable", false);
|
||||||
|
serializer.DataField(ref _baseFireCost, "fireCost", 300);
|
||||||
|
serializer.DataField(ref _ammoPrototype, "ammoPrototype", null);
|
||||||
|
serializer.DataField(ref _lowerChargeLimit, "lowerChargeLimit", 10);
|
||||||
|
serializer.DataField(ref _soundPowerCellInsert, "soundPowerCellInsert", null);
|
||||||
|
serializer.DataField(ref _soundPowerCellEject, "soundPowerCellEject", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
_powerCellContainer = ContainerManagerComponent.Ensure<ContainerSlot>($"{Name}-powercell-container", Owner, out var existing);
|
||||||
|
if (!existing && _powerCellPrototype != null)
|
||||||
|
{
|
||||||
|
var powerCellEntity = Owner.EntityManager.SpawnEntity(_powerCellPrototype, Owner.Transform.GridPosition);
|
||||||
|
_powerCellContainer.Insert(powerCellEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_ammoPrototype != null)
|
||||||
|
{
|
||||||
|
_ammoContainer = ContainerManagerComponent.Ensure<ContainerSlot>($"{Name}-ammo-container", Owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Owner.TryGetComponent(out AppearanceComponent appearanceComponent))
|
||||||
|
{
|
||||||
|
_appearanceComponent = appearanceComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateAppearance();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateAppearance()
|
||||||
|
{
|
||||||
|
_appearanceComponent?.SetData(MagazineBarrelVisuals.MagLoaded, _powerCellContainer.ContainedEntity != null);
|
||||||
|
_appearanceComponent?.SetData(AmmoVisuals.AmmoCount, ShotsLeft);
|
||||||
|
_appearanceComponent?.SetData(AmmoVisuals.AmmoMax, Capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IEntity PeekAmmo()
|
||||||
|
{
|
||||||
|
// Spawn a dummy entity because it's easier to work with I guess
|
||||||
|
// This will get re-used for the projectile
|
||||||
|
var ammo = _ammoContainer.ContainedEntity;
|
||||||
|
if (ammo == null)
|
||||||
|
{
|
||||||
|
ammo = Owner.EntityManager.SpawnEntity(_ammoPrototype, Owner.Transform.GridPosition);
|
||||||
|
_ammoContainer.Insert(ammo);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ammo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IEntity TakeProjectile()
|
||||||
|
{
|
||||||
|
var powerCellEntity = _powerCellContainer.ContainedEntity;
|
||||||
|
|
||||||
|
if (powerCellEntity == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var capacitor = powerCellEntity.GetComponent<PowerCellComponent>();
|
||||||
|
if (capacitor.Charge < _lowerChargeLimit)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can fire confirmed
|
||||||
|
// Multiply the entity's damage / whatever by the percentage of charge the shot has.
|
||||||
|
IEntity entity;
|
||||||
|
var chargeChange = Math.Min(capacitor.Charge, _baseFireCost);
|
||||||
|
capacitor.DeductCharge(chargeChange);
|
||||||
|
var energyRatio = chargeChange / _baseFireCost;
|
||||||
|
|
||||||
|
if (_ammoContainer.ContainedEntity != null)
|
||||||
|
{
|
||||||
|
entity = _ammoContainer.ContainedEntity;
|
||||||
|
_ammoContainer.Remove(entity);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
entity = Owner.EntityManager.SpawnEntity(_ammoPrototype, Owner.Transform.GridPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entity.TryGetComponent(out ProjectileComponent projectileComponent))
|
||||||
|
{
|
||||||
|
if (energyRatio < 1.0)
|
||||||
|
{
|
||||||
|
var newDamages = new Dictionary<DamageType, int>(projectileComponent.Damages);
|
||||||
|
foreach (var (damageType, damage) in projectileComponent.Damages)
|
||||||
|
{
|
||||||
|
newDamages.Add(damageType, (int) (damage * energyRatio));
|
||||||
|
}
|
||||||
|
|
||||||
|
projectileComponent.Damages = newDamages;
|
||||||
|
}
|
||||||
|
} else if (entity.TryGetComponent(out HitscanComponent hitscanComponent))
|
||||||
|
{
|
||||||
|
hitscanComponent.Damage *= energyRatio;
|
||||||
|
hitscanComponent.ColorModifier = energyRatio;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Ammo doesn't have hitscan or projectile?");
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateAppearance();
|
||||||
|
//Dirty();
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryInsertPowerCell(IEntity entity)
|
||||||
|
{
|
||||||
|
if (_powerCellContainer.ContainedEntity != null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!entity.HasComponent<PowerCellComponent>())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_soundPowerCellInsert != null)
|
||||||
|
{
|
||||||
|
EntitySystem.Get<AudioSystem>().PlayAtCoords(_soundPowerCellInsert, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-2));
|
||||||
|
}
|
||||||
|
|
||||||
|
_powerCellContainer.Insert(entity);
|
||||||
|
UpdateAppearance();
|
||||||
|
//Dirty();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEntity RemovePowerCell()
|
||||||
|
{
|
||||||
|
if (!_powerCellRemovable || _powerCellContainer.ContainedEntity == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var entity = _powerCellContainer.ContainedEntity;
|
||||||
|
_powerCellContainer.Remove(entity);
|
||||||
|
if (_soundPowerCellEject != null)
|
||||||
|
{
|
||||||
|
EntitySystem.Get<AudioSystem>().PlayAtCoords(_soundPowerCellEject, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-2));
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateAppearance();
|
||||||
|
//Dirty();
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool UseEntity(UseEntityEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
if (!_powerCellRemovable)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!eventArgs.User.TryGetComponent(out HandsComponent handsComponent) ||
|
||||||
|
PowerCellEntity == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var itemComponent = PowerCellEntity.GetComponent<ItemComponent>();
|
||||||
|
if (!handsComponent.CanPutInHand(itemComponent))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var powerCell = RemovePowerCell();
|
||||||
|
handsComponent.PutInHand(itemComponent);
|
||||||
|
powerCell.Transform.GridPosition = eventArgs.User.Transform.GridPosition;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool InteractUsing(InteractUsingEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
if (!eventArgs.Using.HasComponent<PowerStorageComponent>())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TryInsertPowerCell(eventArgs.Using);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,457 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition;
|
||||||
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
|
using Content.Shared.GameObjects;
|
||||||
|
using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels;
|
||||||
|
using Content.Shared.Interfaces;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Server.GameObjects.Components.Container;
|
||||||
|
using Robust.Server.GameObjects.EntitySystems;
|
||||||
|
using Robust.Shared.Audio;
|
||||||
|
using Robust.Shared.Containers;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects.Systems;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
|
namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class ServerMagazineBarrelComponent : ServerRangedBarrelComponent
|
||||||
|
{
|
||||||
|
public override string Name => "MagazineBarrel";
|
||||||
|
public override uint? NetID => ContentNetIDs.MAGAZINE_BARREL;
|
||||||
|
|
||||||
|
private ContainerSlot _chamberContainer;
|
||||||
|
[ViewVariables] public bool HasMagazine => _magazineContainer.ContainedEntity != null;
|
||||||
|
private ContainerSlot _magazineContainer;
|
||||||
|
|
||||||
|
[ViewVariables] public MagazineType MagazineTypes => _magazineTypes;
|
||||||
|
private MagazineType _magazineTypes;
|
||||||
|
[ViewVariables] public BallisticCaliber Caliber => _caliber;
|
||||||
|
private BallisticCaliber _caliber;
|
||||||
|
|
||||||
|
public override int ShotsLeft
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var count = 0;
|
||||||
|
if (_chamberContainer.ContainedEntity != null)
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
var magazine = _magazineContainer.ContainedEntity;
|
||||||
|
if (magazine != null)
|
||||||
|
{
|
||||||
|
count += magazine.GetComponent<RangedMagazineComponent>().ShotsLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int Capacity
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
// Chamber
|
||||||
|
var count = 1;
|
||||||
|
var magazine = _magazineContainer.ContainedEntity;
|
||||||
|
if (magazine != null)
|
||||||
|
{
|
||||||
|
count += magazine.GetComponent<RangedMagazineComponent>().Capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool BoltOpen { get; private set; } = true;
|
||||||
|
private bool _autoEjectMag;
|
||||||
|
// If the bolt needs to be open before we can insert / remove the mag (i.e. for LMGs)
|
||||||
|
public bool MagNeedsOpenBolt => _magNeedsOpenBolt;
|
||||||
|
private bool _magNeedsOpenBolt;
|
||||||
|
|
||||||
|
private AppearanceComponent _appearanceComponent;
|
||||||
|
|
||||||
|
// Sounds
|
||||||
|
private string _soundBoltOpen;
|
||||||
|
private string _soundBoltClosed;
|
||||||
|
private string _soundRack;
|
||||||
|
private string _soundMagInsert;
|
||||||
|
private string _soundMagEject;
|
||||||
|
private string _soundAutoEject;
|
||||||
|
|
||||||
|
public override void ExposeData(ObjectSerializer serializer)
|
||||||
|
{
|
||||||
|
base.ExposeData(serializer);
|
||||||
|
if (serializer.Reading)
|
||||||
|
{
|
||||||
|
var magTypes = serializer.ReadDataField("magazineTypes", new List<MagazineType>());
|
||||||
|
foreach (var mag in magTypes)
|
||||||
|
{
|
||||||
|
_magazineTypes |= mag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
serializer.DataField(ref _caliber, "caliber", BallisticCaliber.Unspecified);
|
||||||
|
serializer.DataField(ref _autoEjectMag, "autoEjectMag", false);
|
||||||
|
serializer.DataField(ref _magNeedsOpenBolt, "magNeedsOpenBolt", false);
|
||||||
|
serializer.DataField(ref _soundBoltOpen, "soundBoltOpen", null);
|
||||||
|
serializer.DataField(ref _soundBoltClosed, "soundBoltClosed", null);
|
||||||
|
serializer.DataField(ref _soundRack, "soundRack", null);
|
||||||
|
serializer.DataField(ref _soundMagInsert, "soundMagInsert", null);
|
||||||
|
serializer.DataField(ref _soundMagEject, "soundMagEject", null);
|
||||||
|
serializer.DataField(ref _soundAutoEject, "soundAutoEject", "/Audio/Guns/EmptyAlarm/smg_empty_alarm.ogg");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ComponentState GetComponentState()
|
||||||
|
{
|
||||||
|
(int, int)? count = null;
|
||||||
|
var magazine = _magazineContainer.ContainedEntity;
|
||||||
|
if (magazine != null && magazine.TryGetComponent(out RangedMagazineComponent rangedMagazineComponent))
|
||||||
|
{
|
||||||
|
count = (rangedMagazineComponent.ShotsLeft, rangedMagazineComponent.Capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MagazineBarrelComponentState(
|
||||||
|
_chamberContainer.ContainedEntity != null,
|
||||||
|
FireRateSelector,
|
||||||
|
count,
|
||||||
|
SoundGunshot);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
if (Owner.TryGetComponent(out AppearanceComponent appearanceComponent))
|
||||||
|
{
|
||||||
|
_appearanceComponent = appearanceComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
_chamberContainer = ContainerManagerComponent.Ensure<ContainerSlot>($"{Name}-chamber", Owner);
|
||||||
|
_magazineContainer = ContainerManagerComponent.Ensure<ContainerSlot>($"{Name}-magazine", Owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ToggleBolt()
|
||||||
|
{
|
||||||
|
// For magazines only when we normally set BoltOpen we'll defer the UpdateAppearance until everything is done
|
||||||
|
// Whereas this will just call it straight up.
|
||||||
|
BoltOpen = !BoltOpen;
|
||||||
|
var soundSystem = EntitySystem.Get<AudioSystem>();
|
||||||
|
if (BoltOpen)
|
||||||
|
{
|
||||||
|
if (_soundBoltOpen != null)
|
||||||
|
{
|
||||||
|
soundSystem.PlayAtCoords(_soundBoltOpen, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-5));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_soundBoltClosed != null)
|
||||||
|
{
|
||||||
|
soundSystem.PlayAtCoords(_soundBoltClosed, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-5));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Dirty();
|
||||||
|
UpdateAppearance();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IEntity PeekAmmo()
|
||||||
|
{
|
||||||
|
return BoltOpen ? null : _chamberContainer.ContainedEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IEntity TakeProjectile()
|
||||||
|
{
|
||||||
|
if (BoltOpen)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var entity = _chamberContainer.ContainedEntity;
|
||||||
|
|
||||||
|
Cycle();
|
||||||
|
return entity?.GetComponent<AmmoComponent>().TakeBullet();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Cycle(bool manual = false)
|
||||||
|
{
|
||||||
|
if (BoltOpen)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var chamberEntity = _chamberContainer.ContainedEntity;
|
||||||
|
if (chamberEntity != null)
|
||||||
|
{
|
||||||
|
_chamberContainer.Remove(chamberEntity);
|
||||||
|
var ammoComponent = chamberEntity.GetComponent<AmmoComponent>();
|
||||||
|
if (!ammoComponent.Caseless)
|
||||||
|
{
|
||||||
|
EjectCasing(chamberEntity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try and pull a round from the magazine to replace the chamber if possible
|
||||||
|
var magazine = _magazineContainer.ContainedEntity;
|
||||||
|
var nextRound = magazine?.GetComponent<RangedMagazineComponent>().TakeAmmo();
|
||||||
|
|
||||||
|
if (nextRound != null)
|
||||||
|
{
|
||||||
|
// If you're really into gunporn you could put a sound here
|
||||||
|
_chamberContainer.Insert(nextRound);
|
||||||
|
}
|
||||||
|
|
||||||
|
var soundSystem = EntitySystem.Get<AudioSystem>();
|
||||||
|
|
||||||
|
if (_autoEjectMag && magazine != null && magazine.GetComponent<RangedMagazineComponent>().ShotsLeft == 0)
|
||||||
|
{
|
||||||
|
if (_soundAutoEject != null)
|
||||||
|
{
|
||||||
|
soundSystem.PlayAtCoords(_soundAutoEject, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-2));
|
||||||
|
}
|
||||||
|
|
||||||
|
_magazineContainer.Remove(magazine);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextRound == null && !BoltOpen)
|
||||||
|
{
|
||||||
|
if (_soundBoltOpen != null)
|
||||||
|
{
|
||||||
|
soundSystem.PlayAtCoords(_soundBoltOpen, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-5));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ContainerHelpers.TryGetContainer(Owner, out var container))
|
||||||
|
{
|
||||||
|
Owner.PopupMessage(container.Owner, Loc.GetString("Bolt open"));
|
||||||
|
}
|
||||||
|
BoltOpen = true;
|
||||||
|
Dirty();
|
||||||
|
UpdateAppearance();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (manual)
|
||||||
|
{
|
||||||
|
if (_soundRack != null)
|
||||||
|
{
|
||||||
|
soundSystem.PlayAtCoords(_soundRack, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Dirty();
|
||||||
|
UpdateAppearance();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateAppearance()
|
||||||
|
{
|
||||||
|
_appearanceComponent?.SetData(BarrelBoltVisuals.BoltOpen, BoltOpen);
|
||||||
|
_appearanceComponent?.SetData(MagazineBarrelVisuals.MagLoaded, _magazineContainer.ContainedEntity != null);
|
||||||
|
_appearanceComponent?.SetData(AmmoVisuals.AmmoCount, ShotsLeft);
|
||||||
|
_appearanceComponent?.SetData(AmmoVisuals.AmmoMax, Capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool UseEntity(UseEntityEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
// Behavior:
|
||||||
|
// If bolt open just close it
|
||||||
|
// If bolt closed then cycle
|
||||||
|
// If we cycle then get next round
|
||||||
|
// If no more round then open bolt
|
||||||
|
|
||||||
|
if (BoltOpen)
|
||||||
|
{
|
||||||
|
if (_soundBoltClosed != null)
|
||||||
|
{
|
||||||
|
EntitySystem.Get<AudioSystem>().PlayAtCoords(_soundBoltClosed, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-5));
|
||||||
|
}
|
||||||
|
Owner.PopupMessage(eventArgs.User, Loc.GetString("Bolt closed"));
|
||||||
|
BoltOpen = false;
|
||||||
|
Dirty();
|
||||||
|
UpdateAppearance();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Could play a rack-slide specific sound here if you're so inclined (if the chamber is empty but rounds are available)
|
||||||
|
|
||||||
|
Cycle(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveMagazine(IEntity user)
|
||||||
|
{
|
||||||
|
var mag = _magazineContainer.ContainedEntity;
|
||||||
|
|
||||||
|
if (mag == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MagNeedsOpenBolt && !BoltOpen)
|
||||||
|
{
|
||||||
|
Owner.PopupMessage(user, Loc.GetString("Bolt needs to be open"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_magazineContainer.Remove(mag);
|
||||||
|
if (_soundMagEject != null)
|
||||||
|
{
|
||||||
|
EntitySystem.Get<AudioSystem>().PlayAtCoords(_soundMagEject, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-2));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user.TryGetComponent(out HandsComponent handsComponent))
|
||||||
|
{
|
||||||
|
handsComponent.PutInHandOrDrop(mag.GetComponent<ItemComponent>());
|
||||||
|
}
|
||||||
|
|
||||||
|
Dirty();
|
||||||
|
UpdateAppearance();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool InteractUsing(InteractUsingEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
// Insert magazine
|
||||||
|
if (eventArgs.Using.TryGetComponent(out RangedMagazineComponent magazineComponent))
|
||||||
|
{
|
||||||
|
if ((MagazineTypes & magazineComponent.MagazineType) == 0)
|
||||||
|
{
|
||||||
|
Owner.PopupMessage(eventArgs.User, Loc.GetString("Wrong magazine type"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (magazineComponent.Caliber != _caliber)
|
||||||
|
{
|
||||||
|
Owner.PopupMessage(eventArgs.User, Loc.GetString("Wrong caliber"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_magNeedsOpenBolt && !BoltOpen)
|
||||||
|
{
|
||||||
|
Owner.PopupMessage(eventArgs.User, Loc.GetString("Need to open bolt first"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_magazineContainer.ContainedEntity == null)
|
||||||
|
{
|
||||||
|
if (_soundMagInsert != null)
|
||||||
|
{
|
||||||
|
EntitySystem.Get<AudioSystem>().PlayAtCoords(_soundMagInsert, Owner.Transform.GridPosition, AudioParams.Default.WithVolume(-2));
|
||||||
|
}
|
||||||
|
Owner.PopupMessage(eventArgs.User, Loc.GetString("Magazine inserted"));
|
||||||
|
_magazineContainer.Insert(eventArgs.Using);
|
||||||
|
Dirty();
|
||||||
|
UpdateAppearance();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Owner.PopupMessage(eventArgs.User, Loc.GetString("Already holding a magazine"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert 1 ammo
|
||||||
|
if (eventArgs.Using.TryGetComponent(out AmmoComponent ammoComponent))
|
||||||
|
{
|
||||||
|
if (!BoltOpen)
|
||||||
|
{
|
||||||
|
Owner.PopupMessage(eventArgs.User, Loc.GetString("Cannot insert ammo while bolt is closed"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ammoComponent.Caliber != _caliber)
|
||||||
|
{
|
||||||
|
Owner.PopupMessage(eventArgs.User, Loc.GetString("Wrong caliber"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_chamberContainer.ContainedEntity == null)
|
||||||
|
{
|
||||||
|
Owner.PopupMessage(eventArgs.User, Loc.GetString("Ammo inserted"));
|
||||||
|
_chamberContainer.Insert(eventArgs.Using);
|
||||||
|
Dirty();
|
||||||
|
UpdateAppearance();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Owner.PopupMessage(eventArgs.User, Loc.GetString("Chamber full"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Verb]
|
||||||
|
private sealed class EjectMagazineVerb : Verb<ServerMagazineBarrelComponent>
|
||||||
|
{
|
||||||
|
protected override void GetData(IEntity user, ServerMagazineBarrelComponent component, VerbData data)
|
||||||
|
{
|
||||||
|
data.Text = Loc.GetString("Eject magazine");
|
||||||
|
if (component.MagNeedsOpenBolt)
|
||||||
|
{
|
||||||
|
data.Visibility = component.HasMagazine && component.BoltOpen
|
||||||
|
? VerbVisibility.Visible
|
||||||
|
: VerbVisibility.Disabled;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.Visibility = component.HasMagazine ? VerbVisibility.Visible : VerbVisibility.Disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Activate(IEntity user, ServerMagazineBarrelComponent component)
|
||||||
|
{
|
||||||
|
component.RemoveMagazine(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Verb]
|
||||||
|
private sealed class OpenBoltVerb : Verb<ServerMagazineBarrelComponent>
|
||||||
|
{
|
||||||
|
protected override void GetData(IEntity user, ServerMagazineBarrelComponent component, VerbData data)
|
||||||
|
{
|
||||||
|
data.Text = Loc.GetString("Open bolt");
|
||||||
|
data.Visibility = component.BoltOpen ? VerbVisibility.Disabled : VerbVisibility.Visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Activate(IEntity user, ServerMagazineBarrelComponent component)
|
||||||
|
{
|
||||||
|
component.ToggleBolt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Verb]
|
||||||
|
private sealed class CloseBoltVerb : Verb<ServerMagazineBarrelComponent>
|
||||||
|
{
|
||||||
|
protected override void GetData(IEntity user, ServerMagazineBarrelComponent component, VerbData data)
|
||||||
|
{
|
||||||
|
data.Text = Loc.GetString("Close bolt");
|
||||||
|
data.Visibility = component.BoltOpen ? VerbVisibility.Visible : VerbVisibility.Disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Activate(IEntity user, ServerMagazineBarrelComponent component)
|
||||||
|
{
|
||||||
|
component.ToggleBolt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum MagazineType
|
||||||
|
{
|
||||||
|
|
||||||
|
Unspecified = 0,
|
||||||
|
LPistol = 1 << 0, // Placeholder?
|
||||||
|
Pistol = 1 << 1,
|
||||||
|
HCPistol = 1 << 2,
|
||||||
|
Smg = 1 << 3,
|
||||||
|
SmgTopMounted = 1 << 4,
|
||||||
|
Rifle = 1 << 5,
|
||||||
|
IH = 1 << 6, // Placeholder?
|
||||||
|
Box = 1 << 7,
|
||||||
|
Pan = 1 << 8,
|
||||||
|
Dart = 1 << 9, // Placeholder
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,415 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Content.Server.GameObjects.Components.Mobs;
|
||||||
|
using Content.Server.GameObjects.Components.Projectiles;
|
||||||
|
using Content.Server.GameObjects.Components.Sound;
|
||||||
|
using Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition;
|
||||||
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
|
using Content.Shared.Audio;
|
||||||
|
using Content.Shared.GameObjects.Components.Weapons.Ranged;
|
||||||
|
using Content.Shared.Physics;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Server.GameObjects.EntitySystems;
|
||||||
|
using Robust.Shared.Audio;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects.EntitySystemMessages;
|
||||||
|
using Robust.Shared.GameObjects.Systems;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Map;
|
||||||
|
using Robust.Shared.Interfaces.Physics;
|
||||||
|
using Robust.Shared.Interfaces.Random;
|
||||||
|
using Robust.Shared.Interfaces.Timing;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Log;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using Robust.Shared.Physics;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
namespace Content.Server.GameObjects.Components.Weapon.Ranged.Barrels
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// All of the ranged weapon components inherit from this to share mechanics like shooting etc.
|
||||||
|
/// Only difference between them is how they retrieve a projectile to shoot (battery, magazine, etc.)
|
||||||
|
/// </summary>
|
||||||
|
public abstract class ServerRangedBarrelComponent : SharedRangedBarrelComponent, IUse, IInteractUsing
|
||||||
|
{
|
||||||
|
// There's still some of py01 and PJB's work left over, especially in underlying shooting logic,
|
||||||
|
// it's just when I re-organised it changed me as the contributor
|
||||||
|
#pragma warning disable 649
|
||||||
|
[Dependency] private IGameTiming _gameTiming;
|
||||||
|
[Dependency] private IRobustRandom _robustRandom;
|
||||||
|
#pragma warning restore 649
|
||||||
|
|
||||||
|
public override FireRateSelector FireRateSelector => _fireRateSelector;
|
||||||
|
private FireRateSelector _fireRateSelector;
|
||||||
|
public override FireRateSelector AllRateSelectors => _fireRateSelector;
|
||||||
|
private FireRateSelector _allRateSelectors;
|
||||||
|
public override float FireRate => _fireRate;
|
||||||
|
private float _fireRate;
|
||||||
|
|
||||||
|
// _lastFire is when we actually fired (so if we hold the button then recoil doesn't build up if we're not firing)
|
||||||
|
private TimeSpan _lastFire;
|
||||||
|
|
||||||
|
public abstract IEntity PeekAmmo();
|
||||||
|
public abstract IEntity TakeProjectile();
|
||||||
|
|
||||||
|
// Recoil / spray control
|
||||||
|
private Angle _minAngle;
|
||||||
|
private Angle _maxAngle;
|
||||||
|
private Angle _currentAngle = Angle.Zero;
|
||||||
|
/// <summary>
|
||||||
|
/// How slowly the angle's theta decays per second in radians
|
||||||
|
/// </summary>
|
||||||
|
private float _angleDecay;
|
||||||
|
/// <summary>
|
||||||
|
/// How quickly the angle's theta builds for every shot fired in radians
|
||||||
|
/// </summary>
|
||||||
|
private float _angleIncrease;
|
||||||
|
// Multiplies the ammo spread to get the final spread of each pellet
|
||||||
|
private float _spreadRatio;
|
||||||
|
|
||||||
|
public bool CanMuzzleFlash => _canMuzzleFlash;
|
||||||
|
private bool _canMuzzleFlash = true;
|
||||||
|
|
||||||
|
// Sounds
|
||||||
|
public string SoundGunshot
|
||||||
|
{
|
||||||
|
get => _soundGunshot;
|
||||||
|
set => _soundGunshot = value;
|
||||||
|
}
|
||||||
|
private string _soundGunshot;
|
||||||
|
public string SoundEmpty => _soundEmpty;
|
||||||
|
private string _soundEmpty;
|
||||||
|
|
||||||
|
public override void ExposeData(ObjectSerializer serializer)
|
||||||
|
{
|
||||||
|
base.ExposeData(serializer);
|
||||||
|
serializer.DataField(ref _fireRateSelector, "currentSelector", FireRateSelector.Safety);
|
||||||
|
serializer.DataField(ref _fireRate, "fireRate", 2.0f);
|
||||||
|
|
||||||
|
// This hard-to-read area's dealing with recoil
|
||||||
|
// Use degrees in yaml as it's easier to read compared to "0.0125f"
|
||||||
|
if (serializer.Reading)
|
||||||
|
{
|
||||||
|
var minAngle = serializer.ReadDataField("minAngle", 0) / 2;
|
||||||
|
_minAngle = Angle.FromDegrees(minAngle);
|
||||||
|
// Random doubles it as it's +/- so uhh we'll just half it here for readability
|
||||||
|
var maxAngle = serializer.ReadDataField("maxAngle", 45) / 2;
|
||||||
|
_maxAngle = Angle.FromDegrees(maxAngle);
|
||||||
|
var angleIncrease = serializer.ReadDataField("angleIncrease", (40 / _fireRate));
|
||||||
|
_angleIncrease = angleIncrease * (float) Math.PI / 180;
|
||||||
|
var angleDecay = serializer.ReadDataField("angleDecay", (float) 20);
|
||||||
|
_angleDecay = angleDecay * (float) Math.PI / 180;
|
||||||
|
serializer.DataField(ref _spreadRatio, "ammoSpreadRatio", 1.0f);
|
||||||
|
|
||||||
|
// FireRate options
|
||||||
|
var allFireRates = serializer.ReadDataField("allSelectors", new List<FireRateSelector>());
|
||||||
|
foreach (var fireRate in allFireRates)
|
||||||
|
{
|
||||||
|
_allRateSelectors |= fireRate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For simplicity we'll enforce it this way; ammo determines max spread
|
||||||
|
if (_spreadRatio > 1.0f)
|
||||||
|
{
|
||||||
|
Logger.Error("SpreadRatio must be <= 1.0f for guns");
|
||||||
|
throw new InvalidOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
serializer.DataField(ref _canMuzzleFlash, "canMuzzleFlash", true);
|
||||||
|
// Sounds
|
||||||
|
serializer.DataField(ref _soundGunshot, "soundGunshot", null);
|
||||||
|
serializer.DataField(ref _soundEmpty, "soundEmpty", "/Audio/Guns/Empty/empty.ogg");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnAdd()
|
||||||
|
{
|
||||||
|
base.OnAdd();
|
||||||
|
var rangedWeapon = Owner.GetComponent<ServerRangedWeaponComponent>();
|
||||||
|
rangedWeapon.Barrel = this;
|
||||||
|
rangedWeapon.FireHandler += Fire;
|
||||||
|
rangedWeapon.WeaponCanFireHandler += WeaponCanFire;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnRemove()
|
||||||
|
{
|
||||||
|
base.OnRemove();
|
||||||
|
var rangedWeapon = Owner.GetComponent<ServerRangedWeaponComponent>();
|
||||||
|
rangedWeapon.Barrel = null;
|
||||||
|
rangedWeapon.FireHandler -= Fire;
|
||||||
|
rangedWeapon.WeaponCanFireHandler -= WeaponCanFire;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Angle GetRecoilAngle(Angle direction)
|
||||||
|
{
|
||||||
|
var currentTime = _gameTiming.CurTime;
|
||||||
|
var timeSinceLastFire = (currentTime - _lastFire).TotalSeconds;
|
||||||
|
var newTheta = Math.Clamp(_currentAngle.Theta + _angleIncrease - _angleDecay * timeSinceLastFire, _minAngle.Theta, _maxAngle.Theta);
|
||||||
|
_currentAngle = new Angle(newTheta);
|
||||||
|
|
||||||
|
var random = (_robustRandom.NextDouble() - 0.5) * 2;
|
||||||
|
var angle = Angle.FromDegrees(direction.Degrees + _currentAngle.Degrees * random);
|
||||||
|
return angle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract bool UseEntity(UseEntityEventArgs eventArgs);
|
||||||
|
public abstract bool InteractUsing(InteractUsingEventArgs eventArgs);
|
||||||
|
|
||||||
|
public void ChangeFireSelector(FireRateSelector rateSelector)
|
||||||
|
{
|
||||||
|
if ((rateSelector & AllRateSelectors) != 0)
|
||||||
|
{
|
||||||
|
_fireRateSelector = rateSelector;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual bool WeaponCanFire()
|
||||||
|
{
|
||||||
|
// If the ServerRangedWeaponComponent gets re-done probably need to add the checks here
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Fire(IEntity shooter, GridCoordinates target)
|
||||||
|
{
|
||||||
|
var soundSystem = EntitySystem.Get<AudioSystem>();
|
||||||
|
if (ShotsLeft == 0)
|
||||||
|
{
|
||||||
|
if (_soundEmpty != null)
|
||||||
|
{
|
||||||
|
soundSystem.PlayAtCoords(_soundEmpty, Owner.Transform.GridPosition);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ammo = PeekAmmo();
|
||||||
|
var projectile = TakeProjectile();
|
||||||
|
if (projectile == null)
|
||||||
|
{
|
||||||
|
soundSystem.PlayAtCoords(_soundEmpty, Owner.Transform.GridPosition);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point firing is confirmed
|
||||||
|
var worldPosition = IoCManager.Resolve<IMapManager>().GetGrid(target.GridID).LocalToWorld(target).Position;
|
||||||
|
var direction = (worldPosition - shooter.Transform.WorldPosition).ToAngle();
|
||||||
|
var angle = GetRecoilAngle(direction);
|
||||||
|
// This should really be client-side but for now we'll just leave it here
|
||||||
|
if (shooter.TryGetComponent(out CameraRecoilComponent recoilComponent))
|
||||||
|
{
|
||||||
|
recoilComponent.Kick(-angle.ToVec() * 0.15f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This section probably needs tweaking so there can be caseless hitscan etc.
|
||||||
|
if (projectile.TryGetComponent(out HitscanComponent hitscan))
|
||||||
|
{
|
||||||
|
FireHitscan(shooter, hitscan, angle);
|
||||||
|
}
|
||||||
|
else if (projectile.HasComponent<ProjectileComponent>())
|
||||||
|
{
|
||||||
|
var ammoComponent = ammo.GetComponent<AmmoComponent>();
|
||||||
|
|
||||||
|
FireProjectiles(shooter, projectile, ammoComponent.ProjectilesFired, ammoComponent.EvenSpreadAngle, angle, ammoComponent.Velocity);
|
||||||
|
|
||||||
|
if (CanMuzzleFlash)
|
||||||
|
{
|
||||||
|
ammoComponent.MuzzleFlash(Owner.Transform.GridPosition, angle);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ammoComponent.Caseless)
|
||||||
|
{
|
||||||
|
ammo.Delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Invalid types
|
||||||
|
throw new InvalidOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
soundSystem.PlayAtCoords(_soundGunshot, Owner.Transform.GridPosition);
|
||||||
|
_lastFire = _gameTiming.CurTime;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Drops a single cartridge / shell
|
||||||
|
/// Made as a static function just because multiple places need it
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entity"></param>
|
||||||
|
/// <param name="playSound"></param>
|
||||||
|
/// <param name="robustRandom"></param>
|
||||||
|
/// <param name="prototypeManager"></param>
|
||||||
|
/// <param name="ejectDirections"></param>
|
||||||
|
public static void EjectCasing(
|
||||||
|
IEntity entity,
|
||||||
|
bool playSound = true,
|
||||||
|
IRobustRandom robustRandom = null,
|
||||||
|
IPrototypeManager prototypeManager = null,
|
||||||
|
Direction[] ejectDirections = null)
|
||||||
|
{
|
||||||
|
if (robustRandom == null)
|
||||||
|
{
|
||||||
|
robustRandom = IoCManager.Resolve<IRobustRandom>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ejectDirections == null)
|
||||||
|
{
|
||||||
|
ejectDirections = new[] {Direction.East, Direction.North, Direction.South, Direction.West};
|
||||||
|
}
|
||||||
|
|
||||||
|
const float ejectOffset = 0.2f;
|
||||||
|
var ammo = entity.GetComponent<AmmoComponent>();
|
||||||
|
var offsetPos = (robustRandom.NextFloat() * ejectOffset, robustRandom.NextFloat() * ejectOffset);
|
||||||
|
entity.Transform.GridPosition = entity.Transform.GridPosition.Offset(offsetPos);
|
||||||
|
entity.Transform.LocalRotation = robustRandom.Pick(ejectDirections).ToAngle();
|
||||||
|
|
||||||
|
if (ammo.SoundCollectionEject == null || !playSound)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prototypeManager == null)
|
||||||
|
{
|
||||||
|
prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var soundCollection = prototypeManager.Index<SoundCollectionPrototype>(ammo.SoundCollectionEject);
|
||||||
|
var randomFile = robustRandom.Pick(soundCollection.PickFiles);
|
||||||
|
EntitySystem.Get<AudioSystem>().PlayAtCoords(randomFile, entity.Transform.GridPosition, AudioParams.Default.WithVolume(-1));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Drops multiple cartridges / shells on the floor
|
||||||
|
/// Wraps EjectCasing to make it less toxic for bulk ejections
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entities"></param>
|
||||||
|
public static void EjectCasings(IEnumerable<IEntity> entities)
|
||||||
|
{
|
||||||
|
var robustRandom = IoCManager.Resolve<IRobustRandom>();
|
||||||
|
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||||
|
var ejectDirections = new[] {Direction.East, Direction.North, Direction.South, Direction.West};
|
||||||
|
var soundPlayCount = 0;
|
||||||
|
var playSound = true;
|
||||||
|
|
||||||
|
foreach (var entity in entities)
|
||||||
|
{
|
||||||
|
EjectCasing(entity, playSound, robustRandom, prototypeManager, ejectDirections);
|
||||||
|
soundPlayCount++;
|
||||||
|
if (soundPlayCount > 3)
|
||||||
|
{
|
||||||
|
playSound = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Firing
|
||||||
|
/// <summary>
|
||||||
|
/// Handles firing one or many projectiles
|
||||||
|
/// </summary>
|
||||||
|
private void FireProjectiles(IEntity shooter, IEntity baseProjectile, int count, float evenSpreadAngle, Angle angle, float velocity)
|
||||||
|
{
|
||||||
|
List<Angle> sprayAngleChange = null;
|
||||||
|
if (count > 1)
|
||||||
|
{
|
||||||
|
evenSpreadAngle *= _spreadRatio;
|
||||||
|
sprayAngleChange = Linspace(-evenSpreadAngle / 2, evenSpreadAngle / 2, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
IEntity projectile;
|
||||||
|
|
||||||
|
if (i == 0)
|
||||||
|
{
|
||||||
|
projectile = baseProjectile;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
projectile =
|
||||||
|
Owner.EntityManager.SpawnEntity(baseProjectile.Prototype.ID, Owner.Transform.GridPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
Angle projectileAngle;
|
||||||
|
|
||||||
|
if (sprayAngleChange != null)
|
||||||
|
{
|
||||||
|
projectileAngle = angle + sprayAngleChange[i];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
projectileAngle = angle;
|
||||||
|
}
|
||||||
|
|
||||||
|
var physicsComponent = projectile.GetComponent<PhysicsComponent>();
|
||||||
|
physicsComponent.Status = BodyStatus.InAir;
|
||||||
|
projectile.Transform.GridPosition = Owner.Transform.GridPosition;
|
||||||
|
|
||||||
|
var projectileComponent = projectile.GetComponent<ProjectileComponent>();
|
||||||
|
projectileComponent.IgnoreEntity(shooter);
|
||||||
|
projectile.GetComponent<PhysicsComponent>().LinearVelocity = projectileAngle.ToVec() * velocity;
|
||||||
|
projectile.Transform.LocalRotation = projectileAngle.Theta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a list of numbers that form a set of equal intervals between the start and end value. Used to calculate shotgun spread angles.
|
||||||
|
/// </summary>
|
||||||
|
private List<Angle> Linspace(double start, double end, int intervals)
|
||||||
|
{
|
||||||
|
DebugTools.Assert(intervals > 1);
|
||||||
|
|
||||||
|
var linspace = new List<Angle>(intervals);
|
||||||
|
|
||||||
|
for (var i = 0; i <= intervals - 1; i++)
|
||||||
|
{
|
||||||
|
linspace.Add(Angle.FromDegrees(start + (end - start) * i / (intervals - 1)));
|
||||||
|
}
|
||||||
|
return linspace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fires hitscan entities and then displays their effects
|
||||||
|
/// </summary>
|
||||||
|
private void FireHitscan(IEntity shooter, HitscanComponent hitscan, Angle angle)
|
||||||
|
{
|
||||||
|
var ray = new CollisionRay(Owner.Transform.GridPosition.Position, angle.ToVec(), (int) hitscan.CollisionMask);
|
||||||
|
var physicsManager = IoCManager.Resolve<IPhysicsManager>();
|
||||||
|
var rayCastResults = physicsManager.IntersectRay(Owner.Transform.MapID, ray, hitscan.MaxLength, shooter, false).ToList();
|
||||||
|
|
||||||
|
if (rayCastResults.Count >= 1)
|
||||||
|
{
|
||||||
|
var result = rayCastResults[0];
|
||||||
|
var distance = result.HitEntity != null ? result.Distance : hitscan.MaxLength;
|
||||||
|
hitscan.FireEffects(shooter, distance, angle, result.HitEntity);
|
||||||
|
|
||||||
|
if (result.HitEntity == null || !result.HitEntity.TryGetComponent(out DamageableComponent damageable))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
damageable.TakeDamage(
|
||||||
|
hitscan.DamageType,
|
||||||
|
(int)Math.Round(hitscan.Damage, MidpointRounding.AwayFromZero),
|
||||||
|
Owner,
|
||||||
|
shooter);
|
||||||
|
//I used Math.Round over Convert.toInt32, as toInt32 always rounds to
|
||||||
|
//even numbers if halfway between two numbers, rather than rounding to nearest
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
hitscan.FireEffects(shooter, hitscan.MaxLength, angle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Content.Server.GameObjects.Components.Power;
|
|
||||||
using Content.Shared.GameObjects.Components.Power;
|
|
||||||
using Robust.Server.GameObjects;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
using Robust.Shared.Serialization;
|
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.Components.Weapon.Ranged.Hitscan
|
|
||||||
{
|
|
||||||
[RegisterComponent]
|
|
||||||
public class HitscanWeaponCapacitorComponent : PowerCellComponent
|
|
||||||
{
|
|
||||||
private AppearanceComponent _appearance;
|
|
||||||
|
|
||||||
public override string Name => "HitscanWeaponCapacitor";
|
|
||||||
|
|
||||||
public override float Charge
|
|
||||||
{
|
|
||||||
get => base.Charge;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
base.Charge = value;
|
|
||||||
_updateAppearance();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void ExposeData(ObjectSerializer serializer)
|
|
||||||
{
|
|
||||||
base.ExposeData(serializer);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Initialize()
|
|
||||||
{
|
|
||||||
base.Initialize();
|
|
||||||
|
|
||||||
Charge = Capacity;
|
|
||||||
Owner.TryGetComponent(out _appearance);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public float GetChargeFrom(float toDeduct)
|
|
||||||
{
|
|
||||||
//Use this function when you want to shoot even though you don't have enough energy for basecost
|
|
||||||
ChargeChanged();
|
|
||||||
var chargeChangedBy = Math.Min(this.Charge, toDeduct);
|
|
||||||
this.DeductCharge(chargeChangedBy);
|
|
||||||
_updateAppearance();
|
|
||||||
return chargeChangedBy;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void FillFrom(PowerStorageComponent battery)
|
|
||||||
{
|
|
||||||
var capacitorPowerDeficit = this.Capacity - this.Charge;
|
|
||||||
if (battery.CanDeductCharge(capacitorPowerDeficit))
|
|
||||||
{
|
|
||||||
battery.DeductCharge(capacitorPowerDeficit);
|
|
||||||
this.AddCharge(capacitorPowerDeficit);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.AddCharge(battery.Charge);
|
|
||||||
battery.DeductCharge(battery.Charge);
|
|
||||||
}
|
|
||||||
_updateAppearance();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void _updateAppearance()
|
|
||||||
{
|
|
||||||
_appearance?.SetData(PowerCellVisuals.ChargeLevel, Charge / Capacity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using Content.Server.GameObjects.Components.Power;
|
|
||||||
using Content.Server.GameObjects.Components.Sound;
|
|
||||||
using Content.Server.GameObjects.EntitySystems;
|
|
||||||
using Content.Server.Utility;
|
|
||||||
using Content.Shared.GameObjects;
|
|
||||||
using Content.Shared.Interfaces;
|
|
||||||
using Content.Shared.Physics;
|
|
||||||
using Robust.Server.GameObjects.EntitySystems;
|
|
||||||
using Robust.Shared.Audio;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
using Robust.Shared.GameObjects.EntitySystemMessages;
|
|
||||||
using Robust.Shared.GameObjects.Systems;
|
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
|
||||||
using Robust.Shared.Interfaces.Physics;
|
|
||||||
using Robust.Shared.Interfaces.Timing;
|
|
||||||
using Robust.Shared.IoC;
|
|
||||||
using Robust.Shared.Map;
|
|
||||||
using Robust.Shared.Maths;
|
|
||||||
using Robust.Shared.Physics;
|
|
||||||
using Robust.Shared.Serialization;
|
|
||||||
using Robust.Shared.Utility;
|
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.Components.Weapon.Ranged.Hitscan
|
|
||||||
{
|
|
||||||
[RegisterComponent]
|
|
||||||
public class HitscanWeaponComponent : Component, IInteractUsing
|
|
||||||
{
|
|
||||||
private const float MaxLength = 20;
|
|
||||||
public override string Name => "HitscanWeapon";
|
|
||||||
|
|
||||||
string _spritename;
|
|
||||||
private int _damage;
|
|
||||||
private int _baseFireCost;
|
|
||||||
private float _lowerChargeLimit;
|
|
||||||
private string _fireSound;
|
|
||||||
|
|
||||||
//As this is a component that sits on the weapon rather than a static value
|
|
||||||
//we just declare the field and then use GetComponent later to actually get it.
|
|
||||||
//Do remember to add it in both the .yaml prototype and the factory in EntryPoint.cs
|
|
||||||
//Otherwise you will get errors
|
|
||||||
private HitscanWeaponCapacitorComponent capacitorComponent;
|
|
||||||
|
|
||||||
public int Damage => _damage;
|
|
||||||
|
|
||||||
public int BaseFireCost => _baseFireCost;
|
|
||||||
|
|
||||||
public HitscanWeaponCapacitorComponent CapacitorComponent => capacitorComponent;
|
|
||||||
|
|
||||||
public override void ExposeData(ObjectSerializer serializer)
|
|
||||||
{
|
|
||||||
base.ExposeData(serializer);
|
|
||||||
|
|
||||||
serializer.DataField(ref _spritename, "fireSprite", "Objects/laser.png");
|
|
||||||
serializer.DataField(ref _damage, "damage", 10);
|
|
||||||
serializer.DataField(ref _baseFireCost, "baseFireCost", 300);
|
|
||||||
serializer.DataField(ref _lowerChargeLimit, "lowerChargeLimit", 10);
|
|
||||||
serializer.DataField(ref _fireSound, "fireSound", "/Audio/laser.ogg");
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Initialize()
|
|
||||||
{
|
|
||||||
base.Initialize();
|
|
||||||
var rangedWeapon = Owner.GetComponent<RangedWeaponComponent>();
|
|
||||||
capacitorComponent = Owner.GetComponent<HitscanWeaponCapacitorComponent>();
|
|
||||||
rangedWeapon.FireHandler = Fire;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool InteractUsing(InteractUsingEventArgs eventArgs)
|
|
||||||
{
|
|
||||||
if (!eventArgs.Using.TryGetComponent(out PowerStorageComponent component))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (capacitorComponent.Full)
|
|
||||||
{
|
|
||||||
Owner.PopupMessage(eventArgs.User, "Capacitor at max charge");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
capacitorComponent.FillFrom(component);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Fire(IEntity user, GridCoordinates clickLocation)
|
|
||||||
{
|
|
||||||
if (capacitorComponent.Charge < _lowerChargeLimit)
|
|
||||||
{//If capacitor has less energy than the lower limit, do nothing
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
float energyModifier = capacitorComponent.GetChargeFrom(_baseFireCost) / _baseFireCost;
|
|
||||||
var userPosition = user.Transform.WorldPosition; //Remember world positions are ephemeral and can only be used instantaneously
|
|
||||||
var angle = new Angle(clickLocation.Position - userPosition);
|
|
||||||
|
|
||||||
var ray = new CollisionRay(userPosition, angle.ToVec(), (int)(CollisionGroup.Opaque));
|
|
||||||
var rayCastResults = IoCManager.Resolve<IPhysicsManager>().IntersectRay(user.Transform.MapID, ray, MaxLength, user, returnOnFirstHit: false).ToList();
|
|
||||||
|
|
||||||
//The first result is guaranteed to be the closest one
|
|
||||||
if (rayCastResults.Count >= 1)
|
|
||||||
{
|
|
||||||
Hit(rayCastResults[0], energyModifier, user);
|
|
||||||
AfterEffects(user, rayCastResults[0].Distance, angle, energyModifier);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
AfterEffects(user, MaxLength, angle, energyModifier);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void Hit(RayCastResults ray, float damageModifier, IEntity user = null)
|
|
||||||
{
|
|
||||||
if (ray.HitEntity != null && ray.HitEntity.TryGetComponent(out DamageableComponent damage))
|
|
||||||
{
|
|
||||||
damage.TakeDamage(DamageType.Heat, (int)Math.Round(_damage * damageModifier, MidpointRounding.AwayFromZero), Owner, user);
|
|
||||||
//I used Math.Round over Convert.toInt32, as toInt32 always rounds to
|
|
||||||
//even numbers if halfway between two numbers, rather than rounding to nearest
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void AfterEffects(IEntity user, float distance, Angle angle, float energyModifier)
|
|
||||||
{
|
|
||||||
var time = IoCManager.Resolve<IGameTiming>().CurTime;
|
|
||||||
var offset = angle.ToVec() * distance / 2;
|
|
||||||
var message = new EffectSystemMessage
|
|
||||||
{
|
|
||||||
EffectSprite = _spritename,
|
|
||||||
Born = time,
|
|
||||||
DeathTime = time + TimeSpan.FromSeconds(1),
|
|
||||||
Size = new Vector2(distance, 1f),
|
|
||||||
Coordinates = user.Transform.GridPosition.Translated(offset),
|
|
||||||
//Rotated from east facing
|
|
||||||
Rotation = (float) angle.Theta,
|
|
||||||
ColorDelta = new Vector4(0, 0, 0, -1500f),
|
|
||||||
Color = Vector4.Multiply(new Vector4(255, 255, 255, 750), energyModifier),
|
|
||||||
|
|
||||||
Shaded = false
|
|
||||||
};
|
|
||||||
EntitySystem.Get<EffectSystem>().CreateParticle(message);
|
|
||||||
EntitySystem.Get<AudioSystem>().PlayFromEntity(_fireSound, Owner, AudioParams.Default.WithVolume(-5));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,240 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Content.Server.GameObjects.EntitySystems;
|
|
||||||
using Content.Server.Utility;
|
|
||||||
using Content.Shared.GameObjects.Components.Weapons.Ranged;
|
|
||||||
using Content.Shared.Interfaces;
|
|
||||||
using Robust.Server.GameObjects;
|
|
||||||
using Robust.Server.GameObjects.Components.Container;
|
|
||||||
using Robust.Server.Interfaces.GameObjects;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
|
||||||
using Robust.Shared.Serialization;
|
|
||||||
using Robust.Shared.ViewVariables;
|
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile
|
|
||||||
{
|
|
||||||
[RegisterComponent]
|
|
||||||
public class AmmoBoxComponent : Component, IInteractUsing, IMapInit
|
|
||||||
// TODO: Potential improvements:
|
|
||||||
// Add verbs for stack splitting
|
|
||||||
// Behaviour is largely the same as BallisticMagazine except you can't insert it into a gun.
|
|
||||||
{
|
|
||||||
public override string Name => "AmmoBox";
|
|
||||||
private BallisticCaliber _caliber;
|
|
||||||
private int _capacity;
|
|
||||||
[ViewVariables] private int _availableSpawnCount;
|
|
||||||
|
|
||||||
[ViewVariables] private readonly Stack<IEntity> _loadedBullets = new Stack<IEntity>();
|
|
||||||
|
|
||||||
[ViewVariables]
|
|
||||||
public string FillType => _fillType;
|
|
||||||
private string _fillType;
|
|
||||||
|
|
||||||
[ViewVariables] private Container _bulletContainer;
|
|
||||||
[ViewVariables] private AppearanceComponent _appearance;
|
|
||||||
|
|
||||||
[ViewVariables] public int Capacity => _capacity;
|
|
||||||
[ViewVariables] public BallisticCaliber Caliber => _caliber;
|
|
||||||
[ViewVariables] public int CountLeft => _loadedBullets.Count + _availableSpawnCount;
|
|
||||||
|
|
||||||
|
|
||||||
public override void ExposeData(ObjectSerializer serializer)
|
|
||||||
{
|
|
||||||
base.ExposeData(serializer);
|
|
||||||
|
|
||||||
serializer.DataField(ref _caliber, "caliber", BallisticCaliber.Unspecified);
|
|
||||||
serializer.DataField(ref _fillType, "fill", null);
|
|
||||||
serializer.DataField(ref _capacity, "capacity", 30);
|
|
||||||
serializer.DataField(ref _availableSpawnCount, "availableSpawnCount", Capacity);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void _updateAppearance()
|
|
||||||
{
|
|
||||||
_appearance.SetData(BallisticMagazineVisuals.AmmoLeft, CountLeft);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void MapInit()
|
|
||||||
{
|
|
||||||
_availableSpawnCount = Capacity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Initialize()
|
|
||||||
{
|
|
||||||
base.Initialize();
|
|
||||||
|
|
||||||
_appearance = Owner.GetComponent<AppearanceComponent>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Startup()
|
|
||||||
{
|
|
||||||
base.Startup();
|
|
||||||
|
|
||||||
_bulletContainer =
|
|
||||||
ContainerManagerComponent.Ensure<Container>("box_bullet_container", Owner, out var existed);
|
|
||||||
|
|
||||||
if (existed)
|
|
||||||
{
|
|
||||||
foreach (var entity in _bulletContainer.ContainedEntities)
|
|
||||||
{
|
|
||||||
_loadedBullets.Push(entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_updateAppearance();
|
|
||||||
_appearance.SetData(BallisticMagazineVisuals.AmmoCapacity, Capacity);
|
|
||||||
}
|
|
||||||
|
|
||||||
AmmoBoxTransferPopupMessage CanTransferFrom(IEntity source)
|
|
||||||
{
|
|
||||||
// Currently the below duplicates mags but at some stage these will likely differ
|
|
||||||
if (source.TryGetComponent(out BallisticMagazineComponent magazineComponent))
|
|
||||||
{
|
|
||||||
if (magazineComponent.Caliber != Caliber)
|
|
||||||
{
|
|
||||||
return new AmmoBoxTransferPopupMessage(result: false, message: "Wrong caliber");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CountLeft == Capacity)
|
|
||||||
{
|
|
||||||
return new AmmoBoxTransferPopupMessage(result: false, message: "Already full");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (magazineComponent.CountLoaded == 0)
|
|
||||||
{
|
|
||||||
return new AmmoBoxTransferPopupMessage(result: false, message: "No ammo to transfer");
|
|
||||||
}
|
|
||||||
|
|
||||||
return new AmmoBoxTransferPopupMessage(result: true, message: "");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (source.TryGetComponent(out AmmoBoxComponent boxComponent))
|
|
||||||
{
|
|
||||||
if (boxComponent.Caliber != Caliber)
|
|
||||||
{
|
|
||||||
return new AmmoBoxTransferPopupMessage(result: false, message: "Wrong caliber");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CountLeft == Capacity)
|
|
||||||
{
|
|
||||||
return new AmmoBoxTransferPopupMessage(result: false, message: "Already full");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (boxComponent.CountLeft == 0)
|
|
||||||
{
|
|
||||||
return new AmmoBoxTransferPopupMessage(result: false, message: "No ammo to transfer");
|
|
||||||
}
|
|
||||||
|
|
||||||
return new AmmoBoxTransferPopupMessage(result: true, message: "");
|
|
||||||
}
|
|
||||||
|
|
||||||
return new AmmoBoxTransferPopupMessage(result: false, message: "");
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Potentially abstract out to reduce duplicate structs
|
|
||||||
private struct AmmoBoxTransferPopupMessage
|
|
||||||
{
|
|
||||||
public readonly bool Result;
|
|
||||||
public readonly string Message;
|
|
||||||
|
|
||||||
public AmmoBoxTransferPopupMessage(bool result, string message)
|
|
||||||
{
|
|
||||||
Result = result;
|
|
||||||
Message = message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
|
||||||
{
|
|
||||||
var ammoBoxTransfer = CanTransferFrom(eventArgs.Using);
|
|
||||||
if (ammoBoxTransfer.Result) {
|
|
||||||
IEntity bullet;
|
|
||||||
if (eventArgs.Using.TryGetComponent(out BallisticMagazineComponent magazineComponent))
|
|
||||||
{
|
|
||||||
int fillCount = Math.Min(magazineComponent.CountLoaded, Capacity - CountLeft);
|
|
||||||
for (int i = 0; i < fillCount; i++)
|
|
||||||
{
|
|
||||||
bullet = magazineComponent.TakeBullet();
|
|
||||||
AddBullet(bullet);
|
|
||||||
}
|
|
||||||
eventArgs.User.PopupMessage(eventArgs.User, $"Transferred {fillCount} rounds");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (eventArgs.Using.TryGetComponent(out AmmoBoxComponent boxComponent))
|
|
||||||
{
|
|
||||||
int fillCount = Math.Min(boxComponent.CountLeft, Capacity - CountLeft);
|
|
||||||
for (int i = 0; i < fillCount; i++)
|
|
||||||
{
|
|
||||||
bullet = boxComponent.TakeBullet();
|
|
||||||
AddBullet(bullet);
|
|
||||||
}
|
|
||||||
eventArgs.User.PopupMessage(eventArgs.User, $"Transferred {fillCount} rounds");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
eventArgs.User.PopupMessage(eventArgs.User, ammoBoxTransfer.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddBullet(IEntity bullet)
|
|
||||||
{
|
|
||||||
if (Owner.TryGetComponent(out BallisticMagazineComponent magazineComponent))
|
|
||||||
{
|
|
||||||
magazineComponent.AddBullet(bullet);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!bullet.TryGetComponent(out BallisticBulletComponent component))
|
|
||||||
{
|
|
||||||
throw new ArgumentException("entity isn't a bullet.", nameof(bullet));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (component.Caliber != Caliber)
|
|
||||||
{
|
|
||||||
throw new ArgumentException("entity is of the wrong caliber.", nameof(bullet));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CountLeft >= Capacity)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Box is full.");
|
|
||||||
}
|
|
||||||
|
|
||||||
_bulletContainer.Insert(bullet);
|
|
||||||
_loadedBullets.Push(bullet);
|
|
||||||
_updateAppearance();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEntity TakeBullet()
|
|
||||||
{
|
|
||||||
IEntity bullet;
|
|
||||||
if (Owner.TryGetComponent(out BallisticMagazineComponent magazineComponent))
|
|
||||||
{
|
|
||||||
bullet = magazineComponent.TakeBullet();
|
|
||||||
return bullet;
|
|
||||||
}
|
|
||||||
if (_loadedBullets.Count == 0)
|
|
||||||
{
|
|
||||||
if (_availableSpawnCount == 0)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
_availableSpawnCount -= 1;
|
|
||||||
bullet = Owner.EntityManager.SpawnEntity(FillType, Owner.Transform.GridPosition);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
bullet = _loadedBullets.Pop();
|
|
||||||
_bulletContainer.Remove(bullet);
|
|
||||||
}
|
|
||||||
|
|
||||||
_updateAppearance();
|
|
||||||
return bullet;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
using Robust.Shared.GameObjects;
|
|
||||||
using Robust.Shared.Serialization;
|
|
||||||
using Robust.Shared.ViewVariables;
|
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Passes information about the projectiles to be fired by AmmoWeapons
|
|
||||||
/// </summary>
|
|
||||||
[RegisterComponent]
|
|
||||||
public class BallisticBulletComponent : Component
|
|
||||||
{
|
|
||||||
public override string Name => "BallisticBullet";
|
|
||||||
|
|
||||||
private BallisticCaliber _caliber;
|
|
||||||
/// <summary>
|
|
||||||
/// Cartridge calibre, restricts what AmmoWeapons this ammo can be fired from.
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
public BallisticCaliber Caliber { get => _caliber; set => _caliber = value; }
|
|
||||||
|
|
||||||
private string _projectileID;
|
|
||||||
/// <summary>
|
|
||||||
/// YAML ID of the projectiles to be created when firing this ammo.
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
public string ProjectileID { get => _projectileID; set => _projectileID = value; }
|
|
||||||
|
|
||||||
private int _projectilesFired;
|
|
||||||
/// <summary>
|
|
||||||
/// How many copies of the projectile are shot.
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
public int ProjectilesFired { get => _projectilesFired; set => _projectilesFired = value; }
|
|
||||||
|
|
||||||
private float _spreadStdDev_Ammo;
|
|
||||||
/// <summary>
|
|
||||||
/// Weapons that fire projectiles from ammo types.
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
public float SpreadStdDev_Ammo { get => _spreadStdDev_Ammo; set => _spreadStdDev_Ammo = value; }
|
|
||||||
|
|
||||||
private float _evenSpreadAngle_Ammo;
|
|
||||||
/// <summary>
|
|
||||||
/// Arc angle of shotgun pellet spreads, only used if multiple projectiles are being fired.
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
public float EvenSpreadAngle_Ammo { get => _evenSpreadAngle_Ammo; set => _evenSpreadAngle_Ammo = value; }
|
|
||||||
|
|
||||||
private float _velocity_Ammo;
|
|
||||||
/// <summary>
|
|
||||||
/// Adds additional velocity to the projectile, on top of what it already has.
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
public float Velocity_Ammo { get => _velocity_Ammo; set => _velocity_Ammo = value; }
|
|
||||||
|
|
||||||
private bool _spent;
|
|
||||||
/// <summary>
|
|
||||||
/// If the ammo cartridge has been shot already.
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
public bool Spent { get => _spent; set => _spent = value; }
|
|
||||||
|
|
||||||
public override void ExposeData(ObjectSerializer serializer)
|
|
||||||
{
|
|
||||||
base.ExposeData(serializer);
|
|
||||||
serializer.DataField(ref _caliber, "caliber", BallisticCaliber.Unspecified);
|
|
||||||
serializer.DataField(ref _projectileID, "projectile", null);
|
|
||||||
serializer.DataField(ref _spent, "spent", false);
|
|
||||||
serializer.DataField(ref _projectilesFired, "projectilesfired", 1);
|
|
||||||
serializer.DataField(ref _spreadStdDev_Ammo, "ammostddev", 0);
|
|
||||||
serializer.DataField(ref _evenSpreadAngle_Ammo, "ammospread", 0);
|
|
||||||
serializer.DataField(ref _velocity_Ammo, "ammovelocity", 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public enum BallisticCaliber
|
|
||||||
{
|
|
||||||
Unspecified = 0,
|
|
||||||
// .32
|
|
||||||
A32,
|
|
||||||
// .357
|
|
||||||
A357,
|
|
||||||
// .44
|
|
||||||
A44,
|
|
||||||
// .45mm
|
|
||||||
A45mm,
|
|
||||||
// .50 cal
|
|
||||||
A50,
|
|
||||||
// 5.56mm
|
|
||||||
A556mm,
|
|
||||||
// 6.5mm
|
|
||||||
A65mm,
|
|
||||||
// 7.62mm
|
|
||||||
A762mm,
|
|
||||||
// 9mm
|
|
||||||
A9mm,
|
|
||||||
// 10mm
|
|
||||||
A10mm,
|
|
||||||
// 20mm
|
|
||||||
A20mm,
|
|
||||||
// 24mm
|
|
||||||
A24mm,
|
|
||||||
// 12g
|
|
||||||
A12g,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,286 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Content.Server.GameObjects.EntitySystems;
|
|
||||||
using Content.Server.Utility;
|
|
||||||
using Content.Shared.GameObjects.Components.Weapons.Ranged;
|
|
||||||
using Content.Shared.Interfaces;
|
|
||||||
using Robust.Server.GameObjects;
|
|
||||||
using Robust.Server.GameObjects.Components.Container;
|
|
||||||
using Robust.Server.Interfaces.GameObjects;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
|
||||||
using Robust.Shared.Serialization;
|
|
||||||
using Robust.Shared.ViewVariables;
|
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile
|
|
||||||
{
|
|
||||||
[RegisterComponent]
|
|
||||||
public class BallisticMagazineComponent : Component, IMapInit, IInteractUsing
|
|
||||||
{
|
|
||||||
public override string Name => "BallisticMagazine";
|
|
||||||
|
|
||||||
// Stack of loaded bullets.
|
|
||||||
[ViewVariables] private readonly Stack<IEntity> _loadedBullets = new Stack<IEntity>();
|
|
||||||
private string _fillType;
|
|
||||||
|
|
||||||
[ViewVariables] private Container _bulletContainer;
|
|
||||||
[ViewVariables] private AppearanceComponent _appearance;
|
|
||||||
|
|
||||||
private BallisticMagazineType _magazineType;
|
|
||||||
private BallisticCaliber _caliber;
|
|
||||||
private int _capacity;
|
|
||||||
|
|
||||||
[ViewVariables] public string FillType => _fillType;
|
|
||||||
[ViewVariables] public BallisticMagazineType MagazineType => _magazineType;
|
|
||||||
[ViewVariables] public BallisticCaliber Caliber => _caliber;
|
|
||||||
[ViewVariables] public int Capacity => _capacity;
|
|
||||||
|
|
||||||
[ViewVariables] public int CountLoaded => _loadedBullets.Count + _availableSpawnCount;
|
|
||||||
|
|
||||||
[ViewVariables] private int _availableSpawnCount;
|
|
||||||
|
|
||||||
public event Action OnAmmoCountChanged;
|
|
||||||
|
|
||||||
public override void ExposeData(ObjectSerializer serializer)
|
|
||||||
{
|
|
||||||
base.ExposeData(serializer);
|
|
||||||
|
|
||||||
serializer.DataField(ref _magazineType, "magazine", BallisticMagazineType.Unspecified);
|
|
||||||
serializer.DataField(ref _caliber, "caliber", BallisticCaliber.Unspecified);
|
|
||||||
serializer.DataField(ref _fillType, "fill", null);
|
|
||||||
serializer.DataField(ref _capacity, "capacity", 20);
|
|
||||||
serializer.DataField(ref _availableSpawnCount, "availableSpawnCount", Capacity);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Initialize()
|
|
||||||
{
|
|
||||||
base.Initialize();
|
|
||||||
|
|
||||||
_appearance = Owner.GetComponent<AppearanceComponent>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Startup()
|
|
||||||
{
|
|
||||||
base.Startup();
|
|
||||||
|
|
||||||
_bulletContainer =
|
|
||||||
ContainerManagerComponent.Ensure<Container>("magazine_bullet_container", Owner, out var existed);
|
|
||||||
|
|
||||||
if (existed)
|
|
||||||
{
|
|
||||||
foreach (var entity in _bulletContainer.ContainedEntities)
|
|
||||||
{
|
|
||||||
_loadedBullets.Push(entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateAppearance();
|
|
||||||
|
|
||||||
OnAmmoCountChanged?.Invoke();
|
|
||||||
_appearance.SetData(BallisticMagazineVisuals.AmmoCapacity, Capacity);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddBullet(IEntity bullet)
|
|
||||||
{
|
|
||||||
if (!bullet.TryGetComponent(out BallisticBulletComponent component))
|
|
||||||
{
|
|
||||||
throw new ArgumentException("entity isn't a bullet.", nameof(bullet));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (component.Caliber != Caliber)
|
|
||||||
{
|
|
||||||
throw new ArgumentException("entity is of the wrong caliber.", nameof(bullet));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CountLoaded >= Capacity)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Magazine is full.");
|
|
||||||
}
|
|
||||||
|
|
||||||
_bulletContainer.Insert(bullet);
|
|
||||||
_loadedBullets.Push(bullet);
|
|
||||||
UpdateAppearance();
|
|
||||||
OnAmmoCountChanged?.Invoke();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEntity TakeBullet()
|
|
||||||
{
|
|
||||||
IEntity bullet;
|
|
||||||
if (_loadedBullets.Count == 0)
|
|
||||||
{
|
|
||||||
if (_availableSpawnCount == 0)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
_availableSpawnCount -= 1;
|
|
||||||
bullet = Owner.EntityManager.SpawnEntity(FillType, Owner.Transform.GridPosition);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
bullet = _loadedBullets.Pop();
|
|
||||||
_bulletContainer.Remove(bullet);
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateAppearance();
|
|
||||||
OnAmmoCountChanged?.Invoke();
|
|
||||||
return bullet;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Allow putting individual casings into mag (also box)
|
|
||||||
AmmoMagTransferPopupMessage CanTransferFrom(IEntity source)
|
|
||||||
{
|
|
||||||
// Currently the below duplicates box but at some stage these will likely differ
|
|
||||||
if (source.TryGetComponent(out BallisticMagazineComponent magazineComponent))
|
|
||||||
{
|
|
||||||
if (magazineComponent.Caliber != Caliber)
|
|
||||||
{
|
|
||||||
return new AmmoMagTransferPopupMessage(result: false, message: "Wrong caliber");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CountLoaded == Capacity)
|
|
||||||
{
|
|
||||||
return new AmmoMagTransferPopupMessage(result: false, message: "Already full");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (magazineComponent.CountLoaded == 0)
|
|
||||||
{
|
|
||||||
return new AmmoMagTransferPopupMessage(result: false, message: "No ammo to transfer");
|
|
||||||
}
|
|
||||||
|
|
||||||
return new AmmoMagTransferPopupMessage(result: true, message: "");
|
|
||||||
}
|
|
||||||
|
|
||||||
// If box
|
|
||||||
if (source.TryGetComponent(out AmmoBoxComponent boxComponent))
|
|
||||||
{
|
|
||||||
if (boxComponent.Caliber != Caliber)
|
|
||||||
{
|
|
||||||
return new AmmoMagTransferPopupMessage(result: false, message: "Wrong caliber");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CountLoaded == Capacity)
|
|
||||||
{
|
|
||||||
return new AmmoMagTransferPopupMessage(result: false, message: "Already full");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (boxComponent.CountLeft == 0)
|
|
||||||
{
|
|
||||||
return new AmmoMagTransferPopupMessage(result: false, message: "No ammo to transfer");
|
|
||||||
}
|
|
||||||
|
|
||||||
return new AmmoMagTransferPopupMessage(result: true, message: "");
|
|
||||||
}
|
|
||||||
|
|
||||||
return new AmmoMagTransferPopupMessage(result: false, message: "");
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Potentially abstract out to reduce duplicate structs
|
|
||||||
private struct AmmoMagTransferPopupMessage
|
|
||||||
{
|
|
||||||
public readonly bool Result;
|
|
||||||
public readonly string Message;
|
|
||||||
|
|
||||||
public AmmoMagTransferPopupMessage(bool result, string message)
|
|
||||||
{
|
|
||||||
Result = result;
|
|
||||||
Message = message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
|
||||||
{
|
|
||||||
var ammoMagTransfer = CanTransferFrom(eventArgs.Using);
|
|
||||||
if (ammoMagTransfer.Result) {
|
|
||||||
IEntity bullet;
|
|
||||||
if (eventArgs.Using.TryGetComponent(out BallisticMagazineComponent magazineComponent))
|
|
||||||
{
|
|
||||||
int fillCount = Math.Min(magazineComponent.CountLoaded, Capacity - CountLoaded);
|
|
||||||
for (int i = 0; i < fillCount; i++)
|
|
||||||
{
|
|
||||||
bullet = magazineComponent.TakeBullet();
|
|
||||||
AddBullet(bullet);
|
|
||||||
}
|
|
||||||
eventArgs.User.PopupMessage(eventArgs.User, $"Transferred {fillCount} rounds");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (eventArgs.Using.TryGetComponent(out AmmoBoxComponent boxComponent))
|
|
||||||
{
|
|
||||||
int fillCount = Math.Min(boxComponent.CountLeft, Capacity - CountLoaded);
|
|
||||||
for (int i = 0; i < fillCount; i++)
|
|
||||||
{
|
|
||||||
bullet = boxComponent.TakeBullet();
|
|
||||||
AddBullet(bullet);
|
|
||||||
}
|
|
||||||
eventArgs.User.PopupMessage(eventArgs.User, $"Transferred {fillCount} rounds");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
eventArgs.User.PopupMessage(eventArgs.User, ammoMagTransfer.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateAppearance()
|
|
||||||
{
|
|
||||||
_appearance.SetData(BallisticMagazineVisuals.AmmoLeft, CountLoaded);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void MapInit()
|
|
||||||
{
|
|
||||||
_availableSpawnCount = Capacity;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum BallisticMagazineType
|
|
||||||
{
|
|
||||||
Unspecified = 0,
|
|
||||||
|
|
||||||
// .32
|
|
||||||
A32,
|
|
||||||
|
|
||||||
// .357
|
|
||||||
A357,
|
|
||||||
|
|
||||||
// .44
|
|
||||||
A44,
|
|
||||||
|
|
||||||
// .45mm
|
|
||||||
A45mm,
|
|
||||||
|
|
||||||
// .50 cal
|
|
||||||
A50,
|
|
||||||
|
|
||||||
// 5.56mm
|
|
||||||
A556mm,
|
|
||||||
|
|
||||||
// 6.5mm
|
|
||||||
A65mm,
|
|
||||||
|
|
||||||
// 7.62mm
|
|
||||||
A762mm,
|
|
||||||
Maxim,
|
|
||||||
|
|
||||||
// 9mm
|
|
||||||
A9mm,
|
|
||||||
A9mmSMG,
|
|
||||||
A9mmTopMounted,
|
|
||||||
|
|
||||||
// 10mm
|
|
||||||
A10mm,
|
|
||||||
A10mmSMG,
|
|
||||||
|
|
||||||
// 20mm
|
|
||||||
A20mm,
|
|
||||||
|
|
||||||
// 24mm
|
|
||||||
A24mm,
|
|
||||||
|
|
||||||
// 12g
|
|
||||||
A12g,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,311 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Content.Server.GameObjects.Components.Sound;
|
|
||||||
using Content.Server.GameObjects.EntitySystems;
|
|
||||||
using Content.Server.Utility;
|
|
||||||
using Content.Shared.GameObjects;
|
|
||||||
using Content.Shared.GameObjects.Components.Weapons.Ranged;
|
|
||||||
using Content.Shared.Interfaces;
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
using Robust.Server.GameObjects;
|
|
||||||
using Robust.Server.GameObjects.Components.Container;
|
|
||||||
using Robust.Server.GameObjects.EntitySystems;
|
|
||||||
using Robust.Server.Interfaces.GameObjects;
|
|
||||||
using Robust.Shared.Audio;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
using Robust.Shared.GameObjects.Systems;
|
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
|
||||||
using Robust.Shared.Interfaces.Random;
|
|
||||||
using Robust.Shared.IoC;
|
|
||||||
using Robust.Shared.Maths;
|
|
||||||
using Robust.Shared.Random;
|
|
||||||
using Robust.Shared.Serialization;
|
|
||||||
using Robust.Shared.Utility;
|
|
||||||
using Robust.Shared.ViewVariables;
|
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Guns that have a magazine.
|
|
||||||
/// </summary>
|
|
||||||
[RegisterComponent]
|
|
||||||
public class BallisticMagazineWeaponComponent : BallisticWeaponComponent, IUse, IInteractUsing, IMapInit
|
|
||||||
{
|
|
||||||
private const float BulletOffset = 0.2f;
|
|
||||||
|
|
||||||
public override string Name => "BallisticMagazineWeapon";
|
|
||||||
public override uint? NetID => ContentNetIDs.BALLISTIC_MAGAZINE_WEAPON;
|
|
||||||
|
|
||||||
[ViewVariables] private string _defaultMagazine;
|
|
||||||
|
|
||||||
public ContainerSlot MagazineSlot => _magazineSlot;
|
|
||||||
[ViewVariables] private ContainerSlot _magazineSlot;
|
|
||||||
private List<BallisticMagazineType> _magazineTypes;
|
|
||||||
|
|
||||||
[ViewVariables] public List<BallisticMagazineType> MagazineTypes => _magazineTypes;
|
|
||||||
[ViewVariables] private IEntity Magazine => _magazineSlot.ContainedEntity;
|
|
||||||
|
|
||||||
#pragma warning disable 649
|
|
||||||
[Dependency] private readonly IRobustRandom _bulletDropRandom;
|
|
||||||
#pragma warning restore 649
|
|
||||||
[ViewVariables] private string _magInSound;
|
|
||||||
[ViewVariables] private string _magOutSound;
|
|
||||||
[ViewVariables] private string _autoEjectSound;
|
|
||||||
[ViewVariables] private bool _autoEjectMagazine;
|
|
||||||
[ViewVariables] private AppearanceComponent _appearance;
|
|
||||||
|
|
||||||
private static readonly Direction[] RandomBulletDirs =
|
|
||||||
{
|
|
||||||
Direction.North,
|
|
||||||
Direction.East,
|
|
||||||
Direction.South,
|
|
||||||
Direction.West
|
|
||||||
};
|
|
||||||
|
|
||||||
public override void ExposeData(ObjectSerializer serializer)
|
|
||||||
{
|
|
||||||
base.ExposeData(serializer);
|
|
||||||
serializer.DataField(ref _magazineTypes, "magazines",
|
|
||||||
new List<BallisticMagazineType> {BallisticMagazineType.Unspecified});
|
|
||||||
serializer.DataField(ref _defaultMagazine, "default_magazine", null);
|
|
||||||
serializer.DataField(ref _autoEjectMagazine, "auto_eject_magazine", false);
|
|
||||||
serializer.DataField(ref _autoEjectSound, "sound_auto_eject", null);
|
|
||||||
serializer.DataField(ref _magInSound, "sound_magazine_in", null);
|
|
||||||
serializer.DataField(ref _magOutSound, "sound_magazine_out", null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Initialize()
|
|
||||||
{
|
|
||||||
base.Initialize();
|
|
||||||
_appearance = Owner.GetComponent<AppearanceComponent>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Startup()
|
|
||||||
{
|
|
||||||
base.Startup();
|
|
||||||
_magazineSlot = ContainerManagerComponent.Ensure<ContainerSlot>("ballistic_gun_magazine", Owner);
|
|
||||||
if (Magazine != null)
|
|
||||||
{
|
|
||||||
// Already got magazine from loading a container.
|
|
||||||
Magazine.GetComponent<BallisticMagazineComponent>().OnAmmoCountChanged += MagazineAmmoCountChanged;
|
|
||||||
}
|
|
||||||
UpdateAppearance();
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool InsertMagazine(IEntity magazine, bool playSound = true)
|
|
||||||
{
|
|
||||||
if (!magazine.TryGetComponent(out BallisticMagazineComponent magazinetype))
|
|
||||||
{
|
|
||||||
throw new ArgumentException("Not a magazine", nameof(magazine));
|
|
||||||
}
|
|
||||||
if (!MagazineTypes.Contains(magazinetype.MagazineType))
|
|
||||||
{
|
|
||||||
throw new ArgumentException("Wrong magazine type", nameof(magazine));
|
|
||||||
}
|
|
||||||
if (!_magazineSlot.Insert(magazine))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (_magInSound != null && playSound)
|
|
||||||
{
|
|
||||||
EntitySystem.Get<AudioSystem>().PlayFromEntity(_magInSound, Owner);
|
|
||||||
}
|
|
||||||
magazinetype.OnAmmoCountChanged += MagazineAmmoCountChanged;
|
|
||||||
if (GetChambered(0) == null)
|
|
||||||
{
|
|
||||||
// No bullet in chamber, load one from magazine.
|
|
||||||
var bullet = magazinetype.TakeBullet();
|
|
||||||
if (bullet != null)
|
|
||||||
{
|
|
||||||
LoadIntoChamber(0, bullet);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
UpdateAppearance();
|
|
||||||
Dirty();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool EjectMagazine(bool playSound = true)
|
|
||||||
{
|
|
||||||
var entity = Magazine;
|
|
||||||
if (entity == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (_magazineSlot.Remove(entity))
|
|
||||||
{
|
|
||||||
entity.Transform.GridPosition = Owner.Transform.GridPosition;
|
|
||||||
if (_magOutSound != null && playSound)
|
|
||||||
{
|
|
||||||
EntitySystem.Get<AudioSystem>().PlayFromEntity(_magOutSound, Owner, AudioParams.Default.WithVolume(20));
|
|
||||||
}
|
|
||||||
UpdateAppearance();
|
|
||||||
Dirty();
|
|
||||||
entity.GetComponent<BallisticMagazineComponent>().OnAmmoCountChanged -= MagazineAmmoCountChanged;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
UpdateAppearance();
|
|
||||||
Dirty();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// these are complete strings for the sake of the shared string dict
|
|
||||||
[UsedImplicitly]
|
|
||||||
private static readonly string[] _bulletDropSounds =
|
|
||||||
{
|
|
||||||
"/Audio/Guns/Casings/casingfall1.ogg",
|
|
||||||
"/Audio/Guns/Casings/casingfall2.ogg",
|
|
||||||
"/Audio/Guns/Casings/casingfall3.ogg"
|
|
||||||
};
|
|
||||||
|
|
||||||
protected override void CycleChamberedBullet(int chamber)
|
|
||||||
{
|
|
||||||
DebugTools.Assert(chamber == 0);
|
|
||||||
|
|
||||||
// Eject chambered bullet.
|
|
||||||
var entity = RemoveFromChamber(chamber);
|
|
||||||
if (entity == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var offsetPos = (CalcBulletOffset(), CalcBulletOffset());
|
|
||||||
entity.Transform.GridPosition = Owner.Transform.GridPosition.Offset(offsetPos);
|
|
||||||
entity.Transform.LocalRotation = _bulletDropRandom.Pick(RandomBulletDirs).ToAngle();
|
|
||||||
var bulletDropNext = _bulletDropRandom.Next(1, 3);
|
|
||||||
var effect = _bulletDropSounds[bulletDropNext];
|
|
||||||
EntitySystem.Get<AudioSystem>().PlayFromEntity(effect, Owner, AudioParams.Default.WithVolume(-3));
|
|
||||||
|
|
||||||
if (Magazine != null)
|
|
||||||
{
|
|
||||||
var magComponent = Magazine.GetComponent<BallisticMagazineComponent>();
|
|
||||||
var bullet = magComponent.TakeBullet();
|
|
||||||
if (bullet != null)
|
|
||||||
{
|
|
||||||
LoadIntoChamber(0, bullet);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (magComponent.CountLoaded == 0 && _autoEjectMagazine)
|
|
||||||
{
|
|
||||||
DoAutoEject();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Dirty();
|
|
||||||
UpdateAppearance();
|
|
||||||
}
|
|
||||||
|
|
||||||
private float CalcBulletOffset()
|
|
||||||
{
|
|
||||||
return _bulletDropRandom.NextFloat() * (BulletOffset * 2) - BulletOffset;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DoAutoEject()
|
|
||||||
{
|
|
||||||
SendNetworkMessage(new BmwComponentAutoEjectedMessage());
|
|
||||||
EjectMagazine();
|
|
||||||
if (_autoEjectSound != null)
|
|
||||||
{
|
|
||||||
EntitySystem.Get<AudioSystem>().PlayFromEntity(_autoEjectSound, Owner, AudioParams.Default.WithVolume(-5));
|
|
||||||
}
|
|
||||||
Dirty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool UseEntity(UseEntityEventArgs eventArgs)
|
|
||||||
{
|
|
||||||
var ret = EjectMagazine();
|
|
||||||
if (ret)
|
|
||||||
{
|
|
||||||
Owner.PopupMessage(eventArgs.User, "Magazine ejected");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Owner.PopupMessage(eventArgs.User, "No magazine");
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool InteractUsing(InteractUsingEventArgs eventArgs)
|
|
||||||
{
|
|
||||||
if (!eventArgs.Using.TryGetComponent(out BallisticMagazineComponent component))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (Magazine != null)
|
|
||||||
{
|
|
||||||
Owner.PopupMessage(eventArgs.User, "Already got a magazine.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!MagazineTypes.Contains(component.MagazineType))
|
|
||||||
{
|
|
||||||
Owner.PopupMessage(eventArgs.User, "Magazine doesn't fit.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return InsertMagazine(eventArgs.Using);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void MagazineAmmoCountChanged()
|
|
||||||
{
|
|
||||||
Dirty();
|
|
||||||
UpdateAppearance();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateAppearance()
|
|
||||||
{
|
|
||||||
if (Magazine != null)
|
|
||||||
{
|
|
||||||
var comp = Magazine.GetComponent<BallisticMagazineComponent>();
|
|
||||||
_appearance.SetData(BallisticMagazineWeaponVisuals.AmmoLeft, comp.CountLoaded);
|
|
||||||
_appearance.SetData(BallisticMagazineWeaponVisuals.AmmoCapacity, comp.Capacity);
|
|
||||||
_appearance.SetData(BallisticMagazineWeaponVisuals.MagazineLoaded, true);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_appearance.SetData(BallisticMagazineWeaponVisuals.AmmoLeft, 0);
|
|
||||||
_appearance.SetData(BallisticMagazineWeaponVisuals.AmmoLeft, 0);
|
|
||||||
_appearance.SetData(BallisticMagazineWeaponVisuals.MagazineLoaded, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override ComponentState GetComponentState()
|
|
||||||
{
|
|
||||||
var chambered = GetChambered(0) != null;
|
|
||||||
(int, int)? count = null;
|
|
||||||
if (Magazine != null)
|
|
||||||
{
|
|
||||||
var magComponent = Magazine.GetComponent<BallisticMagazineComponent>();
|
|
||||||
count = (magComponent.CountLoaded, magComponent.Capacity);
|
|
||||||
}
|
|
||||||
return new BallisticMagazineWeaponComponentState(chambered, count);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Verb]
|
|
||||||
public sealed class EjectMagazineVerb : Verb<BallisticMagazineWeaponComponent>
|
|
||||||
{
|
|
||||||
protected override void GetData(IEntity user, BallisticMagazineWeaponComponent component, VerbData data)
|
|
||||||
{
|
|
||||||
if (component.Magazine == null)
|
|
||||||
{
|
|
||||||
data.Text = "Eject magazine (magazine missing)";
|
|
||||||
data.Visibility = VerbVisibility.Disabled;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
data.Text = "Eject magazine";
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Activate(IEntity user, BallisticMagazineWeaponComponent component)
|
|
||||||
{
|
|
||||||
component.EjectMagazine();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void IMapInit.MapInit()
|
|
||||||
{
|
|
||||||
if (_defaultMagazine != null)
|
|
||||||
{
|
|
||||||
var magazine = Owner.EntityManager.SpawnEntity(_defaultMagazine, Owner.Transform.GridPosition);
|
|
||||||
InsertMagazine(magazine, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,169 +0,0 @@
|
|||||||
using Content.Server.GameObjects.Components.Sound;
|
|
||||||
using Robust.Server.GameObjects.Components.Container;
|
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
|
||||||
using Robust.Shared.Map;
|
|
||||||
using Robust.Shared.Serialization;
|
|
||||||
using Robust.Shared.ViewVariables;
|
|
||||||
using System;
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
using Robust.Server.GameObjects.EntitySystems;
|
|
||||||
using Robust.Shared.GameObjects.Systems;
|
|
||||||
using Robust.Shared.IoC;
|
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Handles firing projectiles from a contained <see cref="BallisticBulletComponent" />.
|
|
||||||
/// </summary>
|
|
||||||
public abstract class BallisticWeaponComponent : BaseProjectileWeaponComponent
|
|
||||||
{
|
|
||||||
private Chamber[] _chambers;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Number of chambers created during initialization.
|
|
||||||
/// </summary>
|
|
||||||
private int _chamberCount;
|
|
||||||
|
|
||||||
[ViewVariables]
|
|
||||||
private BallisticCaliber _caliber ;
|
|
||||||
/// <summary>
|
|
||||||
/// What type of ammo this gun can fire.
|
|
||||||
/// </summary>
|
|
||||||
|
|
||||||
private string _soundGunEmpty;
|
|
||||||
/// <summary>
|
|
||||||
/// Sound played when trying to shoot if there is no ammo available.
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
public string SoundGunEmpty { get => _soundGunEmpty; set => _soundGunEmpty = value; }
|
|
||||||
|
|
||||||
private float _spreadStdDevGun;
|
|
||||||
/// <summary>
|
|
||||||
/// Increases the standard deviation of the ammo being fired.
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
public float SpreadStdDevGun { get => _spreadStdDevGun; set => _spreadStdDevGun = value; }
|
|
||||||
|
|
||||||
private float _evenSpreadAngleGun;
|
|
||||||
/// <summary>
|
|
||||||
/// Increases the evenspread of the ammo being fired.
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
public float EvenSpreadAngleGun { get => _evenSpreadAngleGun; set => _evenSpreadAngleGun = value; }
|
|
||||||
|
|
||||||
private float _velocityGun;
|
|
||||||
/// <summary>
|
|
||||||
/// Increases the velocity of the ammo being fired.
|
|
||||||
/// </summary>
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
public float VelocityGun { get => _velocityGun; set => _velocityGun = value; }
|
|
||||||
|
|
||||||
public override void ExposeData(ObjectSerializer serializer)
|
|
||||||
{
|
|
||||||
base.ExposeData(serializer);
|
|
||||||
serializer.DataField(ref _soundGunEmpty, "sound_empty", "/Audio/Guns/Empty/empty.ogg");
|
|
||||||
serializer.DataField(ref _spreadStdDevGun, "spreadstddev", 0);
|
|
||||||
serializer.DataField(ref _evenSpreadAngleGun, "evenspread", 0);
|
|
||||||
serializer.DataField(ref _velocityGun, "gunvelocity", 0);
|
|
||||||
serializer.DataField(ref _caliber, "caliber", BallisticCaliber.Unspecified);
|
|
||||||
serializer.DataField(ref _chamberCount, "chambers", 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// for shared string dict, since we don't define these anywhere in content
|
|
||||||
[UsedImplicitly]
|
|
||||||
private static readonly string[] _ballisticsChambersStrings =
|
|
||||||
{
|
|
||||||
"ballistics_chamber_0",
|
|
||||||
"ballistics_chamber_1",
|
|
||||||
"ballistics_chamber_2",
|
|
||||||
"ballistics_chamber_3",
|
|
||||||
"ballistics_chamber_4",
|
|
||||||
"ballistics_chamber_5",
|
|
||||||
"ballistics_chamber_6",
|
|
||||||
"ballistics_chamber_7",
|
|
||||||
"ballistics_chamber_8",
|
|
||||||
"ballistics_chamber_9",
|
|
||||||
};
|
|
||||||
|
|
||||||
public override void Initialize()
|
|
||||||
{
|
|
||||||
base.Initialize();
|
|
||||||
Owner.GetComponent<RangedWeaponComponent>().FireHandler = TryShoot;
|
|
||||||
_chambers = new Chamber[_chamberCount];
|
|
||||||
for (var i = 0; i < _chambers.Length; i++)
|
|
||||||
{
|
|
||||||
var container = ContainerManagerComponent.Ensure<ContainerSlot>($"ballistics_chamber_{i}", Owner);
|
|
||||||
_chambers[i] = new Chamber(container);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Fires projectiles based on loaded ammo from entity to a coordinate.
|
|
||||||
/// </summary>
|
|
||||||
protected void TryShoot(IEntity user, GridCoordinates clickLocation)
|
|
||||||
{
|
|
||||||
var ammo = GetChambered(FirstChamber)?.GetComponent<BallisticBulletComponent>();
|
|
||||||
CycleChamberedBullet(FirstChamber);
|
|
||||||
if (ammo == null || ammo?.Spent == true || ammo?.Caliber != _caliber)
|
|
||||||
{
|
|
||||||
PlayEmptySound();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ammo.Spent = true;
|
|
||||||
var total_stdev = _spreadStdDevGun + ammo.SpreadStdDev_Ammo;
|
|
||||||
var final_evenspread = _evenSpreadAngleGun + ammo.EvenSpreadAngle_Ammo;
|
|
||||||
var final_velocity = _velocityGun + ammo.Velocity_Ammo;
|
|
||||||
FireAtCoord(user, clickLocation, ammo.ProjectileID, total_stdev, ammo.ProjectilesFired, final_evenspread, final_velocity);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEntity GetChambered(int chamber) => _chambers[chamber].Slot.ContainedEntity;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Loads the next ammo casing into the chamber.
|
|
||||||
/// </summary>
|
|
||||||
protected virtual void CycleChamberedBullet(int chamber) { }
|
|
||||||
|
|
||||||
public IEntity RemoveFromChamber(int chamber)
|
|
||||||
{
|
|
||||||
var c = _chambers[chamber];
|
|
||||||
var loaded = c.Slot.ContainedEntity;
|
|
||||||
if (loaded != null)
|
|
||||||
{
|
|
||||||
c.Slot.Remove(loaded);
|
|
||||||
}
|
|
||||||
return loaded;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected bool LoadIntoChamber(int chamber, IEntity bullet)
|
|
||||||
{
|
|
||||||
if (!bullet.TryGetComponent(out BallisticBulletComponent component))
|
|
||||||
{
|
|
||||||
throw new ArgumentException("entity isn't a bullet.", nameof(bullet));
|
|
||||||
}
|
|
||||||
if (component.Caliber != _caliber)
|
|
||||||
{
|
|
||||||
throw new ArgumentException("entity is of the wrong caliber.", nameof(bullet));
|
|
||||||
}
|
|
||||||
if (GetChambered(chamber) != null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
_chambers[chamber].Slot.Insert(bullet);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PlayEmptySound() => EntitySystem.Get<AudioSystem>().PlayFromEntity(_soundGunEmpty, Owner);
|
|
||||||
|
|
||||||
protected sealed class Chamber
|
|
||||||
{
|
|
||||||
public Chamber(ContainerSlot slot)
|
|
||||||
{
|
|
||||||
Slot = slot;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ContainerSlot Slot { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private const int FirstChamber = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
using Content.Server.GameObjects.Components.Mobs;
|
|
||||||
using Content.Server.GameObjects.Components.Projectiles;
|
|
||||||
using Robust.Server.GameObjects;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
|
||||||
using Robust.Shared.Interfaces.Random;
|
|
||||||
using Robust.Shared.IoC;
|
|
||||||
using Robust.Shared.Map;
|
|
||||||
using Robust.Shared.Maths;
|
|
||||||
using Robust.Shared.Random;
|
|
||||||
using Robust.Shared.Serialization;
|
|
||||||
using Robust.Shared.ViewVariables;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Robust.Server.GameObjects.EntitySystems;
|
|
||||||
using Robust.Shared.GameObjects.Systems;
|
|
||||||
using Robust.Shared.Physics;
|
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Methods to shoot projectiles.
|
|
||||||
/// </summary>
|
|
||||||
public abstract class BaseProjectileWeaponComponent : Component
|
|
||||||
{
|
|
||||||
private string _soundGunshot;
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)]
|
|
||||||
public string SoundGunshot
|
|
||||||
{ get => _soundGunshot; set => _soundGunshot = value; }
|
|
||||||
|
|
||||||
#pragma warning disable 649
|
|
||||||
[Dependency] private IRobustRandom _spreadRandom;
|
|
||||||
#pragma warning restore 649
|
|
||||||
|
|
||||||
public override void ExposeData(ObjectSerializer serializer)
|
|
||||||
{
|
|
||||||
base.ExposeData(serializer);
|
|
||||||
serializer.DataField(ref _soundGunshot, "sound_gunshot", "/Audio/Guns/Gunshots/smg.ogg");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Fires projectile from an entity at a coordinate.
|
|
||||||
/// </summary>
|
|
||||||
protected void FireAtCoord(IEntity source, GridCoordinates coord, string projectileType, double spreadStdDev, int projectilesFired = 1, double evenSpreadAngle = 0, float velocity = 0)
|
|
||||||
{
|
|
||||||
var angle = GetAngleFromClickLocation(source, coord);
|
|
||||||
FireAtAngle(source, angle, projectileType, spreadStdDev, projectilesFired, evenSpreadAngle, velocity);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Fires projectile in the direction of an angle.
|
|
||||||
/// </summary>
|
|
||||||
protected void FireAtAngle(IEntity source, Angle angle, string projectileType = null, double spreadStdDev = 0, int projectilesFired = 1, double evenSpreadAngle = 0, float velocity = 0)
|
|
||||||
{
|
|
||||||
List<Angle> sprayanglechange = null;
|
|
||||||
if (evenSpreadAngle != 0 & projectilesFired > 1)
|
|
||||||
{
|
|
||||||
sprayanglechange = Linspace(-evenSpreadAngle/2, evenSpreadAngle/2, projectilesFired);
|
|
||||||
}
|
|
||||||
for (var i = 1; i <= projectilesFired; i++)
|
|
||||||
{
|
|
||||||
Angle finalangle = angle + Angle.FromDegrees(_spreadRandom.NextGaussian(0, spreadStdDev)) + (sprayanglechange != null ? sprayanglechange[i - 1] : 0);
|
|
||||||
var projectile = Owner.EntityManager.SpawnEntity(projectileType, Owner.Transform.GridPosition);
|
|
||||||
projectile.Transform.GridPosition = source.Transform.GridPosition; //move projectile to entity it is being fired from
|
|
||||||
projectile.GetComponent<ProjectileComponent>().IgnoreEntity(source);//make sure it doesn't hit the source entity
|
|
||||||
var finalvelocity = projectile.GetComponent<ProjectileComponent>().Velocity + velocity;//add velocity
|
|
||||||
var physicsComponent = projectile.GetComponent<PhysicsComponent>();
|
|
||||||
physicsComponent.Status = BodyStatus.InAir;
|
|
||||||
physicsComponent.LinearVelocity = finalangle.ToVec() * finalvelocity;//Rotate the bullets sprite to the correct direction
|
|
||||||
projectile.Transform.LocalRotation = finalangle.Theta;
|
|
||||||
}
|
|
||||||
PlayFireSound();
|
|
||||||
if (source.TryGetComponent(out CameraRecoilComponent recoil))
|
|
||||||
{
|
|
||||||
var recoilVec = angle.ToVec() * -0.15f;
|
|
||||||
recoil.Kick(recoilVec);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PlayFireSound() => EntitySystem.Get<AudioSystem>().PlayFromEntity(_soundGunshot, Owner);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the angle from an entity to a coordinate.
|
|
||||||
/// </summary>
|
|
||||||
protected Angle GetAngleFromClickLocation(IEntity source, GridCoordinates clickLocation) => new Angle(clickLocation.Position - source.Transform.GridPosition.Position);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns a list of numbers that form a set of equal intervals between the start and end value. Used to calculate shotgun spread angles.
|
|
||||||
/// </summary>
|
|
||||||
protected List<Angle> Linspace(double start, double end, int intervals)
|
|
||||||
{
|
|
||||||
var linspace = new List<Angle> { };
|
|
||||||
for (var i = 0; i <= intervals - 1; i++)
|
|
||||||
{
|
|
||||||
linspace.Add(Angle.FromDegrees(start + (end - start) * i / (intervals - 1)));
|
|
||||||
}
|
|
||||||
return linspace;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +1,46 @@
|
|||||||
using System;
|
using System;
|
||||||
using Content.Server.GameObjects.Components.Mobs;
|
using Content.Server.GameObjects.Components.Mobs;
|
||||||
|
using Content.Server.GameObjects.Components.Weapon.Ranged.Barrels;
|
||||||
using Content.Server.GameObjects.EntitySystems;
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
using Content.Server.GameObjects.Components.Movement;
|
|
||||||
using Content.Shared.GameObjects.Components.Weapons.Ranged;
|
using Content.Shared.GameObjects.Components.Weapons.Ranged;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
using Robust.Shared.Interfaces.Network;
|
using Robust.Shared.Interfaces.Network;
|
||||||
using Robust.Shared.Interfaces.Timing;
|
using Robust.Shared.Interfaces.Timing;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Log;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Players;
|
using Robust.Shared.Players;
|
||||||
|
|
||||||
namespace Content.Server.GameObjects.Components.Weapon.Ranged
|
namespace Content.Server.GameObjects.Components.Weapon.Ranged
|
||||||
{
|
{
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
public sealed class RangedWeaponComponent : SharedRangedWeaponComponent
|
public sealed class ServerRangedWeaponComponent : SharedRangedWeaponComponent, IHandSelected
|
||||||
{
|
{
|
||||||
private TimeSpan _lastFireTime;
|
private TimeSpan _lastFireTime;
|
||||||
|
|
||||||
public Func<bool> WeaponCanFireHandler;
|
public Func<bool> WeaponCanFireHandler;
|
||||||
public Func<IEntity, bool> UserCanFireHandler;
|
public Func<IEntity, bool> UserCanFireHandler;
|
||||||
public Action<IEntity, GridCoordinates> FireHandler;
|
public Action<IEntity, GridCoordinates> FireHandler;
|
||||||
|
|
||||||
|
public ServerRangedBarrelComponent Barrel
|
||||||
|
{
|
||||||
|
get => _barrel;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_barrel != null && value != null)
|
||||||
|
{
|
||||||
|
Logger.Error("Tried setting Barrel on RangedWeapon that already has one");
|
||||||
|
throw new InvalidOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
_barrel = value;
|
||||||
|
Dirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private ServerRangedBarrelComponent _barrel;
|
||||||
|
|
||||||
|
private FireRateSelector FireRateSelector => _barrel?.FireRateSelector ?? FireRateSelector.Safety;
|
||||||
|
|
||||||
private bool WeaponCanFire()
|
private bool WeaponCanFire()
|
||||||
{
|
{
|
||||||
@@ -32,12 +52,6 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged
|
|||||||
return (UserCanFireHandler == null || UserCanFireHandler(user)) && ActionBlockerSystem.CanAttack(user);
|
return (UserCanFireHandler == null || UserCanFireHandler(user)) && ActionBlockerSystem.CanAttack(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Fire(IEntity user, GridCoordinates clickLocation)
|
|
||||||
{
|
|
||||||
_lastFireTime = IoCManager.Resolve<IGameTiming>().CurTime;
|
|
||||||
FireHandler?.Invoke(user, clickLocation);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void HandleNetworkMessage(ComponentMessage message, INetChannel channel, ICommonSession session = null)
|
public override void HandleNetworkMessage(ComponentMessage message, INetChannel channel, ICommonSession session = null)
|
||||||
{
|
{
|
||||||
base.HandleNetworkMessage(message, channel, session);
|
base.HandleNetworkMessage(message, channel, session);
|
||||||
@@ -49,7 +63,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged
|
|||||||
|
|
||||||
switch (message)
|
switch (message)
|
||||||
{
|
{
|
||||||
case SyncFirePosMessage msg:
|
case FirePosComponentMessage msg:
|
||||||
var user = session.AttachedEntity;
|
var user = session.AttachedEntity;
|
||||||
if (user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
@@ -61,16 +75,9 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Probably shouldn't be a separate method but don't want anything except NPCs calling this,
|
public override ComponentState GetComponentState()
|
||||||
// and currently ranged combat is handled via player only messages
|
|
||||||
public void AiFire(IEntity entity, GridCoordinates coordinates)
|
|
||||||
{
|
{
|
||||||
if (!entity.HasComponent<AiControllerComponent>())
|
return new RangedWeaponComponentState(FireRateSelector);
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Only AIs should call AiFire");
|
|
||||||
}
|
|
||||||
|
|
||||||
_tryFire(entity, coordinates);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void _tryFire(IEntity user, GridCoordinates coordinates)
|
private void _tryFire(IEntity user, GridCoordinates coordinates)
|
||||||
@@ -91,12 +98,19 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged
|
|||||||
|
|
||||||
var curTime = IoCManager.Resolve<IGameTiming>().CurTime;
|
var curTime = IoCManager.Resolve<IGameTiming>().CurTime;
|
||||||
var span = curTime - _lastFireTime;
|
var span = curTime - _lastFireTime;
|
||||||
if (span.TotalSeconds < 1 / FireRate)
|
if (span.TotalSeconds < 1 / _barrel.FireRate)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Fire(user, coordinates);
|
_lastFireTime = curTime;
|
||||||
|
FireHandler?.Invoke(user, coordinates);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Probably a better way to do this.
|
||||||
|
void IHandSelected.HandSelected(HandSelectedEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
Dirty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using Content.Shared.GameObjects.Components.Weapons;
|
||||||
|
using Content.Shared.Physics;
|
||||||
|
using Robust.Server.GameObjects.EntitySystems;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Physics;
|
||||||
|
using Robust.Shared.Interfaces.Timing;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
|
||||||
|
namespace Content.Server.GameObjects.Components.Weapon
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class ServerFlashableComponent : SharedFlashableComponent
|
||||||
|
{
|
||||||
|
private double _duration;
|
||||||
|
private TimeSpan _lastFlash;
|
||||||
|
|
||||||
|
public void Flash(double duration)
|
||||||
|
{
|
||||||
|
var timing = IoCManager.Resolve<IGameTiming>();
|
||||||
|
_lastFlash = timing.CurTime;
|
||||||
|
_duration = duration;
|
||||||
|
Dirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ComponentState GetComponentState()
|
||||||
|
{
|
||||||
|
return new FlashComponentState(_duration, _lastFlash);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void FlashAreaHelper(IEntity source, double range, double duration, string sound = null)
|
||||||
|
{
|
||||||
|
var physicsManager = IoCManager.Resolve<IPhysicsManager>();
|
||||||
|
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||||
|
|
||||||
|
foreach (var entity in entityManager.GetEntities(new TypeEntityQuery(typeof(ServerFlashableComponent))))
|
||||||
|
{
|
||||||
|
if (source.Transform.MapID != entity.Transform.MapID ||
|
||||||
|
entity == source)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var direction = entity.Transform.WorldPosition - source.Transform.WorldPosition;
|
||||||
|
|
||||||
|
if (direction.Length > range)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Direction will be zero if they're hit with the source only I think
|
||||||
|
if (direction == Vector2.Zero)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ray = new CollisionRay(source.Transform.WorldPosition, direction.Normalized, (int) CollisionGroup.Opaque);
|
||||||
|
var rayCastResults = physicsManager.IntersectRay(source.Transform.MapID, ray, direction.Length, source, false).ToList();
|
||||||
|
if (rayCastResults.Count == 0 ||
|
||||||
|
rayCastResults[0].HitEntity != entity)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var flashable = entity.GetComponent<ServerFlashableComponent>();
|
||||||
|
flashable.Flash(duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sound != null)
|
||||||
|
{
|
||||||
|
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<AudioSystem>().PlayAtCoords(sound, source.Transform.GridPosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,4 +23,4 @@ namespace Content.Server.GameObjects.EntitySystems
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -29,5 +29,6 @@
|
|||||||
<Folder Include="GameObjects\Components\Construction\" />
|
<Folder Include="GameObjects\Components\Construction\" />
|
||||||
<Folder Include="GameObjects\Components\Trigger\" />
|
<Folder Include="GameObjects\Components\Trigger\" />
|
||||||
<EmbeddedResource Include="Text\Names\*.txt" />
|
<EmbeddedResource Include="Text\Names\*.txt" />
|
||||||
|
<Folder Include="GameObjects\Components\Visualizers" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
using Robust.Shared.Serialization;
|
|
||||||
|
|
||||||
namespace Content.Shared.GameObjects.Components.Weapons.Ranged
|
|
||||||
{
|
|
||||||
[Serializable, NetSerializable]
|
|
||||||
public class BallisticMagazineWeaponComponentState : ComponentState
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// True if a bullet is chambered.
|
|
||||||
/// </summary>
|
|
||||||
public bool Chambered { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Count of bullets in the magazine.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// Null if no magazine is inserted.
|
|
||||||
/// </remarks>
|
|
||||||
public (int count, int max)? MagazineCount { get; }
|
|
||||||
|
|
||||||
public BallisticMagazineWeaponComponentState(bool chambered, (int count, int max)? magazineCount) : base(ContentNetIDs.BALLISTIC_MAGAZINE_WEAPON)
|
|
||||||
{
|
|
||||||
Chambered = chambered;
|
|
||||||
MagazineCount = magazineCount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BMW is "Ballistic Magazine Weapon" here.
|
|
||||||
/// <summary>
|
|
||||||
/// Fired server -> client when the magazine in a Ballistic Magazine Weapon got auto-ejected.
|
|
||||||
/// </summary>
|
|
||||||
[Serializable, NetSerializable]
|
|
||||||
public sealed class BmwComponentAutoEjectedMessage : ComponentMessage
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
using System;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels
|
||||||
|
{
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum AmmoVisuals
|
||||||
|
{
|
||||||
|
AmmoCount,
|
||||||
|
AmmoMax,
|
||||||
|
Spent,
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum MagazineBarrelVisuals
|
||||||
|
{
|
||||||
|
MagLoaded
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public enum BarrelBoltVisuals
|
||||||
|
{
|
||||||
|
BoltOpen,
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public class MagazineBarrelComponentState : ComponentState
|
||||||
|
{
|
||||||
|
public bool Chambered { get; }
|
||||||
|
public FireRateSelector FireRateSelector { get; }
|
||||||
|
public (int count, int max)? Magazine { get; }
|
||||||
|
public string SoundGunshot { get; }
|
||||||
|
|
||||||
|
public MagazineBarrelComponentState(
|
||||||
|
bool chambered,
|
||||||
|
FireRateSelector fireRateSelector,
|
||||||
|
(int count, int max)? magazine,
|
||||||
|
string soundGunshot) :
|
||||||
|
base(ContentNetIDs.MAGAZINE_BARREL)
|
||||||
|
{
|
||||||
|
Chambered = chambered;
|
||||||
|
FireRateSelector = fireRateSelector;
|
||||||
|
Magazine = magazine;
|
||||||
|
SoundGunshot = soundGunshot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Robust.Shared.Serialization;
|
|
||||||
|
|
||||||
namespace Content.Shared.GameObjects.Components.Weapons.Ranged
|
|
||||||
{
|
|
||||||
[Serializable, NetSerializable]
|
|
||||||
public enum BallisticMagazineVisuals
|
|
||||||
{
|
|
||||||
AmmoCapacity,
|
|
||||||
AmmoLeft,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Robust.Shared.Serialization;
|
|
||||||
|
|
||||||
namespace Content.Shared.GameObjects.Components.Weapons.Ranged
|
|
||||||
{
|
|
||||||
[Serializable, NetSerializable]
|
|
||||||
public enum BallisticMagazineWeaponVisuals
|
|
||||||
{
|
|
||||||
MagazineLoaded,
|
|
||||||
AmmoCapacity,
|
|
||||||
AmmoLeft,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Shared.GameObjects.Components.Weapons.Ranged
|
||||||
|
{
|
||||||
|
public abstract class SharedRangedBarrelComponent : Component
|
||||||
|
{
|
||||||
|
public abstract FireRateSelector FireRateSelector { get; }
|
||||||
|
public abstract FireRateSelector AllRateSelectors { get; }
|
||||||
|
public abstract float FireRate { get; }
|
||||||
|
public abstract int ShotsLeft { get; }
|
||||||
|
public abstract int Capacity { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum FireRateSelector
|
||||||
|
{
|
||||||
|
Safety = 0,
|
||||||
|
Single = 1 << 0,
|
||||||
|
Automatic = 1 << 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,40 +5,35 @@ using Robust.Shared.Serialization;
|
|||||||
|
|
||||||
namespace Content.Shared.GameObjects.Components.Weapons.Ranged
|
namespace Content.Shared.GameObjects.Components.Weapons.Ranged
|
||||||
{
|
{
|
||||||
public class SharedRangedWeaponComponent : Component
|
public abstract class SharedRangedWeaponComponent : Component
|
||||||
{
|
{
|
||||||
private float _fireRate;
|
// Each RangedWeapon should have a RangedWeapon component +
|
||||||
private bool _automatic;
|
// some kind of RangedBarrelComponent (this dictates what ammo is retrieved).
|
||||||
public override string Name => "RangedWeapon";
|
public override string Name => "RangedWeapon";
|
||||||
public override uint? NetID => ContentNetIDs.RANGED_WEAPON;
|
public override uint? NetID => ContentNetIDs.RANGED_WEAPON;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
[Serializable, NetSerializable]
|
||||||
/// If true, this weapon is fully automatic, holding down left mouse button will keep firing it.
|
public sealed class RangedWeaponComponentState : ComponentState
|
||||||
/// </summary>
|
{
|
||||||
public bool Automatic => _automatic;
|
public FireRateSelector FireRateSelector { get; }
|
||||||
|
|
||||||
/// <summary>
|
public RangedWeaponComponentState(
|
||||||
/// If the weapon is automatic, controls how many shots can be fired per second.
|
FireRateSelector fireRateSelector
|
||||||
/// </summary>
|
) : base(ContentNetIDs.RANGED_WEAPON)
|
||||||
public float FireRate => _fireRate;
|
|
||||||
|
|
||||||
public override void ExposeData(ObjectSerializer serializer)
|
|
||||||
{
|
{
|
||||||
base.ExposeData(serializer);
|
FireRateSelector = fireRateSelector;
|
||||||
|
|
||||||
serializer.DataField(ref _fireRate, "firerate", 4);
|
|
||||||
serializer.DataField(ref _automatic, "automatic", false);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
[Serializable, NetSerializable]
|
||||||
protected class SyncFirePosMessage : ComponentMessage
|
public sealed class FirePosComponentMessage : ComponentMessage
|
||||||
|
{
|
||||||
|
public GridCoordinates Target { get; }
|
||||||
|
|
||||||
|
public FirePosComponentMessage(GridCoordinates target)
|
||||||
{
|
{
|
||||||
public readonly GridCoordinates Target;
|
Target = target;
|
||||||
|
|
||||||
public SyncFirePosMessage(GridCoordinates target)
|
|
||||||
{
|
|
||||||
Target = target;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using System;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Shared.GameObjects.Components.Weapons
|
||||||
|
{
|
||||||
|
public class SharedFlashableComponent : Component
|
||||||
|
{
|
||||||
|
public override string Name => "Flashable";
|
||||||
|
public override uint? NetID => ContentNetIDs.FLASHABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public class FlashComponentState : ComponentState
|
||||||
|
{
|
||||||
|
public double Duration { get; }
|
||||||
|
public TimeSpan Time { get; }
|
||||||
|
|
||||||
|
public FlashComponentState(double duration, TimeSpan time) : base(ContentNetIDs.FLASHABLE)
|
||||||
|
{
|
||||||
|
Duration = duration;
|
||||||
|
Time = time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
{
|
{
|
||||||
public const uint DAMAGEABLE = 1000;
|
public const uint DAMAGEABLE = 1000;
|
||||||
public const uint DESTRUCTIBLE = 1001;
|
public const uint DESTRUCTIBLE = 1001;
|
||||||
public const uint BALLISTIC_MAGAZINE_WEAPON = 1002;
|
public const uint MAGAZINE_BARREL = 1002;
|
||||||
public const uint HANDS = 1003;
|
public const uint HANDS = 1003;
|
||||||
public const uint SOLUTION = 1004;
|
public const uint SOLUTION = 1004;
|
||||||
public const uint STORAGE = 1005;
|
public const uint STORAGE = 1005;
|
||||||
@@ -50,7 +50,8 @@
|
|||||||
public const uint PDA = 1044;
|
public const uint PDA = 1044;
|
||||||
public const uint PATHFINDER_DEBUG = 1045;
|
public const uint PATHFINDER_DEBUG = 1045;
|
||||||
public const uint AI_DEBUG = 1046;
|
public const uint AI_DEBUG = 1046;
|
||||||
|
public const uint FLASHABLE = 1047;
|
||||||
|
|
||||||
// Net IDs for integration tests.
|
// Net IDs for integration tests.
|
||||||
public const uint PREDICTION_TEST = 10001;
|
public const uint PREDICTION_TEST = 10001;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ namespace Content.Shared.Utility
|
|||||||
}
|
}
|
||||||
|
|
||||||
var preround = toOne * (levels - 1);
|
var preround = toOne * (levels - 1);
|
||||||
if (toOne <= threshold || levels == 2)
|
if (toOne <= threshold || levels <= 2)
|
||||||
{
|
{
|
||||||
return (int)Math.Ceiling(preround);
|
return (int)Math.Ceiling(preround);
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
Resources/Audio/Guns/Bolt/lmg_bolt_closed.ogg
Normal file
BIN
Resources/Audio/Guns/Bolt/lmg_bolt_closed.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/Guns/Bolt/lmg_bolt_open.ogg
Normal file
BIN
Resources/Audio/Guns/Bolt/lmg_bolt_open.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/Guns/Bolt/rifle_bolt_closed.ogg
Normal file
BIN
Resources/Audio/Guns/Bolt/rifle_bolt_closed.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/Guns/Bolt/rifle_bolt_open.ogg
Normal file
BIN
Resources/Audio/Guns/Bolt/rifle_bolt_open.ogg
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user