(Re)Adds open bolt animations for gun sprites (#17219)

Co-authored-by: and_a <and_a@DESKTOP-RJENGIR>
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
This commit is contained in:
TaralGit
2023-08-12 22:58:07 -07:00
committed by GitHub
parent 82180fd04c
commit 8acac895fc
72 changed files with 726 additions and 353 deletions

View File

@@ -1,7 +1,9 @@
using Content.Shared.Examine;
using Content.Client.Weapons.Ranged.Components;
using Content.Shared.Weapons.Ranged;
using Content.Shared.Weapons.Ranged.Components;
using Content.Shared.Weapons.Ranged.Events;
using Content.Shared.Weapons.Ranged.Systems;
using Robust.Client.GameObjects;
using Robust.Shared.Containers;
namespace Content.Client.Weapons.Ranged.Systems;
@@ -13,6 +15,27 @@ public sealed partial class GunSystem
base.InitializeChamberMagazine();
SubscribeLocalEvent<ChamberMagazineAmmoProviderComponent, AmmoCounterControlEvent>(OnChamberMagazineCounter);
SubscribeLocalEvent<ChamberMagazineAmmoProviderComponent, UpdateAmmoCounterEvent>(OnChamberMagazineAmmoUpdate);
SubscribeLocalEvent<ChamberMagazineAmmoProviderComponent, AppearanceChangeEvent>(OnChamberMagazineAppearance);
}
private void OnChamberMagazineAppearance(EntityUid uid, ChamberMagazineAmmoProviderComponent component, ref AppearanceChangeEvent args)
{
if (args.Sprite == null ||
!args.Sprite.LayerMapTryGet(GunVisualLayers.Base, out var boltLayer) ||
!Appearance.TryGetData(uid, AmmoVisuals.BoltClosed, out bool boltClosed))
{
return;
}
// Maybe re-using base layer for this will bite me someday but screw you future sloth.
if (boltClosed)
{
args.Sprite.LayerSetState(boltLayer, "base");
}
else
{
args.Sprite.LayerSetState(boltLayer, "bolt-open");
}
}
protected override void OnMagazineSlotChange(EntityUid uid, MagazineAmmoProviderComponent component, ContainerModifiedMessage args)

View File

@@ -174,7 +174,7 @@ public sealed partial class GunSystem : SharedGunSystem
if (!cartridge.DeleteOnSpawn && !Containers.IsEntityInContainer(ent!.Value))
EjectCartridge(ent.Value, angle);
Dirty(cartridge);
Dirty(ent!.Value, cartridge);
break;
// Ammo shoots itself
case AmmoComponent newAmmo:

View File

@@ -39,21 +39,11 @@ public sealed partial class BallisticAmmoProviderComponent : Component
public List<EntityUid> Entities = new();
/// <summary>
/// Will the ammoprovider automatically cycle through rounds or does it need doing manually.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("autoCycle")]
public bool AutoCycle = true;
/// <summary>
/// Is the gun ready to shoot; if AutoCycle is true then this will always stay true and not need to be manually done.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("cycled")]
[AutoNetworkedField]
public bool Cycled = true;
/// <summary>
/// Is the magazine allowed to be cycled
/// Is the magazine allowed to be manually cycled to eject a cartridge.
/// </summary>
/// <remarks>
/// Set to false for entities like turrets to avoid users being able to cycle them.
/// </remarks>
[ViewVariables(VVAccess.ReadWrite), DataField("cycleable")]
[AutoNetworkedField]
public bool Cycleable = true;

View File

@@ -1,7 +1,31 @@
using Robust.Shared.Audio;
namespace Content.Shared.Weapons.Ranged.Components;
/// <summary>
/// Chamber + mags in one package. If you need just magazine then use <see cref="MagazineAmmoProviderComponent"/>
/// </summary>
[RegisterComponent]
public sealed class ChamberMagazineAmmoProviderComponent : MagazineAmmoProviderComponent {}
[RegisterComponent, AutoGenerateComponentState]
public sealed partial class ChamberMagazineAmmoProviderComponent : MagazineAmmoProviderComponent
{
/// <summary>
/// If the gun has a bolt and whether that bolt is closed. Firing is impossible
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("boltClosed"), AutoNetworkedField]
public bool? BoltClosed = false;
/// <summary>
/// Does the gun automatically open and close the bolt upon shooting.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("autoCycle"), AutoNetworkedField]
public bool AutoCycle = true;
[ViewVariables(VVAccess.ReadWrite), DataField("soundBoltClosed"), AutoNetworkedField]
public SoundSpecifier? BoltClosedSound = new SoundPathSpecifier("/Audio/Weapons/Guns/Bolt/rifle_bolt_closed.ogg");
[ViewVariables(VVAccess.ReadWrite), DataField("soundBoltOpened"), AutoNetworkedField]
public SoundSpecifier? BoltOpenedSound = new SoundPathSpecifier("/Audio/Weapons/Guns/Bolt/rifle_bolt_open.ogg");
[ViewVariables(VVAccess.ReadWrite), DataField("soundRack"), AutoNetworkedField]
public SoundSpecifier? RackSound = new SoundPathSpecifier("/Audio/Weapons/Guns/Cock/ltrifle_cock.ogg");
}

View File

