(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>
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -45,7 +45,6 @@
|
||||
whitelist:
|
||||
tags:
|
||||
- Grenade
|
||||
autoCycle: false
|
||||
capacity: 3
|
||||
proto: GrenadeFrag
|
||||
soundInsert:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
soundGunshot:
|
||||
path: /Audio/Weapons/Guns/Gunshots/sniper.ogg
|
||||
- type: BallisticAmmoProvider
|
||||
autoCycle: false
|
||||
capacity: 10
|
||||
proto: CartridgeLightRifle
|
||||
whitelist:
|
||||
|
||||
@@ -156,7 +156,6 @@
|
||||
- type: BallisticAmmoProvider
|
||||
proto: CartridgePistol
|
||||
capacity: 50
|
||||
cycleable: false
|
||||
- type: Construction
|
||||
deconstructionTarget: null
|
||||
graph: WeaponTurretSyndicateDisposable
|
||||
|
||||
|
After Width: | Height: | Size: 313 B |
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 331 B |
@@ -10,6 +10,9 @@
|
||||
{
|
||||
"name": "icon"
|
||||
},
|
||||
{
|
||||
"name": "bolt-open"
|
||||
},
|
||||
{
|
||||
"name": "inhand-left",
|
||||
"directions": 4
|
||||
|
||||
|
After Width: | Height: | Size: 355 B |
@@ -13,6 +13,9 @@
|
||||
{
|
||||
"name": "base"
|
||||
},
|
||||
{
|
||||
"name": "bolt-open"
|
||||
},
|
||||
{
|
||||
"name": "mag-0"
|
||||
},
|
||||
|
||||
|
After Width: | Height: | Size: 503 B |
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 344 B |
@@ -10,6 +10,9 @@
|
||||
{
|
||||
"name": "icon"
|
||||
},
|
||||
{
|
||||
"name": "bolt-open"
|
||||
},
|
||||
{
|
||||
"name": "inhand-left",
|
||||
"directions": 4
|
||||
|
||||
|
After Width: | Height: | Size: 345 B |
@@ -10,6 +10,9 @@
|
||||
{
|
||||
"name": "icon"
|
||||
},
|
||||
{
|
||||
"name": "bolt-open"
|
||||
},
|
||||
{
|
||||
"name": "inhand-left",
|
||||
"directions": 4
|
||||
|
||||
|
After Width: | Height: | Size: 351 B |
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 334 B |
@@ -10,6 +10,9 @@
|
||||
{
|
||||
"name": "icon"
|
||||
},
|
||||
{
|
||||
"name": "bolt-open"
|
||||
},
|
||||
{
|
||||
"name": "inhand-left",
|
||||
"directions": 4
|
||||
|
||||
|
After Width: | Height: | Size: 318 B |
@@ -13,6 +13,9 @@
|
||||
{
|
||||
"name": "base"
|
||||
},
|
||||
{
|
||||
"name": "bolt-open"
|
||||
},
|
||||
{
|
||||
"name": "mag-0"
|
||||
},
|
||||
|
||||
|
After Width: | Height: | Size: 356 B |
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 459 B |
@@ -13,6 +13,9 @@
|
||||
{
|
||||
"name": "base"
|
||||
},
|
||||
{
|
||||
"name": "bolt-open"
|
||||
},
|
||||
{
|
||||
"name": "mag-0"
|
||||
},
|
||||
|
||||
|
After Width: | Height: | Size: 274 B |
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 276 B |
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 378 B |
@@ -13,6 +13,9 @@
|
||||
{
|
||||
"name": "base"
|
||||
},
|
||||
{
|
||||
"name": "bolt-open"
|
||||
},
|
||||
{
|
||||
"name": "mag-0"
|
||||
},
|
||||
|
||||
|
After Width: | Height: | Size: 346 B |
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 308 B |
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 392 B |
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 286 B |
@@ -10,6 +10,9 @@
|
||||
{
|
||||
"name": "icon"
|
||||
},
|
||||
{
|
||||
"name": "bolt-open"
|
||||
},
|
||||
{
|
||||
"name": "inhand-left",
|
||||
"directions": 4
|
||||
|
||||
|
After Width: | Height: | Size: 347 B |
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 302 B |
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 309 B |
@@ -11,6 +11,9 @@
|
||||
{
|
||||
"name": "icon"
|
||||
},
|
||||
{
|
||||
"name": "bolt-open"
|
||||
},
|
||||
{
|
||||
"name": "inhand-left",
|
||||
"directions": 4
|
||||
|
||||
|
After Width: | Height: | Size: 280 B |
@@ -10,6 +10,9 @@
|
||||
{
|
||||
"name": "icon"
|
||||
},
|
||||
{
|
||||
"name": "bolt-open"
|
||||
},
|
||||
{
|
||||
"name": "inhand-left",
|
||||
"directions": 4
|
||||
|
||||
|
After Width: | Height: | Size: 250 B |
@@ -10,6 +10,9 @@
|
||||
{
|
||||
"name": "icon"
|
||||
},
|
||||
{
|
||||
"name": "bolt-open"
|
||||
},
|
||||
{
|
||||
"name": "inhand-left",
|
||||
"directions": 4
|
||||
|
||||
|
After Width: | Height: | Size: 364 B |
@@ -10,6 +10,9 @@
|
||||
{
|
||||
"name": "base"
|
||||
},
|
||||
{
|
||||
"name": "bolt-open"
|
||||
},
|
||||
{
|
||||
"name": "inhand-left",
|
||||
"directions": 4
|
||||
|
||||
|
After Width: | Height: | Size: 294 B |
@@ -10,6 +10,9 @@
|
||||
{
|
||||
"name": "base"
|
||||
},
|
||||
{
|
||||
"name": "bolt-open"
|
||||
},
|
||||
{
|
||||
"name": "inhand-left",
|
||||
"directions": 4
|
||||
|
||||