@@ -11,6 +11,11 @@ public sealed class TakeAmmoEvent : EntityEventArgs
public readonly int Shots;
public List<(EntityUid? Entity, IShootable Shootable)> Ammo;
/// <summary>
/// If no ammo returned what is the reason for it?
/// </summary>
public string? Reason;
/// <summary>
/// Coordinates to spawn the ammo at.
/// </summary>

View File

@@ -132,12 +132,17 @@ public abstract partial class SharedGunSystem
{
if (!args.CanAccess || !args.CanInteract || args.Hands == null || !component.Cycleable)
return;
args.Verbs.Add(new Verb()
if (component.Cycleable)
{
Text = Loc.GetString("gun-ballistic-cycle"),
Disabled = GetBallisticShots(component) == 0,
Act = () => ManualCycle(uid, component, Transform(uid).MapPosition, args.User),
});
args.Verbs.Add(new Verb()
{
Text = Loc.GetString("gun-ballistic-cycle"),
Disabled = GetBallisticShots(component) == 0,
Act = () => ManualCycle(uid, component, Transform(uid).MapPosition, args.User),
});
}
}
private void OnBallisticExamine(EntityUid uid, BallisticAmmoProviderComponent component, ExaminedEvent args)
@@ -165,8 +170,6 @@ public abstract partial class SharedGunSystem
Audio.PlayPredicted(component.SoundRack, uid, user);
var shots = GetBallisticShots(component);
component.Cycled = true;
Cycle(uid, component, coordinates);
var text = Loc.GetString(shots == 0 ? "gun-ballistic-cycled-empty" : "gun-ballistic-cycled");
@@ -181,6 +184,9 @@ public abstract partial class SharedGunSystem
private void OnBallisticInit(EntityUid uid, BallisticAmmoProviderComponent component, ComponentInit args)
{
component.Container = Containers.EnsureContainer<Container>(uid, "ballistic-ammo");
// TODO: This is called twice though we need to support loading appearance data (and we need to call it on MapInit
// to ensure it's correct).
UpdateBallisticAppearance(uid, component);
}
private void OnBallisticMapInit(EntityUid uid, BallisticAmmoProviderComponent component, MapInitEvent args)
@@ -190,6 +196,7 @@ public abstract partial class SharedGunSystem
if (component.FillProto != null)
{
component.UnspawnedCount = Math.Max(0, component.Capacity - component.Container.ContainedEntities.Count);
UpdateBallisticAppearance(uid, component);
Dirty(uid, component);
}
}
@@ -203,9 +210,6 @@ public abstract partial class SharedGunSystem
{
for (var i = 0; i < args.Shots; i++)
{
if (!component.Cycled)
break;
EntityUid entity;
if (component.Entities.Count > 0)
@@ -213,14 +217,6 @@ public abstract partial class SharedGunSystem
entity = component.Entities[^1];
args.Ammo.Add((entity, EnsureComp<AmmoComponent>(entity)));
// Leave the entity as is if it doesn't auto cycle
// TODO: Suss this out with NewAmmoComponent as I don't think it gets removed from container properly
if (!component.AutoCycle)
{
return;
}
component.Entities.RemoveAt(component.Entities.Count - 1);
component.Container.Remove(entity);
}
@@ -229,25 +225,6 @@ public abstract partial class SharedGunSystem
component.UnspawnedCount--;
entity = Spawn(component.FillProto, args.Coordinates);
args.Ammo.Add((entity, EnsureComp<AmmoComponent>(entity)));
// Put it back in if it doesn't auto-cycle
if (HasComp<CartridgeAmmoComponent>(entity) && !component.AutoCycle)
{
if (!entity.IsClientSide())
{
component.Entities.Add(entity);
component.Container.Insert(entity);
}
else
{
component.UnspawnedCount++;
}
}
}
if (!component.AutoCycle)
{
component.Cycled = false;
}
}

View File

@@ -1,5 +1,7 @@
using System.Diagnostics.CodeAnalysis;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Examine;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Verbs;
using Content.Shared.Weapons.Ranged.Components;
@@ -14,21 +16,244 @@ public abstract partial class SharedGunSystem
protected virtual void InitializeChamberMagazine()
{
SubscribeLocalEvent<ChamberMagazineAmmoProviderComponent, ComponentStartup>(OnChamberStartup);
SubscribeLocalEvent<ChamberMagazineAmmoProviderComponent, TakeAmmoEvent>(OnChamberMagazineTakeAmmo);
SubscribeLocalEvent<ChamberMagazineAmmoProviderComponent, GetAmmoCountEvent>(OnChamberAmmoCount);
/*
* Open and close bolts are separate verbs.
* Racking does both in one hit and has a different sound (to avoid RSI + sounds cooler).
*/
SubscribeLocalEvent<ChamberMagazineAmmoProviderComponent, GetVerbsEvent<Verb>>(OnChamberVerb);
SubscribeLocalEvent<ChamberMagazineAmmoProviderComponent, GetVerbsEvent<ActivationVerb>>(OnChamberActivationVerb);
SubscribeLocalEvent<ChamberMagazineAmmoProviderComponent, GetVerbsEvent<InteractionVerb>>(OnChamberInteractionVerb);
SubscribeLocalEvent<ChamberMagazineAmmoProviderComponent, GetVerbsEvent<AlternativeVerb>>(OnMagazineVerb);
SubscribeLocalEvent<ChamberMagazineAmmoProviderComponent, ActivateInWorldEvent>(OnChamberActivate);
SubscribeLocalEvent<ChamberMagazineAmmoProviderComponent, UseInHandEvent>(OnChamberUse);
SubscribeLocalEvent<ChamberMagazineAmmoProviderComponent, EntInsertedIntoContainerMessage>(OnMagazineSlotChange);
SubscribeLocalEvent<ChamberMagazineAmmoProviderComponent, EntRemovedFromContainerMessage>(OnMagazineSlotChange);
SubscribeLocalEvent<ChamberMagazineAmmoProviderComponent, UseInHandEvent>(OnMagazineUse);
SubscribeLocalEvent<ChamberMagazineAmmoProviderComponent, ExaminedEvent>(OnChamberMagazineExamine);
}
private void OnChamberStartup(EntityUid uid, ChamberMagazineAmmoProviderComponent component, ComponentStartup args)
{
// Appearance data doesn't get serialized and want to make sure this is correct on spawn (regardless of MapInit) so.
if (component.BoltClosed != null)
{
Appearance.SetData(uid, AmmoVisuals.BoltClosed, component.BoltClosed.Value);
}
}
private void OnChamberVerb(EntityUid uid, ChamberMagazineAmmoProviderComponent component, GetVerbsEvent<Verb> args)
{
if (component.BoltClosed != null)
{
args.Verbs.Add(new Verb()
{
Text = component.BoltClosed.Value ? Loc.GetString("gun-chamber-bolt-open") : Loc.GetString("gun-chamber-bolt-close"),
Act = () => ToggleBolt(uid, component, args.User),
});
}
}
private void OnChamberActivate(EntityUid uid, ChamberMagazineAmmoProviderComponent component, ActivateInWorldEvent args)
{
if (args.Handled || component.BoltClosed == null)
return;
args.Handled = true;
ToggleBolt(uid, component, args.User);
}
private void OnChamberUse(EntityUid uid, ChamberMagazineAmmoProviderComponent component, UseInHandEvent args)
{
if (args.Handled || component.BoltClosed == null)
return;
args.Handled = true;
UseChambered(uid, component, args.User);
}
private void OnChamberActivationVerb(EntityUid uid, ChamberMagazineAmmoProviderComponent component, GetVerbsEvent<ActivationVerb> args)
{
if (!args.CanAccess || !args.CanInteract || component.BoltClosed == null)
return;
args.Verbs.Add(new ActivationVerb()
{
Text = Loc.GetString("gun-chamber-rack"),
Act = () =>
{
UseChambered(uid, component, args.User);
}
});
}
private void UseChambered(EntityUid uid, ChamberMagazineAmmoProviderComponent component, EntityUid? user = null)
{
if (component.BoltClosed == null || !component.BoltClosed.Value)
return;
if (TryTakeChamberEntity(uid, out var chamberEnt))
{
if (_netManager.IsServer)
{
EjectCartridge(chamberEnt.Value);
}
else
{
// Similar to below just due to prediction.
TransformSystem.DetachParentToNull(chamberEnt.Value, Transform(chamberEnt.Value));
}
}
CycleCartridge(uid, component, user);
if (component.BoltClosed.Value)
{
Audio.PlayPredicted(component.RackSound, uid, user);
}
}
private void OnChamberInteractionVerb(EntityUid uid, ChamberMagazineAmmoProviderComponent component, GetVerbsEvent<InteractionVerb> args)
{
if (!args.CanAccess || !args.CanInteract || component.BoltClosed == null)
return;
args.Verbs.Add(new InteractionVerb()
{
Text = component.BoltClosed.Value ? Loc.GetString("gun-chamber-bolt-open") : Loc.GetString("gun-chamber-bolt-close"),
Act = () =>
{
// Just toggling might be more user friendly instead of trying to set to whatever they think?
ToggleBolt(uid, component, args.User);
}
});
}
public void SetBoltClosed(EntityUid uid, ChamberMagazineAmmoProviderComponent component, bool value, EntityUid? user = null, AppearanceComponent? appearance = null, ItemSlotsComponent? slots = null)
{
if (component.BoltClosed == null || value == component.BoltClosed)
return;
Resolve(uid, ref appearance, ref slots, false);
Appearance.SetData(uid, AmmoVisuals.BoltClosed, value, appearance);
if (value)
{
CycleCartridge(uid, component, user, appearance);
if (user != null)
PopupSystem.PopupClient(Loc.GetString("gun-chamber-bolt-closed"), uid, user.Value);
if (slots != null)
{
_slots.SetLock(uid, ChamberSlot, true, slots);
}
Audio.PlayPredicted(component.BoltClosedSound, uid, user);
}
else
{
if (TryTakeChamberEntity(uid, out var chambered))
{
if (_netManager.IsServer)
{
EjectCartridge(chambered.Value);
}
else
{
// Prediction moment
// The problem is client will dump the cartridge on the ground and the new server state
// won't correspond due to randomness so looks weird
// but we also need to always take it from the chamber or else ammocount won't be correct.
TransformSystem.DetachParentToNull(chambered.Value, Transform(chambered.Value));
}
UpdateAmmoCount(uid);
}
if (user != null)
PopupSystem.PopupClient(Loc.GetString("gun-chamber-bolt-opened"), uid, user.Value);
if (slots != null)
{
_slots.SetLock(uid, ChamberSlot, false, slots);
}
Audio.PlayPredicted(component.BoltOpenedSound, uid, user);
}
component.BoltClosed = value;
Dirty(uid, component);
}
/// <summary>
/// Tries to take ammo from the magazine and insert into the chamber.
/// </summary>
private void CycleCartridge(EntityUid uid, ChamberMagazineAmmoProviderComponent component, EntityUid? user = null, AppearanceComponent? appearance = null)
{
// Try to put a new round in if possible.
var magEnt = GetMagazineEntity(uid);
// Similar to what takeammo does though that uses an optimised version where
// multiple bullets may be fired in a single tick.
if (magEnt != null)
{
var relayedArgs = new TakeAmmoEvent(1,
new List<(EntityUid? Entity, IShootable Shootable)>(),
Transform(uid).Coordinates,
user);
RaiseLocalEvent(magEnt.Value, relayedArgs);
if (relayedArgs.Ammo.Count > 0)
{
var newChamberEnt = relayedArgs.Ammo[0].Entity;
TryInsertChamber(uid, newChamberEnt!.Value);
var ammoEv = new GetAmmoCountEvent();
RaiseLocalEvent(magEnt.Value, ref ammoEv);
FinaliseMagazineTakeAmmo(uid, component, ammoEv.Count, ammoEv.Capacity, user, appearance);
UpdateAmmoCount(uid);
// Clientside reconciliation things
if (_netManager.IsClient)
{
foreach (var (ent, _) in relayedArgs.Ammo)
{
Del(ent!.Value);
}
}
}
else
{
UpdateAmmoCount(uid);
}
}
}
public void ToggleBolt(EntityUid uid, ChamberMagazineAmmoProviderComponent component, EntityUid? user = null)
{
if (component.BoltClosed == null)
return;
SetBoltClosed(uid, component, !component.BoltClosed.Value, user);
}
private void OnChamberMagazineExamine(EntityUid uid, ChamberMagazineAmmoProviderComponent component, ExaminedEvent args)
{
if (!args.IsInDetailsRange)
return;
var (count, _) = GetChamberMagazineCountCapacity(uid, component);
if (component.BoltClosed != null)
{
args.PushMarkup(Loc.GetString("gun-chamber-bolt", ("bolt", component.BoltClosed), ("color", component.BoltClosed.Value ? Color.FromHex("#94e1f2") : Color.FromHex("#f29d94"))));
}
args.PushMarkup(Loc.GetString("gun-magazine-examine", ("color", AmmoExamineColor), ("count", count)));
}
@@ -88,52 +313,80 @@ public abstract partial class SharedGunSystem
private void OnChamberMagazineTakeAmmo(EntityUid uid, ChamberMagazineAmmoProviderComponent component, TakeAmmoEvent args)
{
if (component.BoltClosed == false)
{
args.Reason = Loc.GetString("gun-chamber-bolt-ammo");
return;
}
// So chamber logic is kinda sussier than the others
// Essentially we want to treat the chamber as a potentially free slot and then the mag as the remaining slots
// i.e. if we shoot 3 times, then we use the chamber once (regardless if it's empty or not) and 2 from the mag
// We move the n + 1 shot into the chamber as we essentially treat it like a stack.
TryComp<AppearanceComponent>(uid, out var appearance);
if (TryTakeChamberEntity(uid, out var chamberEnt))
EntityUid? chamberEnt;
// Normal behaviour for guns.
if (component.AutoCycle)
{
if (TryTakeChamberEntity(uid, out chamberEnt))
{
args.Ammo.Add((chamberEnt.Value, EnsureComp<AmmoComponent>(chamberEnt.Value)));
}
// No ammo returned.
else
{
return;
}
var magEnt = GetMagazineEntity(uid);
// Pass an event to the magazine to get more (to refill chamber or for shooting).
if (magEnt != null)
{
// We pass in Shots not Shots - 1 as we'll take the last entity and move it into the chamber.
var relayedArgs = new TakeAmmoEvent(args.Shots, new List<(EntityUid? Entity, IShootable Shootable)>(), args.Coordinates, args.User);
RaiseLocalEvent(magEnt.Value, relayedArgs);
// Put in the nth slot back into the chamber
// Rest of the ammo gets shot
if (relayedArgs.Ammo.Count > 0)
{
var newChamberEnt = relayedArgs.Ammo[^1].Entity;
TryInsertChamber(uid, newChamberEnt!.Value);
}
// Anything above the chamber-refill amount gets fired.
for (var i = 0; i < relayedArgs.Ammo.Count - 1; i++)
{
args.Ammo.Add(relayedArgs.Ammo[i]);
}
// If no more ammo then open bolt.
if (relayedArgs.Ammo.Count == 0)
{
SetBoltClosed(uid, component, false, user: args.User, appearance: appearance);
}
}
else
{
Appearance.SetData(uid, AmmoVisuals.MagLoaded, false, appearance);
return;
}
var ammoEv = new GetAmmoCountEvent();
RaiseLocalEvent(magEnt.Value, ref ammoEv);
FinaliseMagazineTakeAmmo(uid, component, ammoEv.Count, ammoEv.Capacity, args.User, appearance);
}
// If gun doesn't autocycle (e.g. bolt-action weapons) then we leave the chambered entity in there but still return it.
else if (Containers.TryGetContainer(uid, ChamberSlot, out var container) &&
container is ContainerSlot { ContainedEntity: not null } slot)
{
// Shooting code won't eject it if it's still contained.
chamberEnt = slot.ContainedEntity;
args.Ammo.Add((chamberEnt.Value, EnsureComp<AmmoComponent>(chamberEnt.Value)));
}
var magEnt = GetMagazineEntity(uid);
// Pass an event to the magazine to get more (to refill chamber or for shooting).
if (magEnt != null)
{
// We pass in Shots not Shots - 1 as we'll take the last entity and move it into the chamber.
var relayedArgs = new TakeAmmoEvent(args.Shots, new List<(EntityUid? Entity, IShootable Shootable)>(), args.Coordinates, args.User);
RaiseLocalEvent(magEnt.Value, relayedArgs);
// Put in the nth slot back into the chamber
// Rest of the ammo gets shot
if (relayedArgs.Ammo.Count > 0)
{
var newChamberEnt = relayedArgs.Ammo[^1].Entity;
TryInsertChamber(uid, newChamberEnt!.Value);
}
// Anything above the chamber-refill amount gets fired.
for (var i = 0; i < relayedArgs.Ammo.Count - 1; i++)
{
args.Ammo.Add(relayedArgs.Ammo[i]);
}
}
else
{
Appearance.SetData(uid, AmmoVisuals.MagLoaded, false, appearance);
return;
}
var count = chamberEnt != null ? 1 : 0;
const int capacity = 1;
var ammoEv = new GetAmmoCountEvent();
RaiseLocalEvent(magEnt.Value, ref ammoEv);
FinaliseMagazineTakeAmmo(uid, component, args, count + ammoEv.Count, capacity + ammoEv.Capacity, appearance);
}
}

View File

@@ -32,7 +32,7 @@ public partial class SharedGunSystem
private bool TryGetClothingSlotEntity(EntityUid uid, ClothingSlotAmmoProviderComponent component, [NotNullWhen(true)] out EntityUid? slotEntity)
{
slotEntity = null;
if (!_container.TryGetContainingContainer(uid, out var container))
if (!Containers.TryGetContainingContainer(uid, out var container))
return false;
var user = container.Owner;

View File

@@ -8,9 +8,6 @@ namespace Content.Shared.Weapons.Ranged.Systems;
public partial class SharedGunSystem
{
[Dependency] private readonly INetManager _netMan = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
private void InitializeContainer()
{
SubscribeLocalEvent<ContainerAmmoProviderComponent, TakeAmmoEvent>(OnContainerTakeAmmo);
@@ -20,7 +17,7 @@ public partial class SharedGunSystem
private void OnContainerTakeAmmo(EntityUid uid, ContainerAmmoProviderComponent component, TakeAmmoEvent args)
{
component.ProviderUid ??= uid;
if (!_container.TryGetContainer(component.ProviderUid.Value, component.Container, out var container))
if (!Containers.TryGetContainer(component.ProviderUid.Value, component.Container, out var container))
return;
for (var i = 0; i < args.Shots; i++)
@@ -30,7 +27,7 @@ public partial class SharedGunSystem
var ent = container.ContainedEntities[0];
if (_netMan.IsServer)
if (_netManager.IsServer)
container.Remove(ent);
args.Ammo.Add((ent, EnsureComp<AmmoComponent>(ent)));
@@ -40,7 +37,7 @@ public partial class SharedGunSystem
private void OnContainerAmmoCount(EntityUid uid, ContainerAmmoProviderComponent component, ref GetAmmoCountEvent args)
{
component.ProviderUid ??= uid;
if (!_container.TryGetContainer(component.ProviderUid.Value, component.Container, out var container))
if (!Containers.TryGetContainer(component.ProviderUid.Value, component.Container, out var container))
{
args.Capacity = 0;
args.Count = 0;

View File

@@ -66,7 +66,13 @@ public abstract partial class SharedGunSystem
if (!TryComp<AppearanceComponent>(uid, out var appearance))
return;
Appearance.SetData(uid, AmmoVisuals.MagLoaded, GetMagazineEntity(uid) != null, appearance);
var magEnt = GetMagazineEntity(uid);
Appearance.SetData(uid, AmmoVisuals.MagLoaded, magEnt != null, appearance);
if (magEnt != null)
{
UpdateMagazineAppearance(uid, component, magEnt.Value);
}
}
protected (int, int) GetMagazineCountCapacity(EntityUid uid, MagazineAmmoProviderComponent component)
@@ -124,16 +130,16 @@ public abstract partial class SharedGunSystem
var ammoEv = new GetAmmoCountEvent();
RaiseLocalEvent(magEntity.Value, ref ammoEv);
FinaliseMagazineTakeAmmo(uid, component, args, ammoEv.Count, ammoEv.Capacity, appearance);
FinaliseMagazineTakeAmmo(uid, component, ammoEv.Count, ammoEv.Capacity, args.User, appearance);
}
private void FinaliseMagazineTakeAmmo(EntityUid uid, MagazineAmmoProviderComponent component, TakeAmmoEvent args, int count, int capacity, AppearanceComponent? appearance)
private void FinaliseMagazineTakeAmmo(EntityUid uid, MagazineAmmoProviderComponent component, int count, int capacity, EntityUid? user, AppearanceComponent? appearance)
{
// If no ammo then check for autoeject
if (component.AutoEject && args.Ammo.Count == 0)
if (component.AutoEject && count == 0)
{
EjectMagazine(uid, component);
Audio.PlayPredicted(component.SoundAutoEject, uid, args.User);
Audio.PlayPredicted(component.SoundAutoEject, uid, user);
}
UpdateMagazineAppearance(uid, appearance, true, count, capacity);
@@ -146,12 +152,6 @@ public abstract partial class SharedGunSystem
var count = 0;
var capacity = 0;
if (component is ChamberMagazineAmmoProviderComponent chamber)
{
count = GetChamberEntity(uid) != null ? 1 : 0;
capacity = 1;
}
if (TryComp<AppearanceComponent>(magEnt, out var magAppearance))
{
Appearance.TryGetData<int>(magEnt, AmmoVisuals.AmmoCount, out var addCount, magAppearance);

View File

@@ -307,6 +307,11 @@ public abstract partial class SharedGunSystem : EntitySystem
// If they're firing an existing clip then don't play anything.
if (shots > 0)
{
if (ev.Reason != null)
{
PopupSystem.PopupClient(ev.Reason, gunUid, user);
}
// Don't spam safety sounds at gun fire rate, play it at a reduced rate.
// May cause prediction issues? Needs more tweaking
gun.NextFire = TimeSpan.FromSeconds(Math.Max(lastFire.TotalSeconds + SafetyNextFire, gun.NextFire.TotalSeconds));
@@ -467,4 +472,5 @@ public enum AmmoVisuals : byte
AmmoMax,
HasAmmo, // used for generic visualizers. c# stuff can just check ammocount != 0
MagLoaded,
BoltClosed,
}

View File

@@ -111,6 +111,9 @@ public sealed class WieldableSystem : EntitySystem
{
if (args.Handled)
return;
args.Handled = true;
if(!component.Wielded)
AttemptWield(uid, component, args.User);
else

View File

@@ -26,6 +26,15 @@ gun-cartridge-unspent = It is [color=lime]not spent[/color].
# BatteryAmmoProvider
gun-battery-examine = It has enough charge for [color={$color}]{$count}[/color] shots.
# CartridgeAmmoProvider
gun-chamber-bolt-ammo = Gun not bolted
gun-chamber-bolt = The bolt is [color={$color}]{$bolt}[/color].
gun-chamber-bolt-closed = Closed bolt
gun-chamber-bolt-opened = Opened bolt
gun-chamber-bolt-close = Close bolt
gun-chamber-bolt-open = Open bolt
gun-chamber-rack = Rack
# MagazineAmmoProvider
gun-magazine-examine = It has [color={$color}]{$count}[/color] shots remaining.

View File

@@ -10,6 +10,7 @@
- Debug
- type: Sprite
sprite: Objects/Weapons/Guns/Pistols/debug.rsi
state: icon
- type: Clothing
sprite: Objects/Weapons/Guns/Pistols/debug.rsi
- type: Gun

View File

@@ -31,6 +31,8 @@
soundEmpty:
path: /Audio/Weapons/Guns/Empty/lmg_empty.ogg
- type: ChamberMagazineAmmoProvider
soundRack:
path: /Audio/Weapons/Guns/Cock/lmg_cock.ogg
- type: AmmoCounter
- type: ItemSlots
slots:

View File

@@ -45,7 +45,6 @@
whitelist:
tags:
- Grenade
autoCycle: false
capacity: 3
proto: GrenadeFrag
soundInsert:

View File

@@ -28,6 +28,8 @@
soundGunshot:
path: /Audio/Weapons/Guns/Gunshots/pistol.ogg
- type: ChamberMagazineAmmoProvider
soundRack:
path: /Audio/Weapons/Guns/Cock/pistol_cock.ogg
- type: ItemSlots
slots:
gun_magazine:
@@ -108,6 +110,8 @@
map: ["enum.GunVisualLayers.Mag"]
- type: Clothing
sprite: Objects/Weapons/Guns/Pistols/cobra.rsi
- type: ChamberMagazineAmmoProvider
boltClosed: null
- type: Gun
fireRate: 4
soundGunshot:

View File

@@ -23,6 +23,8 @@
soundGunshot:
path: /Audio/Weapons/Guns/Gunshots/batrifle.ogg
- type: ChamberMagazineAmmoProvider
soundRack:
path: /Audio/Weapons/Guns/Cock/sf_rifle_cock.ogg
- type: ItemSlots
slots:
gun_magazine:
@@ -66,6 +68,8 @@
soundGunshot:
path: /Audio/Weapons/Guns/Gunshots/rifle2.ogg
- type: ChamberMagazineAmmoProvider
soundRack:
path: /Audio/Weapons/Guns/Cock/ltrifle_cock.ogg
- type: ItemSlots
slots:
gun_magazine:

View File

@@ -28,6 +28,8 @@
soundGunshot:
path: /Audio/Weapons/Guns/Gunshots/smg.ogg
- type: ChamberMagazineAmmoProvider
soundRack:
path: /Audio/Weapons/Guns/Cock/smg_cock.ogg
- type: ItemSlots
slots:
gun_magazine:
@@ -95,7 +97,7 @@
soundGunshot:
path: /Audio/Weapons/Guns/Gunshots/c-20r.ogg
- type: ChamberMagazineAmmoProvider
# autoEject: true # Do not set this until the PVS prediction issue is resolved
autoEject: true
- type: MagazineVisuals
magState: mag
steps: 6
@@ -216,6 +218,8 @@
shader: unshaded
- type: Clothing
sprite: Objects/Weapons/Guns/SMGs/wt550.rsi
- type: ChamberMagazineAmmoProvider
boltClosed: null
- type: Gun
fireRate: 5
selectedMode: FullAuto

View File

@@ -29,7 +29,6 @@
soundEmpty:
path: /Audio/Weapons/Guns/Empty/empty.ogg
- type: BallisticAmmoProvider
autoCycle: false
whitelist:
tags:
- ShellShotgun
@@ -266,4 +265,4 @@
id: WeaponShotgunImprovisedLoaded
components:
- type: BallisticAmmoProvider
proto: ShellShotgunImprovised
proto: ShellShotgunImprovised

View File

@@ -25,7 +25,6 @@
soundGunshot:
path: /Audio/Weapons/Guns/Gunshots/sniper.ogg
- type: BallisticAmmoProvider
autoCycle: false
capacity: 10
proto: CartridgeLightRifle
whitelist:

View File

@@ -156,7 +156,6 @@
- type: BallisticAmmoProvider
proto: CartridgePistol
capacity: 50
cycleable: false
- type: Construction
deconstructionTarget: null
graph: WeaponTurretSyndicateDisposable

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 B

View File

@@ -6,44 +6,47 @@
"x": 32,
"y": 32
},
"states": [
{
"name": "icon"
},
{
"name": "base"
},
{
"name": "mag-0"
},
{
"name": "mag-1"
},
{
"name": "mag-2"
},
{
"name": "mag-3"
},
{
"name": "inhand-left",
"directions": 4
},
{
"name": "inhand-right",
"directions": 4
},
{
"name": "wielded-inhand-left",
"directions": 4
},
{
"name": "wielded-inhand-right",
"directions": 4
},
{
"name": "equipped-BACKPACK",
"directions": 4
}
]
"states": [
{
"name": "icon"
},
{
"name": "base"
},
{
"name": "bolt-open"
},
{
"name": "mag-0"
},
{
"name": "mag-1"
},
{
"name": "mag-2"
},
{
"name": "mag-3"
},
{
"name": "inhand-left",
"directions": 4
},
{
"name": "inhand-right",
"directions": 4
},
{
"name": "wielded-inhand-left",
"directions": 4
},
{
"name": "wielded-inhand-right",
"directions": 4
},
{
"name": "equipped-BACKPACK",
"directions": 4
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 B

View File

@@ -10,6 +10,9 @@
{
"name": "icon"
},
{
"name": "bolt-open"
},
{
"name": "inhand-left",
"directions": 4

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 B

View File

@@ -13,6 +13,9 @@
{
"name": "base"
},
{
"name": "bolt-open"
},
{
"name": "mag-0"
},

Binary file not shown.

After

Width:  |  Height:  |  Size: 503 B

View File

@@ -13,6 +13,9 @@
{
"name": "base"
},
{
"name": "bolt-open"
},
{
"name": "mag-0"
},
@@ -27,9 +30,9 @@
"name": "inhand-right",
"directions": 4
},
{
"name": "equipped-BELT",
"directions": 4
}
{
"name": "equipped-BELT",
"directions": 4
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 B

View File

@@ -10,6 +10,9 @@
{
"name": "icon"
},
{
"name": "bolt-open"
},
{
"name": "inhand-left",
"directions": 4

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 B

View File

@@ -10,6 +10,9 @@
{
"name": "icon"
},
{
"name": "bolt-open"
},
{
"name": "inhand-left",
"directions": 4

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 B

View File

@@ -6,21 +6,24 @@
"x": 32,
"y": 32
},
"states": [
{
"name": "inhand-left",
"directions": 4
},
{
"name": "icon"
},
{
"name": "inhand-right",
"directions": 4
},
{
"name": "equipped-BELT",
"directions": 4
}
]
"states": [
{
"name": "inhand-left",
"directions": 4
},
{
"name": "icon"
},
{
"name": "bolt-open"
},
{
"name": "inhand-right",
"directions": 4
},
{
"name": "equipped-BELT",
"directions": 4
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 B

View File

@@ -10,6 +10,9 @@
{
"name": "icon"
},
{
"name": "bolt-open"
},
{
"name": "inhand-left",
"directions": 4

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 B

View File

@@ -13,6 +13,9 @@
{
"name": "base"
},
{
"name": "bolt-open"
},
{
"name": "mag-0"
},

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 B

View File

@@ -7,26 +7,29 @@
"y": 32
},
"states": [
{
"name": "icon"
},
{
"name": "base"
},
{
"name": "mag-0"
},
{
"name": "inhand-left",
"directions": 4
},
{
"name": "inhand-right",
"directions": 4
},
{
"name": "equipped-BACKPACK",
"directions": 4
}
{
"name": "icon"
},
{
"name": "base"
},
{
"name": "bolt-open"
},
{
"name": "mag-0"
},
{
"name": "inhand-left",
"directions": 4
},
{
"name": "inhand-right",
"directions": 4
},
{
"name": "equipped-BACKPACK",
"directions": 4
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 B

View File

@@ -13,6 +13,9 @@
{
"name": "base"
},
{
"name": "bolt-open"
},
{
"name": "mag-0"
},

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 B

View File

@@ -7,29 +7,32 @@
"y": 32
},
"states": [
{
"name": "icon"
},
{
"name": "base"
},
{
"name": "mag-0"
},
{
"name": "suppressor"
},
{
"name": "inhand-left",
"directions": 4
},
{
"name": "inhand-right",
"directions": 4
},
{
"name": "equipped-BACKPACK",
"directions": 4
}
{
"name": "icon"
},
{
"name": "base"
},
{
"name": "bolt-open"
},
{
"name": "mag-0"
},
{
"name": "suppressor"
},
{
"name": "inhand-left",
"directions": 4
},
{
"name": "inhand-right",
"directions": 4
},
{
"name": "equipped-BACKPACK",
"directions": 4
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 B

View File

@@ -7,44 +7,47 @@
"y": 32
},
"states": [
{
"name": "icon"
},
{
"name": "base"
},
{
"name": "mag-0"
},
{
"name": "mag-1"
},
{
"name": "mag-2"
},
{
"name": "mag-3"
},
{
"name": "mag-4"
},
{
"name": "mag-5"
},
{
"name": "suppressor"
},
{
"name": "inhand-left",
"directions": 4
},
{
"name": "inhand-right",
"directions": 4
},
{
"name": "equipped-BACKPACK",
"directions": 4
}
{
"name": "icon"
},
{
"name": "base"
},
{
"name": "bolt-open"
},
{
"name": "mag-0"
},
{
"name": "mag-1"
},
{
"name": "mag-2"
},
{
"name": "mag-3"
},
{
"name": "mag-4"
},
{
"name": "mag-5"
},
{
"name": "suppressor"
},
{
"name": "inhand-left",
"directions": 4
},
{
"name": "inhand-right",
"directions": 4
},
{
"name": "equipped-BACKPACK",
"directions": 4
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 B

View File

@@ -13,6 +13,9 @@
{
"name": "base"
},
{
"name": "bolt-open"
},
{
"name": "mag-0"
},

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 B

View File

@@ -7,26 +7,29 @@
"y": 32
},
"states": [
{
"name": "icon"
},
{
"name": "base"
},
{
"name": "mag-0"
},
{
"name": "inhand-left",
"directions": 4
},
{
"name": "inhand-right",
"directions": 4
},
{
"name": "equipped-BACKPACK",
"directions": 4
}
{
"name": "icon"
},
{
"name": "base"
},
{
"name": "bolt-open"
},
{
"name": "mag-0"
},
{
"name": "inhand-left",
"directions": 4
},
{
"name": "inhand-right",
"directions": 4
},
{
"name": "equipped-BACKPACK",
"directions": 4
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

View File

@@ -6,17 +6,20 @@
"x": 32,
"y": 32
},
"states": [
{
"name": "icon"
},
{
"name": "inhand-right",
"directions": 4
},
{
"name": "inhand-left",
"directions": 4
}
]
"states": [
{
"name": "icon"
},
{
"name": "bolt-open"
},
{
"name": "inhand-right",
"directions": 4
},
{
"name": "inhand-left",
"directions": 4
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 392 B

View File

@@ -7,26 +7,29 @@
"y": 32
},
"states": [
{
"name": "icon"
},
{
"name": "base"
},
{
"name": "mag-0"
},
{
"name": "inhand-left",
"directions": 4
},
{
"name": "inhand-right",
"directions": 4
},
{
"name": "equipped-BACKPACK",
"directions": 4
}
{
"name": "icon"
},
{
"name": "base"
},
{
"name": "bolt-open"
},
{
"name": "mag-0"
},
{
"name": "inhand-left",
"directions": 4
},
{
"name": "inhand-right",
"directions": 4
},
{
"name": "equipped-BACKPACK",
"directions": 4
}
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 B

View File

@@ -10,6 +10,9 @@
{
"name": "icon"
},
{
"name": "bolt-open"
},
{
"name": "inhand-left",
"directions": 4

Binary file not shown.

After

Width:  |  Height:  |  Size: 347 B

View File

@@ -7,20 +7,23 @@
"y": 32
},
"states": [
{
"name": "icon"
},
{
"name": "inhand-left",
"directions": 4
},
{
"name": "inhand-right",
"directions": 4
},
{
"name": "equipped-BACKPACK",
"directions": 4
}
{
"name": "icon"
},
{
"name": "bolt-open"
},
{
"name": "inhand-left",
"directions": 4
},
{
"name": "inhand-right",
"directions": 4
},
{
"name": "equipped-BACKPACK",
"directions": 4
}
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 B

View File

@@ -6,21 +6,24 @@
"x": 32,
"y": 32
},
"states": [
{
"name": "icon"
},
{
"name": "inhand-right",
"directions": 4
},
{
"name": "inhand-left",
"directions": 4
},
{
"name": "equipped-BELT",
"directions": 4
}
]
}
"states": [
{
"name": "icon"
},
{
"name": "bolt-open"
},
{
"name": "inhand-right",
"directions": 4
},
{
"name": "inhand-left",
"directions": 4
},
{
"name": "equipped-BELT",
"directions": 4
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 B

View File

@@ -11,6 +11,9 @@
{
"name": "icon"
},
{
"name": "bolt-open"
},
{
"name": "inhand-left",
"directions": 4

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 B

View File

@@ -10,6 +10,9 @@
{
"name": "icon"
},
{
"name": "bolt-open"
},
{
"name": "inhand-left",
"directions": 4

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 B

View File

@@ -10,6 +10,9 @@
{
"name": "icon"
},
{
"name": "bolt-open"
},
{
"name": "inhand-left",
"directions": 4

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 B

View File

@@ -10,6 +10,9 @@
{
"name": "base"
},
{
"name": "bolt-open"
},
{
"name": "inhand-left",
"directions": 4

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 B

View File

@@ -10,6 +10,9 @@
{
"name": "base"
},
{
"name": "bolt-open"
},
{
"name": "inhand-left",
"directions": 4