Gun refactor (#8301)
Co-authored-by: Kara <lunarautomaton6@gmail.com> Co-authored-by: T-Stalker <le0nel_1van@hotmail.com> Co-authored-by: T-Stalker <43253663+DogZeroX@users.noreply.github.com> Co-authored-by: ElectroJr <leonsfriedrich@gmail.com> Co-authored-by: metalgearsloth <metalgearsloth@gmail.com>
This commit is contained in:
@@ -23,7 +23,6 @@
|
||||
<ProjectReference Include="..\RobustToolbox\Robust.Client\Robust.Client.csproj" />
|
||||
<ProjectReference Include="..\Content.Shared\Content.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="..\RobustToolbox\MSBuild\XamlIL.targets" />
|
||||
<Import Project="..\RobustToolbox\MSBuild\Robust.Analyzers.targets" />
|
||||
</Project>
|
||||
|
||||
17
Content.Client/Effects/EffectVisualizerSystem.cs
Normal file
17
Content.Client/Effects/EffectVisualizerSystem.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Robust.Client.Animations;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Effects;
|
||||
|
||||
public sealed class EffectVisualizerSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<EffectVisualsComponent, AnimationCompletedEvent>(OnEffectAnimComplete);
|
||||
}
|
||||
|
||||
private void OnEffectAnimComplete(EntityUid uid, EffectVisualsComponent component, AnimationCompletedEvent args)
|
||||
{
|
||||
QueueDel(uid);
|
||||
}
|
||||
}
|
||||
8
Content.Client/Effects/EffectVisualsComponent.cs
Normal file
8
Content.Client/Effects/EffectVisualsComponent.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Content.Client.Effects;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class EffectVisualsComponent : Component
|
||||
{
|
||||
public float Length;
|
||||
public float Accumulator = 0f;
|
||||
}
|
||||
@@ -1,7 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client.Items
|
||||
{
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
using Content.Client.Items.Components;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.Weapons.Ranged.Barrels.Components;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.GameStates;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
namespace Content.Client.Weapons.Ranged.Barrels.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
[NetworkedComponent()]
|
||||
public sealed class ClientBatteryBarrelComponent : Component, IItemStatus
|
||||
{
|
||||
public StatusControl? ItemStatus;
|
||||
|
||||
public Control MakeControl()
|
||||
{
|
||||
ItemStatus = new StatusControl(this);
|
||||
|
||||
if (IoCManager.Resolve<IEntityManager>().TryGetComponent(Owner, out AppearanceComponent appearance))
|
||||
ItemStatus.Update(appearance);
|
||||
|
||||
return ItemStatus;
|
||||
}
|
||||
|
||||
public void DestroyControl(Control control)
|
||||
{
|
||||
if (ItemStatus == control)
|
||||
{
|
||||
ItemStatus = null;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class StatusControl : Control
|
||||
{
|
||||
private readonly ClientBatteryBarrelComponent _parent;
|
||||
private readonly BoxContainer _bulletsList;
|
||||
private readonly Label _noBatteryLabel;
|
||||
private readonly Label _ammoCount;
|
||||
|
||||
public StatusControl(ClientBatteryBarrelComponent parent)
|
||||
{
|
||||
MinHeight = 15;
|
||||
_parent = parent;
|
||||
HorizontalExpand = true;
|
||||
VerticalAlignment = VAlignment.Center;
|
||||
|
||||
AddChild(new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
HorizontalExpand = true,
|
||||
Children =
|
||||
{
|
||||
new Control
|
||||
{
|
||||
HorizontalExpand = true,
|
||||
Children =
|
||||
{
|
||||
(_bulletsList = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
SeparationOverride = 4
|
||||
}),
|
||||
(_noBatteryLabel = new Label
|
||||
{
|
||||
Text = "No Battery!",
|
||||
StyleClasses = {StyleNano.StyleClassItemStatus}
|
||||
})
|
||||
}
|
||||
},
|
||||
new Control() { MinSize = (5,0) },
|
||||
(_ammoCount = new Label
|
||||
{
|
||||
StyleClasses = {StyleNano.StyleClassItemStatus},
|
||||
HorizontalAlignment = HAlignment.Right,
|
||||
}),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void Update(AppearanceComponent appearance)
|
||||
{
|
||||
_bulletsList.RemoveAllChildren();
|
||||
|
||||
if (!appearance.TryGetData(MagazineBarrelVisuals.MagLoaded, out bool loaded) || !loaded)
|
||||
{
|
||||
_noBatteryLabel.Visible = true;
|
||||
_ammoCount.Visible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
appearance.TryGetData(AmmoVisuals.AmmoCount, out int count);
|
||||
appearance.TryGetData(AmmoVisuals.AmmoMax, out int capacity);
|
||||
|
||||
_noBatteryLabel.Visible = false;
|
||||
_ammoCount.Visible = true;
|
||||
|
||||
_ammoCount.Text = $"x{count:00}";
|
||||
capacity = Math.Min(capacity, 8);
|
||||
FillBulletRow(_bulletsList, count, capacity);
|
||||
}
|
||||
|
||||
private static void FillBulletRow(Control container, int count, int capacity)
|
||||
{
|
||||
var colorGone = Color.FromHex("#000000");
|
||||
var color = Color.FromHex("#E00000");
|
||||
|
||||
// Draw the empty ones
|
||||
for (var i = count; i < capacity; i++)
|
||||
{
|
||||
container.AddChild(new PanelContainer
|
||||
{
|
||||
PanelOverride = new StyleBoxFlat()
|
||||
{
|
||||
BackgroundColor = colorGone,
|
||||
},
|
||||
MinSize = (10, 15),
|
||||
});
|
||||
}
|
||||
|
||||
// Draw the full ones, but limit the count to the capacity
|
||||
count = Math.Min(count, capacity);
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
container.AddChild(new PanelContainer
|
||||
{
|
||||
PanelOverride = new StyleBoxFlat()
|
||||
{
|
||||
BackgroundColor = color,
|
||||
},
|
||||
MinSize = (10, 15),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,212 +0,0 @@
|
||||
using System;
|
||||
using Content.Client.IoC;
|
||||
using Content.Client.Items.Components;
|
||||
using Content.Client.Resources;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.Weapons.Ranged.Barrels.Components;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
namespace Content.Client.Weapons.Ranged.Barrels.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
[NetworkedComponent()]
|
||||
public sealed class ClientBoltActionBarrelComponent : Component, IItemStatus
|
||||
{
|
||||
private StatusControl? _statusControl;
|
||||
|
||||
/// <summary>
|
||||
/// chambered is true when a bullet is chambered
|
||||
/// spent is true when the chambered bullet is spent
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public (bool chambered, bool spent) Chamber { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Count of bullets in the magazine.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Null if no magazine is inserted.
|
||||
/// </remarks>
|
||||
[ViewVariables]
|
||||
public (int count, int max)? MagazineCount { get; private set; }
|
||||
|
||||
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
|
||||
{
|
||||
base.HandleComponentState(curState, nextState);
|
||||
|
||||
if (curState is not BoltActionBarrelComponentState cast)
|
||||
return;
|
||||
|
||||
Chamber = cast.Chamber;
|
||||
MagazineCount = cast.Magazine;
|
||||
_statusControl?.Update();
|
||||
}
|
||||
|
||||
public Control MakeControl()
|
||||
{
|
||||
_statusControl = new StatusControl(this);
|
||||
_statusControl.Update();
|
||||
return _statusControl;
|
||||
}
|
||||
|
||||
public void DestroyControl(Control control)
|
||||
{
|
||||
if (_statusControl == control)
|
||||
{
|
||||
_statusControl = null;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class StatusControl : Control
|
||||
{
|
||||
private readonly ClientBoltActionBarrelComponent _parent;
|
||||
private readonly BoxContainer _bulletsListTop;
|
||||
private readonly BoxContainer _bulletsListBottom;
|
||||
private readonly TextureRect _chamberedBullet;
|
||||
private readonly Label _noMagazineLabel;
|
||||
|
||||
public StatusControl(ClientBoltActionBarrelComponent parent)
|
||||
{
|
||||
MinHeight = 15;
|
||||
_parent = parent;
|
||||
HorizontalExpand = true;
|
||||
VerticalAlignment = VAlignment.Center;
|
||||
AddChild(new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
HorizontalExpand = true,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
SeparationOverride = 0,
|
||||
Children =
|
||||
{
|
||||
(_bulletsListTop = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
SeparationOverride = 0
|
||||
}),
|
||||
new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
HorizontalExpand = true,
|
||||
Children =
|
||||
{
|
||||
new Control
|
||||
{
|
||||
HorizontalExpand = true,
|
||||
Children =
|
||||
{
|
||||
(_bulletsListBottom = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
SeparationOverride = 0
|
||||
}),
|
||||
(_noMagazineLabel = new Label
|
||||
{
|
||||
Text = "No Magazine!",
|
||||
StyleClasses = {StyleNano.StyleClassItemStatus}
|
||||
})
|
||||
}
|
||||
},
|
||||
(_chamberedBullet = new TextureRect
|
||||
{
|
||||
Texture = StaticIoC.ResC.GetTexture("/Textures/Interface/ItemStatus/Bullets/chambered.png"),
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
HorizontalAlignment = HAlignment.Right,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
_chamberedBullet.ModulateSelfOverride =
|
||||
_parent.Chamber.chambered ?
|
||||
_parent.Chamber.spent ? Color.Red : Color.FromHex("#d7df60")
|
||||
: Color.Black;
|
||||
|
||||
_bulletsListTop.RemoveAllChildren();
|
||||
_bulletsListBottom.RemoveAllChildren();
|
||||
|
||||
if (_parent.MagazineCount == null)
|
||||
{
|
||||
_noMagazineLabel.Visible = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var (count, capacity) = _parent.MagazineCount.Value;
|
||||
|
||||
_noMagazineLabel.Visible = false;
|
||||
|
||||
string texturePath;
|
||||
if (capacity <= 20)
|
||||
{
|
||||
texturePath = "/Textures/Interface/ItemStatus/Bullets/normal.png";
|
||||
}
|
||||
else if (capacity <= 30)
|
||||
{
|
||||
texturePath = "/Textures/Interface/ItemStatus/Bullets/small.png";
|
||||
}
|
||||
else
|
||||
{
|
||||
texturePath = "/Textures/Interface/ItemStatus/Bullets/tiny.png";
|
||||
}
|
||||
|
||||
var texture = StaticIoC.ResC.GetTexture(texturePath);
|
||||
|
||||
const int tinyMaxRow = 60;
|
||||
|
||||
if (capacity > tinyMaxRow)
|
||||
{
|
||||
FillBulletRow(_bulletsListBottom, Math.Min(tinyMaxRow, count), tinyMaxRow, texture);
|
||||
FillBulletRow(_bulletsListTop, Math.Max(0, count - tinyMaxRow), capacity - tinyMaxRow, texture);
|
||||
}
|
||||
else
|
||||
{
|
||||
FillBulletRow(_bulletsListBottom, count, capacity, texture);
|
||||
}
|
||||
}
|
||||
|
||||
private static void FillBulletRow(Control container, int count, int capacity, Texture texture)
|
||||
{
|
||||
var colorA = Color.FromHex("#b68f0e");
|
||||
var colorB = Color.FromHex("#d7df60");
|
||||
var colorGoneA = Color.FromHex("#000000");
|
||||
var colorGoneB = Color.FromHex("#222222");
|
||||
|
||||
var altColor = false;
|
||||
|
||||
for (var i = count; i < capacity; i++)
|
||||
{
|
||||
container.AddChild(new TextureRect
|
||||
{
|
||||
Texture = texture,
|
||||
ModulateSelfOverride = altColor ? colorGoneA : colorGoneB
|
||||
});
|
||||
|
||||
altColor ^= true;
|
||||
}
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
container.AddChild(new TextureRect
|
||||
{
|
||||
Texture = texture,
|
||||
ModulateSelfOverride = altColor ? colorA : colorB
|
||||
});
|
||||
|
||||
altColor ^= true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,248 +0,0 @@
|
||||
using System;
|
||||
using Content.Client.IoC;
|
||||
using Content.Client.Items.Components;
|
||||
using Content.Client.Resources;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.Weapons.Ranged.Barrels.Components;
|
||||
using Robust.Client.Animations;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Animations;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
namespace Content.Client.Weapons.Ranged.Barrels.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
[NetworkedComponent()]
|
||||
public sealed class ClientMagazineBarrelComponent : Component, IItemStatus
|
||||
{
|
||||
private static readonly Animation AlarmAnimationSmg = new()
|
||||
{
|
||||
Length = TimeSpan.FromSeconds(1.4),
|
||||
AnimationTracks =
|
||||
{
|
||||
new AnimationTrackControlProperty
|
||||
{
|
||||
// These timings match the SMG audio file.
|
||||
Property = nameof(Label.FontColorOverride),
|
||||
InterpolationMode = AnimationInterpolationMode.Previous,
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackProperty.KeyFrame(Color.Red, 0.1f),
|
||||
new AnimationTrackProperty.KeyFrame(null!, 0.3f),
|
||||
new AnimationTrackProperty.KeyFrame(Color.Red, 0.2f),
|
||||
new AnimationTrackProperty.KeyFrame(null!, 0.3f),
|
||||
new AnimationTrackProperty.KeyFrame(Color.Red, 0.2f),
|
||||
new AnimationTrackProperty.KeyFrame(null!, 0.3f),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private static readonly Animation AlarmAnimationLmg = new()
|
||||
{
|
||||
Length = TimeSpan.FromSeconds(0.75),
|
||||
AnimationTracks =
|
||||
{
|
||||
new AnimationTrackControlProperty
|
||||
{
|
||||
// These timings match the SMG audio file.
|
||||
Property = nameof(Label.FontColorOverride),
|
||||
InterpolationMode = AnimationInterpolationMode.Previous,
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackProperty.KeyFrame(Color.Red, 0.0f),
|
||||
new AnimationTrackProperty.KeyFrame(null!, 0.15f),
|
||||
new AnimationTrackProperty.KeyFrame(Color.Red, 0.15f),
|
||||
new AnimationTrackProperty.KeyFrame(null!, 0.15f),
|
||||
new AnimationTrackProperty.KeyFrame(Color.Red, 0.15f),
|
||||
new AnimationTrackProperty.KeyFrame(null!, 0.15f),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
private StatusControl? _statusControl;
|
||||
|
||||
/// <summary>
|
||||
/// True if a bullet is chambered.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public bool Chambered { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Count of bullets in the magazine.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Null if no magazine is inserted.
|
||||
/// </remarks>
|
||||
[ViewVariables]
|
||||
public (int count, int max)? MagazineCount { get; private set; }
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)] [DataField("lmg_alarm_animation")] private bool _isLmgAlarmAnimation = default;
|
||||
|
||||
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
|
||||
{
|
||||
base.HandleComponentState(curState, nextState);
|
||||
|
||||
if (curState is not MagazineBarrelComponentState cast)
|
||||
return;
|
||||
|
||||
Chambered = cast.Chambered;
|
||||
MagazineCount = cast.Magazine;
|
||||
_statusControl?.Update();
|
||||
}
|
||||
|
||||
public void PlayAlarmAnimation()
|
||||
{
|
||||
_statusControl?.PlayAlarmAnimation();
|
||||
}
|
||||
|
||||
public Control MakeControl()
|
||||
{
|
||||
_statusControl = new StatusControl(this);
|
||||
_statusControl.Update();
|
||||
return _statusControl;
|
||||
}
|
||||
|
||||
public void DestroyControl(Control control)
|
||||
{
|
||||
if (_statusControl == control)
|
||||
{
|
||||
_statusControl = null;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class StatusControl : Control
|
||||
{
|
||||
private readonly ClientMagazineBarrelComponent _parent;
|
||||
private readonly BoxContainer _bulletsList;
|
||||
private readonly TextureRect _chamberedBullet;
|
||||
private readonly Label _noMagazineLabel;
|
||||
private readonly Label _ammoCount;
|
||||
|
||||
public StatusControl(ClientMagazineBarrelComponent parent)
|
||||
{
|
||||
MinHeight = 15;
|
||||
_parent = parent;
|
||||
HorizontalExpand = true;
|
||||
VerticalAlignment = VAlignment.Center;
|
||||
|
||||
AddChild(new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
HorizontalExpand = true,
|
||||
Children =
|
||||
{
|
||||
(_chamberedBullet = new TextureRect
|
||||
{
|
||||
Texture = StaticIoC.ResC.GetTexture("/Textures/Interface/ItemStatus/Bullets/chambered_rotated.png"),
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
HorizontalAlignment = HAlignment.Right,
|
||||
}),
|
||||
new Control() { MinSize = (5,0) },
|
||||
new Control
|
||||
{
|
||||
HorizontalExpand = true,
|
||||
Children =
|
||||
{
|
||||
(_bulletsList = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
SeparationOverride = 0
|
||||
}),
|
||||
(_noMagazineLabel = new Label
|
||||
{
|
||||
Text = "No Magazine!",
|
||||
StyleClasses = {StyleNano.StyleClassItemStatus}
|
||||
})
|
||||
}
|
||||
},
|
||||
new Control() { MinSize = (5,0) },
|
||||
(_ammoCount = new Label
|
||||
{
|
||||
StyleClasses = {StyleNano.StyleClassItemStatus},
|
||||
HorizontalAlignment = HAlignment.Right,
|
||||
}),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
_chamberedBullet.ModulateSelfOverride =
|
||||
_parent.Chambered ? Color.FromHex("#d7df60") : Color.Black;
|
||||
|
||||
_bulletsList.RemoveAllChildren();
|
||||
|
||||
if (_parent.MagazineCount == null)
|
||||
{
|
||||
_noMagazineLabel.Visible = true;
|
||||
_ammoCount.Visible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var (count, capacity) = _parent.MagazineCount.Value;
|
||||
|
||||
_noMagazineLabel.Visible = false;
|
||||
_ammoCount.Visible = true;
|
||||
|
||||
var texturePath = "/Textures/Interface/ItemStatus/Bullets/normal.png";
|
||||
var texture = StaticIoC.ResC.GetTexture(texturePath);
|
||||
|
||||
_ammoCount.Text = $"x{count:00}";
|
||||
capacity = Math.Min(capacity, 20);
|
||||
FillBulletRow(_bulletsList, count, capacity, texture);
|
||||
}
|
||||
|
||||
private static void FillBulletRow(Control container, int count, int capacity, Texture texture)
|
||||
{
|
||||
var colorA = Color.FromHex("#b68f0e");
|
||||
var colorB = Color.FromHex("#d7df60");
|
||||
var colorGoneA = Color.FromHex("#000000");
|
||||
var colorGoneB = Color.FromHex("#222222");
|
||||
|
||||
var altColor = false;
|
||||
|
||||
// Draw the empty ones
|
||||
for (var i = count; i < capacity; i++)
|
||||
{
|
||||
container.AddChild(new TextureRect
|
||||
{
|
||||
Texture = texture,
|
||||
ModulateSelfOverride = altColor ? colorGoneA : colorGoneB,
|
||||
Stretch = TextureRect.StretchMode.KeepCentered
|
||||
});
|
||||
|
||||
altColor ^= true;
|
||||
}
|
||||
|
||||
// Draw the full ones, but limit the count to the capacity
|
||||
count = Math.Min(count, capacity);
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
container.AddChild(new TextureRect
|
||||
{
|
||||
Texture = texture,
|
||||
ModulateSelfOverride = altColor ? colorA : colorB,
|
||||
Stretch = TextureRect.StretchMode.KeepCentered
|
||||
});
|
||||
|
||||
altColor ^= true;
|
||||
}
|
||||
}
|
||||
|
||||
public void PlayAlarmAnimation()
|
||||
{
|
||||
var animation = _parent._isLmgAlarmAnimation ? AlarmAnimationLmg : AlarmAnimationSmg;
|
||||
_noMagazineLabel.PlayAnimation(animation, "alarm");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,212 +0,0 @@
|
||||
using System;
|
||||
using Content.Client.IoC;
|
||||
using Content.Client.Items.Components;
|
||||
using Content.Client.Resources;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.Weapons.Ranged.Barrels.Components;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
namespace Content.Client.Weapons.Ranged.Barrels.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
[NetworkedComponent()]
|
||||
public sealed class ClientPumpBarrelComponent : Component, IItemStatus
|
||||
{
|
||||
private StatusControl? _statusControl;
|
||||
|
||||
/// <summary>
|
||||
/// chambered is true when a bullet is chambered
|
||||
/// spent is true when the chambered bullet is spent
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public (bool chambered, bool spent) Chamber { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Count of bullets in the magazine.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Null if no magazine is inserted.
|
||||
/// </remarks>
|
||||
[ViewVariables]
|
||||
public (int count, int max)? MagazineCount { get; private set; }
|
||||
|
||||
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
|
||||
{
|
||||
base.HandleComponentState(curState, nextState);
|
||||
|
||||
if (curState is not PumpBarrelComponentState cast)
|
||||
return;
|
||||
|
||||
Chamber = cast.Chamber;
|
||||
MagazineCount = cast.Magazine;
|
||||
_statusControl?.Update();
|
||||
}
|
||||
|
||||
public Control MakeControl()
|
||||
{
|
||||
_statusControl = new StatusControl(this);
|
||||
_statusControl.Update();
|
||||
return _statusControl;
|
||||
}
|
||||
|
||||
public void DestroyControl(Control control)
|
||||
{
|
||||
if (_statusControl == control)
|
||||
{
|
||||
_statusControl = null;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class StatusControl : Control
|
||||
{
|
||||
private readonly ClientPumpBarrelComponent _parent;
|
||||
private readonly BoxContainer _bulletsListTop;
|
||||
private readonly BoxContainer _bulletsListBottom;
|
||||
private readonly TextureRect _chamberedBullet;
|
||||
private readonly Label _noMagazineLabel;
|
||||
|
||||
public StatusControl(ClientPumpBarrelComponent parent)
|
||||
{
|
||||
MinHeight = 15;
|
||||
_parent = parent;
|
||||
HorizontalExpand = true;
|
||||
VerticalAlignment = VAlignment.Center;
|
||||
AddChild(new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
HorizontalExpand = true,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
SeparationOverride = 0,
|
||||
Children =
|
||||
{
|
||||
(_bulletsListTop = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
SeparationOverride = 0
|
||||
}),
|
||||
new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
HorizontalExpand = true,
|
||||
Children =
|
||||
{
|
||||
new Control
|
||||
{
|
||||
HorizontalExpand = true,
|
||||
Children =
|
||||
{
|
||||
(_bulletsListBottom = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
SeparationOverride = 0
|
||||
}),
|
||||
(_noMagazineLabel = new Label
|
||||
{
|
||||
Text = "No Magazine!",
|
||||
StyleClasses = {StyleNano.StyleClassItemStatus}
|
||||
})
|
||||
}
|
||||
},
|
||||
(_chamberedBullet = new TextureRect
|
||||
{
|
||||
Texture = StaticIoC.ResC.GetTexture("/Textures/Interface/ItemStatus/Bullets/chambered.png"),
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
HorizontalAlignment = HAlignment.Right,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
_chamberedBullet.ModulateSelfOverride =
|
||||
_parent.Chamber.chambered ?
|
||||
_parent.Chamber.spent ? Color.Red : Color.FromHex("#d7df60")
|
||||
: Color.Black;
|
||||
|
||||
_bulletsListTop.RemoveAllChildren();
|
||||
_bulletsListBottom.RemoveAllChildren();
|
||||
|
||||
if (_parent.MagazineCount == null)
|
||||
{
|
||||
_noMagazineLabel.Visible = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var (count, capacity) = _parent.MagazineCount.Value;
|
||||
|
||||
_noMagazineLabel.Visible = false;
|
||||
|
||||
string texturePath;
|
||||
if (capacity <= 20)
|
||||
{
|
||||
texturePath = "/Textures/Interface/ItemStatus/Bullets/normal.png";
|
||||
}
|
||||
else if (capacity <= 30)
|
||||
{
|
||||
texturePath = "/Textures/Interface/ItemStatus/Bullets/small.png";
|
||||
}
|
||||
else
|
||||
{
|
||||
texturePath = "/Textures/Interface/ItemStatus/Bullets/tiny.png";
|
||||
}
|
||||
|
||||
var texture = StaticIoC.ResC.GetTexture(texturePath);
|
||||
|
||||
const int tinyMaxRow = 60;
|
||||
|
||||
if (capacity > tinyMaxRow)
|
||||
{
|
||||
FillBulletRow(_bulletsListBottom, Math.Min(tinyMaxRow, count), tinyMaxRow, texture);
|
||||
FillBulletRow(_bulletsListTop, Math.Max(0, count - tinyMaxRow), capacity - tinyMaxRow, texture);
|
||||
}
|
||||
else
|
||||
{
|
||||
FillBulletRow(_bulletsListBottom, count, capacity, texture);
|
||||
}
|
||||
}
|
||||
|
||||
private static void FillBulletRow(Control container, int count, int capacity, Texture texture)
|
||||
{
|
||||
var colorA = Color.FromHex("#b68f0e");
|
||||
var colorB = Color.FromHex("#d7df60");
|
||||
var colorGoneA = Color.FromHex("#000000");
|
||||
var colorGoneB = Color.FromHex("#222222");
|
||||
|
||||
var altColor = false;
|
||||
|
||||
for (var i = count; i < capacity; i++)
|
||||
{
|
||||
container.AddChild(new TextureRect
|
||||
{
|
||||
Texture = texture,
|
||||
ModulateSelfOverride = altColor ? colorGoneA : colorGoneB
|
||||
});
|
||||
|
||||
altColor ^= true;
|
||||
}
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
container.AddChild(new TextureRect
|
||||
{
|
||||
Texture = texture,
|
||||
ModulateSelfOverride = altColor ? colorA : colorB
|
||||
});
|
||||
|
||||
altColor ^= true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
using Content.Client.IoC;
|
||||
using Content.Client.Items.Components;
|
||||
using Content.Client.Resources;
|
||||
using Content.Shared.Weapons.Ranged.Barrels.Components;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
namespace Content.Client.Weapons.Ranged.Barrels.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
[NetworkedComponent()]
|
||||
public sealed class ClientRevolverBarrelComponent : Component, IItemStatus
|
||||
{
|
||||
private StatusControl? _statusControl;
|
||||
|
||||
/// <summary>
|
||||
/// A array that lists the bullet states
|
||||
/// true means a spent bullet
|
||||
/// false means a "shootable" bullet
|
||||
/// null means no bullet
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public bool?[] Bullets { get; private set; } = new bool?[0];
|
||||
|
||||
[ViewVariables]
|
||||
public int CurrentSlot { get; private set; }
|
||||
|
||||
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
|
||||
{
|
||||
base.HandleComponentState(curState, nextState);
|
||||
|
||||
if (curState is not RevolverBarrelComponentState cast)
|
||||
return;
|
||||
|
||||
CurrentSlot = cast.CurrentSlot;
|
||||
Bullets = cast.Bullets;
|
||||
_statusControl?.Update();
|
||||
}
|
||||
|
||||
public Control MakeControl()
|
||||
{
|
||||
_statusControl = new StatusControl(this);
|
||||
_statusControl.Update();
|
||||
return _statusControl;
|
||||
}
|
||||
|
||||
public void DestroyControl(Control control)
|
||||
{
|
||||
if (_statusControl == control)
|
||||
{
|
||||
_statusControl = null;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class StatusControl : Control
|
||||
{
|
||||
private readonly ClientRevolverBarrelComponent _parent;
|
||||
private readonly BoxContainer _bulletsList;
|
||||
|
||||
public StatusControl(ClientRevolverBarrelComponent parent)
|
||||
{
|
||||
MinHeight = 15;
|
||||
_parent = parent;
|
||||
HorizontalExpand = true;
|
||||
VerticalAlignment = VAlignment.Center;
|
||||
AddChild((_bulletsList = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
HorizontalExpand = true,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
SeparationOverride = 0
|
||||
}));
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
_bulletsList.RemoveAllChildren();
|
||||
|
||||
var capacity = _parent.Bullets.Length;
|
||||
|
||||
string texturePath;
|
||||
if (capacity <= 20)
|
||||
{
|
||||
texturePath = "/Textures/Interface/ItemStatus/Bullets/normal.png";
|
||||
}
|
||||
else if (capacity <= 30)
|
||||
{
|
||||
texturePath = "/Textures/Interface/ItemStatus/Bullets/small.png";
|
||||
}
|
||||
else
|
||||
{
|
||||
texturePath = "/Textures/Interface/ItemStatus/Bullets/tiny.png";
|
||||
}
|
||||
|
||||
var texture = StaticIoC.ResC.GetTexture(texturePath);
|
||||
var spentTexture = StaticIoC.ResC.GetTexture("/Textures/Interface/ItemStatus/Bullets/empty.png");
|
||||
|
||||
FillBulletRow(_bulletsList, texture, spentTexture);
|
||||
}
|
||||
|
||||
private void FillBulletRow(Control container, Texture texture, Texture emptyTexture)
|
||||
{
|
||||
var colorA = Color.FromHex("#b68f0e");
|
||||
var colorB = Color.FromHex("#d7df60");
|
||||
var colorSpentA = Color.FromHex("#b50e25");
|
||||
var colorSpentB = Color.FromHex("#d3745f");
|
||||
var colorGoneA = Color.FromHex("#000000");
|
||||
var colorGoneB = Color.FromHex("#222222");
|
||||
|
||||
var altColor = false;
|
||||
var scale = 1.3f;
|
||||
|
||||
for (var i = 0; i < _parent.Bullets.Length; i++)
|
||||
{
|
||||
var bulletSpent = _parent.Bullets[i];
|
||||
// Add a outline
|
||||
var box = new Control()
|
||||
{
|
||||
MinSize = texture.Size * scale,
|
||||
};
|
||||
if (i == _parent.CurrentSlot)
|
||||
{
|
||||
box.AddChild(new TextureRect
|
||||
{
|
||||
Texture = texture,
|
||||
TextureScale = (scale, scale),
|
||||
ModulateSelfOverride = Color.LimeGreen,
|
||||
});
|
||||
}
|
||||
Color color;
|
||||
Texture bulletTexture = texture;
|
||||
|
||||
if (bulletSpent.HasValue)
|
||||
{
|
||||
if (bulletSpent.Value)
|
||||
{
|
||||
color = altColor ? colorSpentA : colorSpentB;
|
||||
bulletTexture = emptyTexture;
|
||||
}
|
||||
else
|
||||
{
|
||||
color = altColor ? colorA : colorB;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
color = altColor ? colorGoneA : colorGoneB;
|
||||
}
|
||||
|
||||
box.AddChild(new TextureRect
|
||||
{
|
||||
Stretch = TextureRect.StretchMode.KeepCentered,
|
||||
Texture = bulletTexture,
|
||||
ModulateSelfOverride = color,
|
||||
});
|
||||
altColor ^= true;
|
||||
container.AddChild(box);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
using Content.Client.Weapons.Ranged.Barrels.Components;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Weapons.Ranged.Barrels.EntitySystems;
|
||||
|
||||
public sealed class ClientBatteryBarrelSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<ClientBatteryBarrelComponent, AppearanceChangeEvent>(OnAppearanceChange);
|
||||
}
|
||||
|
||||
private void OnAppearanceChange(EntityUid uid, ClientBatteryBarrelComponent component, ref AppearanceChangeEvent args)
|
||||
{
|
||||
component.ItemStatus?.Update(args.Component);
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
using Content.Shared.Weapons.Ranged.Barrels.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Client.Weapons.Ranged.Barrels.Visualizers
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class BarrelBoltVisualizer : AppearanceVisualizer
|
||||
{
|
||||
public override void InitializeEntity(EntityUid entity)
|
||||
{
|
||||
base.InitializeEntity(entity);
|
||||
var sprite = IoCManager.Resolve<IEntityManager>().GetComponent<ISpriteComponent>(entity);
|
||||
sprite.LayerSetState(RangedBarrelVisualLayers.Bolt, "bolt-open");
|
||||
}
|
||||
|
||||
public override void OnChangeData(AppearanceComponent component)
|
||||
{
|
||||
base.OnChangeData(component);
|
||||
var sprite = IoCManager.Resolve<IEntityManager>().GetComponent<ISpriteComponent>(component.Owner);
|
||||
|
||||
if (!component.TryGetData(BarrelBoltVisuals.BoltOpen, out bool boltOpen))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (boltOpen)
|
||||
{
|
||||
sprite.LayerSetState(RangedBarrelVisualLayers.Bolt, "bolt-open");
|
||||
}
|
||||
else
|
||||
{
|
||||
sprite.LayerSetState(RangedBarrelVisualLayers.Bolt, "bolt-closed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
using Content.Shared.Rounding;
|
||||
using Content.Shared.Weapons.Ranged.Barrels.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Content.Client.Weapons.Ranged.Barrels.Visualizers
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class MagVisualizer : AppearanceVisualizer
|
||||
{
|
||||
private bool _magLoaded;
|
||||
[DataField("magState")]
|
||||
private string? _magState;
|
||||
[DataField("steps")]
|
||||
private int _magSteps;
|
||||
[DataField("zeroVisible")]
|
||||
private bool _zeroVisible;
|
||||
|
||||
public override void InitializeEntity(EntityUid entity)
|
||||
{
|
||||
base.InitializeEntity(entity);
|
||||
var sprite = IoCManager.Resolve<IEntityManager>().GetComponent<ISpriteComponent>(entity);
|
||||
|
||||
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)
|
||||
{
|
||||
base.OnChangeData(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 = IoCManager.Resolve<IEntityManager>().GetComponent<ISpriteComponent>(component.Owner);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
using Content.Shared.Weapons.Ranged.Barrels.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Client.Weapons.Ranged.Barrels.Visualizers
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class SpentAmmoVisualizer : AppearanceVisualizer
|
||||
{
|
||||
public override void OnChangeData(AppearanceComponent component)
|
||||
{
|
||||
base.OnChangeData(component);
|
||||
var sprite = IoCManager.Resolve<IEntityManager>().GetComponent<ISpriteComponent>(component.Owner);
|
||||
|
||||
if (!component.TryGetData(AmmoVisuals.Spent, out bool spent))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
sprite.LayerSetState(AmmoVisualLayers.Base, spent ? "spent" : "base");
|
||||
}
|
||||
}
|
||||
|
||||
public enum AmmoVisualLayers : byte
|
||||
{
|
||||
Base,
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
using Content.Shared.Weapons.Ranged.Components;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Content.Client.Weapons.Ranged
|
||||
{
|
||||
// Yeah I put it all in the same enum, don't judge me
|
||||
public enum RangedBarrelVisualLayers : byte
|
||||
{
|
||||
Base,
|
||||
BaseUnshaded,
|
||||
Bolt,
|
||||
Mag,
|
||||
MagUnshaded,
|
||||
}
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class ClientRangedWeaponComponent : SharedRangedWeaponComponent
|
||||
{
|
||||
public FireRateSelector FireRateSelector { get; private set; } = FireRateSelector.Automatic;
|
||||
|
||||
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
|
||||
{
|
||||
base.HandleComponentState(curState, nextState);
|
||||
if (curState is not RangedWeaponComponentState rangedState)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FireRateSelector = rangedState.FireRateSelector;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Client.Weapons.Ranged.Systems;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
namespace Content.Client.Weapons.Ranged;
|
||||
@@ -0,0 +1,10 @@
|
||||
using Content.Shared.Weapons.Ranged.Components;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client.Weapons.Ranged.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class AmmoCounterComponent : SharedAmmoCounterComponent
|
||||
{
|
||||
public Control? Control;
|
||||
}
|
||||
107
Content.Client/Weapons/Ranged/Components/MagVisualizer.cs
Normal file
107
Content.Client/Weapons/Ranged/Components/MagVisualizer.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
using Content.Shared.Rounding;
|
||||
using Content.Shared.Weapons.Ranged.Systems;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using SharedGunSystem = Content.Shared.Weapons.Ranged.Systems.SharedGunSystem;
|
||||
|
||||
namespace Content.Client.Weapons.Ranged.Components;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class MagVisualizer : AppearanceVisualizer
|
||||
{
|
||||
[DataField("magState")] private string? _magState;
|
||||
[DataField("steps")] private int _magSteps;
|
||||
[DataField("zeroVisible")] private bool _zeroVisible;
|
||||
|
||||
public override void InitializeEntity(EntityUid entity)
|
||||
{
|
||||
base.InitializeEntity(entity);
|
||||
var sprite = IoCManager.Resolve<IEntityManager>().GetComponent<ISpriteComponent>(entity);
|
||||
|
||||
if (sprite.LayerMapTryGet(GunVisualLayers.Mag, out _))
|
||||
{
|
||||
sprite.LayerSetState(GunVisualLayers.Mag, $"{_magState}-{_magSteps - 1}");
|
||||
sprite.LayerSetVisible(GunVisualLayers.Mag, false);
|
||||
}
|
||||
|
||||
if (sprite.LayerMapTryGet(GunVisualLayers.MagUnshaded, out _))
|
||||
{
|
||||
sprite.LayerSetState(GunVisualLayers.MagUnshaded, $"{_magState}-unshaded-{_magSteps - 1}");
|
||||
sprite.LayerSetVisible(GunVisualLayers.MagUnshaded, false);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnChangeData(AppearanceComponent component)
|
||||
{
|
||||
base.OnChangeData(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 = IoCManager.Resolve<IEntityManager>().GetComponent<ISpriteComponent>(component.Owner);
|
||||
|
||||
if (!component.TryGetData(AmmoVisuals.MagLoaded, out bool magloaded) ||
|
||||
magloaded)
|
||||
{
|
||||
if (!component.TryGetData(AmmoVisuals.AmmoMax, out int capacity))
|
||||
{
|
||||
capacity = _magSteps;
|
||||
}
|
||||
|
||||
if (!component.TryGetData(AmmoVisuals.AmmoCount, out int current))
|
||||
{
|
||||
current = _magSteps;
|
||||
}
|
||||
|
||||
var step = ContentHelpers.RoundToLevels(current, capacity, _magSteps);
|
||||
|
||||
if (step == 0 && !_zeroVisible)
|
||||
{
|
||||
if (sprite.LayerMapTryGet(GunVisualLayers.Mag, out _))
|
||||
{
|
||||
sprite.LayerSetVisible(GunVisualLayers.Mag, false);
|
||||
}
|
||||
|
||||
if (sprite.LayerMapTryGet(GunVisualLayers.MagUnshaded, out _))
|
||||
{
|
||||
sprite.LayerSetVisible(GunVisualLayers.MagUnshaded, false);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (sprite.LayerMapTryGet(GunVisualLayers.Mag, out _))
|
||||
{
|
||||
sprite.LayerSetVisible(GunVisualLayers.Mag, true);
|
||||
sprite.LayerSetState(GunVisualLayers.Mag, $"{_magState}-{step}");
|
||||
}
|
||||
|
||||
if (sprite.LayerMapTryGet(GunVisualLayers.MagUnshaded, out _))
|
||||
{
|
||||
sprite.LayerSetVisible(GunVisualLayers.MagUnshaded, true);
|
||||
sprite.LayerSetState(GunVisualLayers.MagUnshaded, $"{_magState}-unshaded-{step}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (sprite.LayerMapTryGet(GunVisualLayers.Mag, out _))
|
||||
{
|
||||
sprite.LayerSetVisible(GunVisualLayers.Mag, false);
|
||||
}
|
||||
|
||||
if (sprite.LayerMapTryGet(GunVisualLayers.MagUnshaded, out _))
|
||||
{
|
||||
sprite.LayerSetVisible(GunVisualLayers.MagUnshaded, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum GunVisualLayers : byte
|
||||
{
|
||||
Base,
|
||||
BaseUnshaded,
|
||||
Mag,
|
||||
MagUnshaded,
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Content.Client.Weapons.Ranged.Systems;
|
||||
|
||||
namespace Content.Client.Weapons.Ranged.Components;
|
||||
|
||||
[RegisterComponent, Friend(typeof(GunSystem))]
|
||||
public sealed class SpentAmmoVisualsComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Should we do "{_state}-spent" or just "spent"
|
||||
/// </summary>
|
||||
[DataField("suffix")] public bool Suffix = true;
|
||||
|
||||
[DataField("state")]
|
||||
public string State = "base";
|
||||
}
|
||||
|
||||
public enum AmmoVisualLayers : byte
|
||||
{
|
||||
Base,
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
using Content.Client.Weapons.Ranged.Barrels.Components;
|
||||
using Content.Shared.Weapons.Ranged;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Client.Weapons.Ranged;
|
||||
|
||||
public sealed class GunSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeNetworkEvent<MagazineAutoEjectEvent>(OnMagAutoEject);
|
||||
}
|
||||
|
||||
private void OnMagAutoEject(MagazineAutoEjectEvent ev)
|
||||
{
|
||||
var player = _playerManager.LocalPlayer?.ControlledEntity;
|
||||
|
||||
if (!TryComp(ev.Uid, out ClientMagazineBarrelComponent? mag) ||
|
||||
!_container.TryGetContainingContainer(ev.Uid, out var container) ||
|
||||
container.Owner != player) return;
|
||||
|
||||
mag.PlayAlarmAnimation();
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
using System;
|
||||
using Content.Client.CombatMode;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Weapons.Ranged.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Weapons.Ranged
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class RangedWeaponSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly InputSystem _inputSystem = default!;
|
||||
[Dependency] private readonly CombatModeSystem _combatModeSystem = default!;
|
||||
|
||||
private bool _blocked;
|
||||
private int _shotCounter;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
UpdatesOutsidePrediction = true;
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
if (!_gameTiming.IsFirstTimePredicted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var state = _inputSystem.CmdStates.GetState(EngineKeyFunctions.Use);
|
||||
if (!_combatModeSystem.IsInCombatMode() || state != BoundKeyState.Down)
|
||||
{
|
||||
_shotCounter = 0;
|
||||
_blocked = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var entity = _playerManager.LocalPlayer?.ControlledEntity;
|
||||
if (!EntityManager.TryGetComponent(entity, out SharedHandsComponent? hands))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (hands.ActiveHandEntity is not EntityUid held || !EntityManager.TryGetComponent(held, out ClientRangedWeaponComponent? weapon))
|
||||
{
|
||||
_blocked = true;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (weapon.FireRateSelector)
|
||||
{
|
||||
case FireRateSelector.Safety:
|
||||
_blocked = true;
|
||||
return;
|
||||
case FireRateSelector.Single:
|
||||
if (_shotCounter >= 1)
|
||||
{
|
||||
_blocked = true;
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
case FireRateSelector.Automatic:
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
if (_blocked)
|
||||
return;
|
||||
|
||||
var mapCoordinates = _eyeManager.ScreenToMap(_inputManager.MouseScreenPosition);
|
||||
EntityCoordinates coordinates;
|
||||
|
||||
if (_mapManager.TryFindGridAt(mapCoordinates, out var grid))
|
||||
{
|
||||
coordinates = EntityCoordinates.FromMap(grid.GridEntityId, mapCoordinates);
|
||||
}
|
||||
else
|
||||
{
|
||||
coordinates = EntityCoordinates.FromMap(_mapManager.GetMapEntityId(mapCoordinates.MapId), mapCoordinates);
|
||||
}
|
||||
|
||||
SyncFirePos(coordinates);
|
||||
}
|
||||
|
||||
private void SyncFirePos(EntityCoordinates coordinates)
|
||||
{
|
||||
RaiseNetworkEvent(new FirePosEvent(coordinates));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
using Content.Client.Projectiles;
|
||||
using Content.Shared.Weapons.Ranged;
|
||||
using Content.Shared.Weapons.Ranged.Components;
|
||||
using Content.Shared.Weapons.Ranged.Systems;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Client.Weapons.Ranged;
|
||||
namespace Content.Client.Weapons.Ranged.Systems;
|
||||
|
||||
public sealed class FlyBySoundSystem : SharedFlyBySoundSystem
|
||||
{
|
||||
513
Content.Client/Weapons/Ranged/Systems/GunSystem.AmmoCounter.cs
Normal file
513
Content.Client/Weapons/Ranged/Systems/GunSystem.AmmoCounter.cs
Normal file
@@ -0,0 +1,513 @@
|
||||
using Content.Client.IoC;
|
||||
using Content.Client.Items;
|
||||
using Content.Client.Resources;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Client.Weapons.Ranged.Components;
|
||||
using Robust.Client.Animations;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
|
||||
namespace Content.Client.Weapons.Ranged.Systems;
|
||||
|
||||
public sealed partial class GunSystem
|
||||
{
|
||||
private void OnAmmoCounterCollect(EntityUid uid, AmmoCounterComponent component, ItemStatusCollectMessage args)
|
||||
{
|
||||
RefreshControl(uid, component);
|
||||
|
||||
if (component.Control != null)
|
||||
args.Controls.Add(component.Control);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the control being used to show ammo. Useful if you change the AmmoProvider.
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="component"></param>
|
||||
private void RefreshControl(EntityUid uid, AmmoCounterComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component, false)) return;
|
||||
|
||||
component.Control?.Dispose();
|
||||
component.Control = null;
|
||||
|
||||
var ev = new AmmoCounterControlEvent();
|
||||
RaiseLocalEvent(uid, ev, false);
|
||||
|
||||
// Fallback to default if none specified
|
||||
ev.Control ??= new DefaultStatusControl();
|
||||
|
||||
component.Control = ev.Control;
|
||||
UpdateAmmoCount(uid, component);
|
||||
}
|
||||
|
||||
private void UpdateAmmoCount(EntityUid uid, AmmoCounterComponent component)
|
||||
{
|
||||
if (component.Control == null) return;
|
||||
|
||||
var ev = new UpdateAmmoCounterEvent()
|
||||
{
|
||||
Control = component.Control
|
||||
};
|
||||
|
||||
RaiseLocalEvent(uid, ev, false);
|
||||
}
|
||||
|
||||
protected override void UpdateAmmoCount(EntityUid uid)
|
||||
{
|
||||
// Don't use resolves because the method is shared and there's no compref and I'm trying to
|
||||
// share as much code as possible
|
||||
if (!Timing.IsFirstTimePredicted ||
|
||||
!TryComp<AmmoCounterComponent>(uid, out var clientComp)) return;
|
||||
|
||||
UpdateAmmoCount(uid, clientComp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised when an ammocounter is requesting a control.
|
||||
/// </summary>
|
||||
public sealed class AmmoCounterControlEvent : EntityEventArgs
|
||||
{
|
||||
public Control? Control;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised whenever the ammo count / magazine for a control needs updating.
|
||||
/// </summary>
|
||||
public sealed class UpdateAmmoCounterEvent : HandledEntityEventArgs
|
||||
{
|
||||
public Control Control = default!;
|
||||
}
|
||||
|
||||
#region Controls
|
||||
|
||||
private sealed class DefaultStatusControl : Control
|
||||
{
|
||||
private readonly BoxContainer _bulletsListTop;
|
||||
private readonly BoxContainer _bulletsListBottom;
|
||||
|
||||
public DefaultStatusControl()
|
||||
{
|
||||
MinHeight = 15;
|
||||
HorizontalExpand = true;
|
||||
VerticalAlignment = VAlignment.Center;
|
||||
AddChild(new BoxContainer
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Vertical,
|
||||
HorizontalExpand = true,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
SeparationOverride = 0,
|
||||
Children =
|
||||
{
|
||||
(_bulletsListTop = new BoxContainer
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||||
SeparationOverride = 0
|
||||
}),
|
||||
new BoxContainer
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||||
HorizontalExpand = true,
|
||||
Children =
|
||||
{
|
||||
new Control
|
||||
{
|
||||
HorizontalExpand = true,
|
||||
Children =
|
||||
{
|
||||
(_bulletsListBottom = new BoxContainer
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
SeparationOverride = 0
|
||||
}),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void Update(int count, int capacity)
|
||||
{
|
||||
_bulletsListTop.RemoveAllChildren();
|
||||
_bulletsListBottom.RemoveAllChildren();
|
||||
|
||||
string texturePath;
|
||||
if (capacity <= 20)
|
||||
{
|
||||
texturePath = "/Textures/Interface/ItemStatus/Bullets/normal.png";
|
||||
}
|
||||
else if (capacity <= 30)
|
||||
{
|
||||
texturePath = "/Textures/Interface/ItemStatus/Bullets/small.png";
|
||||
}
|
||||
else
|
||||
{
|
||||
texturePath = "/Textures/Interface/ItemStatus/Bullets/tiny.png";
|
||||
}
|
||||
|
||||
var texture = StaticIoC.ResC.GetTexture(texturePath);
|
||||
|
||||
const int tinyMaxRow = 60;
|
||||
|
||||
if (capacity > tinyMaxRow)
|
||||
{
|
||||
FillBulletRow(_bulletsListBottom, Math.Min(tinyMaxRow, count), tinyMaxRow, texture);
|
||||
FillBulletRow(_bulletsListTop, Math.Max(0, count - tinyMaxRow), capacity - tinyMaxRow, texture);
|
||||
}
|
||||
else
|
||||
{
|
||||
FillBulletRow(_bulletsListBottom, count, capacity, texture);
|
||||
}
|
||||
}
|
||||
|
||||
private static void FillBulletRow(Control container, int count, int capacity, Texture texture)
|
||||
{
|
||||
var colorA = Color.FromHex("#b68f0e");
|
||||
var colorB = Color.FromHex("#d7df60");
|
||||
var colorGoneA = Color.FromHex("#000000");
|
||||
var colorGoneB = Color.FromHex("#222222");
|
||||
|
||||
var altColor = false;
|
||||
|
||||
for (var i = count; i < capacity; i++)
|
||||
{
|
||||
container.AddChild(new TextureRect
|
||||
{
|
||||
Texture = texture,
|
||||
ModulateSelfOverride = altColor ? colorGoneA : colorGoneB
|
||||
});
|
||||
|
||||
altColor ^= true;
|
||||
}
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
container.AddChild(new TextureRect
|
||||
{
|
||||
Texture = texture,
|
||||
ModulateSelfOverride = altColor ? colorA : colorB
|
||||
});
|
||||
|
||||
altColor ^= true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class BoxesStatusControl : Control
|
||||
{
|
||||
private readonly BoxContainer _bulletsList;
|
||||
private readonly Label _ammoCount;
|
||||
|
||||
public BoxesStatusControl()
|
||||
{
|
||||
MinHeight = 15;
|
||||
HorizontalExpand = true;
|
||||
VerticalAlignment = VAlignment.Center;
|
||||
|
||||
AddChild(new BoxContainer
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||||
HorizontalExpand = true,
|
||||
Children =
|
||||
{
|
||||
new Control
|
||||
{
|
||||
HorizontalExpand = true,
|
||||
Children =
|
||||
{
|
||||
(_bulletsList = new BoxContainer
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
SeparationOverride = 4
|
||||
}),
|
||||
}
|
||||
},
|
||||
new Control() { MinSize = (5, 0) },
|
||||
(_ammoCount = new Label
|
||||
{
|
||||
StyleClasses = { StyleNano.StyleClassItemStatus },
|
||||
HorizontalAlignment = HAlignment.Right,
|
||||
}),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void Update(int count, int max)
|
||||
{
|
||||
_bulletsList.RemoveAllChildren();
|
||||
|
||||
_ammoCount.Visible = true;
|
||||
|
||||
_ammoCount.Text = $"x{count:00}";
|
||||
max = Math.Min(max, 8);
|
||||
FillBulletRow(_bulletsList, count, max);
|
||||
}
|
||||
|
||||
private static void FillBulletRow(Control container, int count, int capacity)
|
||||
{
|
||||
var colorGone = Color.FromHex("#000000");
|
||||
var color = Color.FromHex("#E00000");
|
||||
|
||||
// Draw the empty ones
|
||||
for (var i = count; i < capacity; i++)
|
||||
{
|
||||
container.AddChild(new PanelContainer
|
||||
{
|
||||
PanelOverride = new StyleBoxFlat()
|
||||
{
|
||||
BackgroundColor = colorGone,
|
||||
},
|
||||
MinSize = (10, 15),
|
||||
});
|
||||
}
|
||||
|
||||
// Draw the full ones, but limit the count to the capacity
|
||||
count = Math.Min(count, capacity);
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
container.AddChild(new PanelContainer
|
||||
{
|
||||
PanelOverride = new StyleBoxFlat()
|
||||
{
|
||||
BackgroundColor = color,
|
||||
},
|
||||
MinSize = (10, 15),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ChamberMagazineStatusControl : Control
|
||||
{
|
||||
private readonly BoxContainer _bulletsList;
|
||||
private readonly TextureRect _chamberedBullet;
|
||||
private readonly Label _noMagazineLabel;
|
||||
private readonly Label _ammoCount;
|
||||
|
||||
public ChamberMagazineStatusControl()
|
||||
{
|
||||
MinHeight = 15;
|
||||
HorizontalExpand = true;
|
||||
VerticalAlignment = VAlignment.Center;
|
||||
|
||||
AddChild(new BoxContainer
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||||
HorizontalExpand = true,
|
||||
Children =
|
||||
{
|
||||
(_chamberedBullet = new TextureRect
|
||||
{
|
||||
Texture = StaticIoC.ResC.GetTexture("/Textures/Interface/ItemStatus/Bullets/chambered_rotated.png"),
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
HorizontalAlignment = HAlignment.Right,
|
||||
}),
|
||||
new Control() { MinSize = (5,0) },
|
||||
new Control
|
||||
{
|
||||
HorizontalExpand = true,
|
||||
Children =
|
||||
{
|
||||
(_bulletsList = new BoxContainer
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
SeparationOverride = 0
|
||||
}),
|
||||
(_noMagazineLabel = new Label
|
||||
{
|
||||
Text = "No Magazine!",
|
||||
StyleClasses = {StyleNano.StyleClassItemStatus}
|
||||
})
|
||||
}
|
||||
},
|
||||
new Control() { MinSize = (5,0) },
|
||||
(_ammoCount = new Label
|
||||
{
|
||||
StyleClasses = {StyleNano.StyleClassItemStatus},
|
||||
HorizontalAlignment = HAlignment.Right,
|
||||
}),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void Update(bool chambered, bool magazine, int count, int capacity)
|
||||
{
|
||||
_chamberedBullet.ModulateSelfOverride =
|
||||
chambered ? Color.FromHex("#d7df60") : Color.Black;
|
||||
|
||||
_bulletsList.RemoveAllChildren();
|
||||
|
||||
if (!magazine)
|
||||
{
|
||||
_noMagazineLabel.Visible = true;
|
||||
_ammoCount.Visible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
_noMagazineLabel.Visible = false;
|
||||
_ammoCount.Visible = true;
|
||||
|
||||
var texturePath = "/Textures/Interface/ItemStatus/Bullets/normal.png";
|
||||
var texture = StaticIoC.ResC.GetTexture(texturePath);
|
||||
|
||||
_ammoCount.Text = $"x{count:00}";
|
||||
capacity = Math.Min(capacity, 20);
|
||||
FillBulletRow(_bulletsList, count, capacity, texture);
|
||||
}
|
||||
|
||||
private static void FillBulletRow(Control container, int count, int capacity, Texture texture)
|
||||
{
|
||||
var colorA = Color.FromHex("#b68f0e");
|
||||
var colorB = Color.FromHex("#d7df60");
|
||||
var colorGoneA = Color.FromHex("#000000");
|
||||
var colorGoneB = Color.FromHex("#222222");
|
||||
|
||||
var altColor = false;
|
||||
|
||||
// Draw the empty ones
|
||||
for (var i = count; i < capacity; i++)
|
||||
{
|
||||
container.AddChild(new TextureRect
|
||||
{
|
||||
Texture = texture,
|
||||
ModulateSelfOverride = altColor ? colorGoneA : colorGoneB,
|
||||
Stretch = TextureRect.StretchMode.KeepCentered
|
||||
});
|
||||
|
||||
altColor ^= true;
|
||||
}
|
||||
|
||||
// Draw the full ones, but limit the count to the capacity
|
||||
count = Math.Min(count, capacity);
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
container.AddChild(new TextureRect
|
||||
{
|
||||
Texture = texture,
|
||||
ModulateSelfOverride = altColor ? colorA : colorB,
|
||||
Stretch = TextureRect.StretchMode.KeepCentered
|
||||
});
|
||||
|
||||
altColor ^= true;
|
||||
}
|
||||
}
|
||||
|
||||
public void PlayAlarmAnimation(Animation animation)
|
||||
{
|
||||
_noMagazineLabel.PlayAnimation(animation, "alarm");
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class RevolverStatusControl : Control
|
||||
{
|
||||
private readonly BoxContainer _bulletsList;
|
||||
|
||||
public RevolverStatusControl()
|
||||
{
|
||||
MinHeight = 15;
|
||||
HorizontalExpand = true;
|
||||
VerticalAlignment = VAlignment.Center;
|
||||
AddChild((_bulletsList = new BoxContainer
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||||
HorizontalExpand = true,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
SeparationOverride = 0
|
||||
}));
|
||||
}
|
||||
|
||||
public void Update(int currentIndex, bool?[] bullets)
|
||||
{
|
||||
_bulletsList.RemoveAllChildren();
|
||||
var capacity = bullets.Length;
|
||||
|
||||
string texturePath;
|
||||
if (capacity <= 20)
|
||||
{
|
||||
texturePath = "/Textures/Interface/ItemStatus/Bullets/normal.png";
|
||||
}
|
||||
else if (capacity <= 30)
|
||||
{
|
||||
texturePath = "/Textures/Interface/ItemStatus/Bullets/small.png";
|
||||
}
|
||||
else
|
||||
{
|
||||
texturePath = "/Textures/Interface/ItemStatus/Bullets/tiny.png";
|
||||
}
|
||||
|
||||
var texture = StaticIoC.ResC.GetTexture(texturePath);
|
||||
var spentTexture = StaticIoC.ResC.GetTexture("/Textures/Interface/ItemStatus/Bullets/empty.png");
|
||||
|
||||
FillBulletRow(currentIndex, bullets, _bulletsList, texture, spentTexture);
|
||||
}
|
||||
|
||||
private void FillBulletRow(int currentIndex, bool?[] bullets, Control container, Texture texture, Texture emptyTexture)
|
||||
{
|
||||
var capacity = bullets.Length;
|
||||
var colorA = Color.FromHex("#b68f0e");
|
||||
var colorB = Color.FromHex("#d7df60");
|
||||
var colorSpentA = Color.FromHex("#b50e25");
|
||||
var colorSpentB = Color.FromHex("#d3745f");
|
||||
var colorGoneA = Color.FromHex("#000000");
|
||||
var colorGoneB = Color.FromHex("#222222");
|
||||
|
||||
var altColor = false;
|
||||
var scale = 1.3f;
|
||||
|
||||
for (var i = 0; i < capacity; i++)
|
||||
{
|
||||
var bulletFree = bullets[i];
|
||||
// Add a outline
|
||||
var box = new Control()
|
||||
{
|
||||
MinSize = texture.Size * scale,
|
||||
};
|
||||
if (i == currentIndex)
|
||||
{
|
||||
box.AddChild(new TextureRect
|
||||
{
|
||||
Texture = texture,
|
||||
TextureScale = (scale, scale),
|
||||
ModulateSelfOverride = Color.LimeGreen,
|
||||
});
|
||||
}
|
||||
Color color;
|
||||
Texture bulletTexture = texture;
|
||||
|
||||
if (bulletFree.HasValue)
|
||||
{
|
||||
if (bulletFree.Value)
|
||||
{
|
||||
color = altColor ? colorA : colorB;
|
||||
}
|
||||
else
|
||||
{
|
||||
color = altColor ? colorSpentA : colorSpentB;
|
||||
bulletTexture = emptyTexture;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
color = altColor ? colorGoneA : colorGoneB;
|
||||
}
|
||||
|
||||
box.AddChild(new TextureRect
|
||||
{
|
||||
Stretch = TextureRect.StretchMode.KeepCentered,
|
||||
Texture = bulletTexture,
|
||||
ModulateSelfOverride = color,
|
||||
});
|
||||
altColor ^= true;
|
||||
container.AddChild(box);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
48
Content.Client/Weapons/Ranged/Systems/GunSystem.Ballistic.cs
Normal file
48
Content.Client/Weapons/Ranged/Systems/GunSystem.Ballistic.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using Content.Shared.Weapons.Ranged.Components;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Client.Weapons.Ranged.Systems;
|
||||
|
||||
public sealed partial class GunSystem
|
||||
{
|
||||
protected override void InitializeBallistic()
|
||||
{
|
||||
base.InitializeBallistic();
|
||||
SubscribeLocalEvent<BallisticAmmoProviderComponent, UpdateAmmoCounterEvent>(OnBallisticAmmoCount);
|
||||
}
|
||||
|
||||
private void OnBallisticAmmoCount(EntityUid uid, BallisticAmmoProviderComponent component, UpdateAmmoCounterEvent args)
|
||||
{
|
||||
if (args.Control is DefaultStatusControl control)
|
||||
{
|
||||
control.Update(GetBallisticShots(component), component.Capacity);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Cycle(BallisticAmmoProviderComponent component, MapCoordinates coordinates)
|
||||
{
|
||||
if (!Timing.IsFirstTimePredicted) return;
|
||||
|
||||
EntityUid? ent = null;
|
||||
|
||||
// TODO: Combine with TakeAmmo
|
||||
if (component.Entities.Count > 0)
|
||||
{
|
||||
var existing = component.Entities[^1];
|
||||
component.Entities.RemoveAt(component.Entities.Count - 1);
|
||||
|
||||
component.Container.Remove(existing);
|
||||
EnsureComp<AmmoComponent>(existing);
|
||||
}
|
||||
else if (component.UnspawnedCount > 0)
|
||||
{
|
||||
component.UnspawnedCount--;
|
||||
ent = Spawn(component.FillProto, coordinates);
|
||||
EnsureComp<AmmoComponent>(ent.Value);
|
||||
}
|
||||
|
||||
if (ent != null && ent.Value.IsClientSide())
|
||||
Del(ent.Value);
|
||||
}
|
||||
}
|
||||
30
Content.Client/Weapons/Ranged/Systems/GunSystem.Battery.cs
Normal file
30
Content.Client/Weapons/Ranged/Systems/GunSystem.Battery.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using Content.Shared.Weapons.Ranged.Components;
|
||||
|
||||
namespace Content.Client.Weapons.Ranged.Systems;
|
||||
|
||||
public sealed partial class GunSystem
|
||||
{
|
||||
protected override void InitializeBattery()
|
||||
{
|
||||
base.InitializeBattery();
|
||||
// Hitscan
|
||||
SubscribeLocalEvent<HitscanBatteryAmmoProviderComponent, AmmoCounterControlEvent>(OnControl);
|
||||
SubscribeLocalEvent<HitscanBatteryAmmoProviderComponent, UpdateAmmoCounterEvent>(OnAmmoCountUpdate);
|
||||
|
||||
// Projectile
|
||||
SubscribeLocalEvent<ProjectileBatteryAmmoProviderComponent, AmmoCounterControlEvent>(OnControl);
|
||||
SubscribeLocalEvent<ProjectileBatteryAmmoProviderComponent, UpdateAmmoCounterEvent>(OnAmmoCountUpdate);
|
||||
}
|
||||
|
||||
private void OnAmmoCountUpdate(EntityUid uid, BatteryAmmoProviderComponent component, UpdateAmmoCounterEvent args)
|
||||
{
|
||||
if (args.Control is not BoxesStatusControl boxes) return;
|
||||
|
||||
boxes.Update(component.Shots, component.Capacity);
|
||||
}
|
||||
|
||||
private void OnControl(EntityUid uid, BatteryAmmoProviderComponent component, AmmoCounterControlEvent args)
|
||||
{
|
||||
args.Control = new BoxesStatusControl();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Weapons.Ranged.Components;
|
||||
using Content.Shared.Weapons.Ranged.Events;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Client.Weapons.Ranged.Systems;
|
||||
|
||||
public sealed partial class GunSystem
|
||||
{
|
||||
protected override void InitializeChamberMagazine()
|
||||
{
|
||||
base.InitializeChamberMagazine();
|
||||
SubscribeLocalEvent<ChamberMagazineAmmoProviderComponent, AmmoCounterControlEvent>(OnChamberMagazineCounter);
|
||||
SubscribeLocalEvent<ChamberMagazineAmmoProviderComponent, UpdateAmmoCounterEvent>(OnChamberMagazineAmmoUpdate);
|
||||
SubscribeLocalEvent<ChamberMagazineAmmoProviderComponent, EntRemovedFromContainerMessage>(OnChamberEntRemove);
|
||||
}
|
||||
|
||||
private void OnChamberEntRemove(EntityUid uid, ChamberMagazineAmmoProviderComponent component, EntRemovedFromContainerMessage args)
|
||||
{
|
||||
if (args.Container.ID != ChamberSlot) return;
|
||||
|
||||
// This is dirty af. Prediction moment.
|
||||
// We may be predicting spawning entities and the engine just removes them from the container so we'll just delete them.
|
||||
if (args.Entity.IsClientSide())
|
||||
QueueDel(args.Entity);
|
||||
|
||||
// AFAIK the only main alternative is having some client-specific handling via a bool or otherwise for the state.
|
||||
// which is much larger and I'm not sure how much better it is. It's bad enough we have to do it with revolvers
|
||||
// to avoid 6-7 additional entity spawns.
|
||||
}
|
||||
|
||||
private void OnChamberMagazineCounter(EntityUid uid, ChamberMagazineAmmoProviderComponent component, AmmoCounterControlEvent args)
|
||||
{
|
||||
args.Control = new ChamberMagazineStatusControl();
|
||||
}
|
||||
|
||||
private void OnChamberMagazineAmmoUpdate(EntityUid uid, ChamberMagazineAmmoProviderComponent component, UpdateAmmoCounterEvent args)
|
||||
{
|
||||
if (args.Control is not ChamberMagazineStatusControl control) return;
|
||||
|
||||
var chambered = GetChamberEntity(uid);
|
||||
var magEntity = GetMagazineEntity(uid);
|
||||
var ammoCountEv = new GetAmmoCountEvent();
|
||||
|
||||
if (magEntity != null)
|
||||
RaiseLocalEvent(magEntity.Value, ref ammoCountEv, false);
|
||||
|
||||
control.Update(chambered != null, magEntity != null, ammoCountEv.Count, ammoCountEv.Capacity);
|
||||
}
|
||||
}
|
||||
29
Content.Client/Weapons/Ranged/Systems/GunSystem.Magazine.cs
Normal file
29
Content.Client/Weapons/Ranged/Systems/GunSystem.Magazine.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using Content.Shared.Weapons.Ranged;
|
||||
|
||||
namespace Content.Client.Weapons.Ranged.Systems;
|
||||
|
||||
public sealed partial class GunSystem
|
||||
{
|
||||
protected override void InitializeMagazine()
|
||||
{
|
||||
base.InitializeMagazine();
|
||||
SubscribeLocalEvent<MagazineAmmoProviderComponent, UpdateAmmoCounterEvent>(OnMagazineAmmoUpdate);
|
||||
}
|
||||
|
||||
private void OnMagazineAmmoUpdate(EntityUid uid, MagazineAmmoProviderComponent component, UpdateAmmoCounterEvent args)
|
||||
{
|
||||
var ent = GetMagazineEntity(uid);
|
||||
|
||||
if (ent == null)
|
||||
{
|
||||
if (args.Control is DefaultStatusControl control)
|
||||
{
|
||||
control.Update(0, 0);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
RaiseLocalEvent(ent.Value, args, false);
|
||||
}
|
||||
}
|
||||
37
Content.Client/Weapons/Ranged/Systems/GunSystem.Revolver.cs
Normal file
37
Content.Client/Weapons/Ranged/Systems/GunSystem.Revolver.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using Content.Shared.Weapons.Ranged.Components;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Weapons.Ranged.Systems;
|
||||
|
||||
public sealed partial class GunSystem
|
||||
{
|
||||
protected override void InitializeRevolver()
|
||||
{
|
||||
base.InitializeRevolver();
|
||||
SubscribeLocalEvent<RevolverAmmoProviderComponent, AmmoCounterControlEvent>(OnRevolverCounter);
|
||||
SubscribeLocalEvent<RevolverAmmoProviderComponent, UpdateAmmoCounterEvent>(OnRevolverAmmoUpdate);
|
||||
SubscribeLocalEvent<RevolverAmmoProviderComponent, EntRemovedFromContainerMessage>(OnRevolverEntRemove);
|
||||
}
|
||||
|
||||
private void OnRevolverEntRemove(EntityUid uid, RevolverAmmoProviderComponent component, EntRemovedFromContainerMessage args)
|
||||
{
|
||||
if (args.Container.ID != RevolverContainer) return;
|
||||
|
||||
// See ChamberMagazineAmmoProvider
|
||||
if (!args.Entity.IsClientSide()) return;
|
||||
|
||||
QueueDel(args.Entity);
|
||||
}
|
||||
|
||||
private void OnRevolverAmmoUpdate(EntityUid uid, RevolverAmmoProviderComponent component, UpdateAmmoCounterEvent args)
|
||||
{
|
||||
if (args.Control is not RevolverStatusControl control) return;
|
||||
control.Update(component.CurrentIndex, component.Chambers);
|
||||
}
|
||||
|
||||
private void OnRevolverCounter(EntityUid uid, RevolverAmmoProviderComponent component, AmmoCounterControlEvent args)
|
||||
{
|
||||
args.Control = new RevolverStatusControl();
|
||||
}
|
||||
}
|
||||
34
Content.Client/Weapons/Ranged/Systems/GunSystem.SpentAmmo.cs
Normal file
34
Content.Client/Weapons/Ranged/Systems/GunSystem.SpentAmmo.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using Content.Client.Weapons.Ranged.Components;
|
||||
using Content.Shared.Weapons.Ranged.Systems;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Weapons.Ranged.Systems;
|
||||
|
||||
public sealed partial class GunSystem
|
||||
{
|
||||
private void InitializeSpentAmmo()
|
||||
{
|
||||
SubscribeLocalEvent<SpentAmmoVisualsComponent, AppearanceChangeEvent>(OnSpentAmmoAppearance);
|
||||
}
|
||||
|
||||
private void OnSpentAmmoAppearance(EntityUid uid, SpentAmmoVisualsComponent component, ref AppearanceChangeEvent args)
|
||||
{
|
||||
var sprite = args.Sprite;
|
||||
if (sprite == null) return;
|
||||
|
||||
if (!args.AppearanceData.TryGetValue(AmmoVisuals.Spent, out var varSpent))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var spent = (bool) varSpent;
|
||||
string state;
|
||||
|
||||
if (spent)
|
||||
state = component.Suffix ? $"{component.State}-spent" : "spent";
|
||||
else
|
||||
state = component.State;
|
||||
|
||||
sprite.LayerSetState(AmmoVisualLayers.Base, state);
|
||||
}
|
||||
}
|
||||
187
Content.Client/Weapons/Ranged/Systems/GunSystem.cs
Normal file
187
Content.Client/Weapons/Ranged/Systems/GunSystem.cs
Normal file
@@ -0,0 +1,187 @@
|
||||
using Content.Client.Items;
|
||||
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.Animations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Utility;
|
||||
using SharedGunSystem = Content.Shared.Weapons.Ranged.Systems.SharedGunSystem;
|
||||
|
||||
namespace Content.Client.Weapons.Ranged.Systems;
|
||||
|
||||
public sealed partial class GunSystem : SharedGunSystem
|
||||
{
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly AnimationPlayerSystem _animPlayer = default!;
|
||||
[Dependency] private readonly EffectSystem _effects = default!;
|
||||
[Dependency] private readonly InputSystem _inputSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
UpdatesOutsidePrediction = true;
|
||||
SubscribeLocalEvent<AmmoCounterComponent, ItemStatusCollectMessage>(OnAmmoCounterCollect);
|
||||
|
||||
// Plays animated effects on the client.
|
||||
SubscribeNetworkEvent<HitscanEvent>(OnHitscan);
|
||||
|
||||
InitializeSpentAmmo();
|
||||
}
|
||||
|
||||
private void OnHitscan(HitscanEvent ev)
|
||||
{
|
||||
// ALL I WANT IS AN ANIMATED EFFECT
|
||||
foreach (var a in ev.Sprites)
|
||||
{
|
||||
if (a.Sprite is not SpriteSpecifier.Rsi rsi) continue;
|
||||
|
||||
var ent = Spawn("HitscanEffect", a.coordinates);
|
||||
var sprite = Comp<SpriteComponent>(ent);
|
||||
var xform = Transform(ent);
|
||||
xform.LocalRotation = a.angle;
|
||||
sprite[EffectLayers.Unshaded].AutoAnimated = false;
|
||||
sprite.LayerSetSprite(EffectLayers.Unshaded, rsi);
|
||||
sprite.LayerSetState(EffectLayers.Unshaded, rsi.RsiState);
|
||||
sprite.Scale = new Vector2(a.Distance, 1f);
|
||||
sprite[EffectLayers.Unshaded].Visible = true;
|
||||
|
||||
var anim = new Animation()
|
||||
{
|
||||
Length = TimeSpan.FromSeconds(0.48f),
|
||||
AnimationTracks =
|
||||
{
|
||||
new AnimationTrackSpriteFlick()
|
||||
{
|
||||
LayerKey = EffectLayers.Unshaded,
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackSpriteFlick.KeyFrame(rsi.RsiState, 0f),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_animPlayer.Play(ent, null, anim, "hitscan-effect");
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
if (!Timing.IsFirstTimePredicted)
|
||||
return;
|
||||
|
||||
var entityNull = _player.LocalPlayer?.ControlledEntity;
|
||||
|
||||
if (entityNull == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var entity = entityNull.Value;
|
||||
var gun = GetGun(entity);
|
||||
|
||||
if (gun == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_inputSystem.CmdStates.GetState(EngineKeyFunctions.Use) != BoundKeyState.Down)
|
||||
{
|
||||
if (gun.ShotCounter != 0)
|
||||
EntityManager.RaisePredictiveEvent(new RequestStopShootEvent { Gun = gun.Owner });
|
||||
return;
|
||||
}
|
||||
|
||||
if (gun.NextFire > Timing.CurTime)
|
||||
return;
|
||||
|
||||
var mousePos = _eyeManager.ScreenToMap(_inputManager.MouseScreenPosition);
|
||||
EntityCoordinates coordinates;
|
||||
|
||||
// Bro why would I want a ternary here
|
||||
// ReSharper disable once ConvertIfStatementToConditionalTernaryExpression
|
||||
if (MapManager.TryFindGridAt(mousePos, out var grid))
|
||||
{
|
||||
coordinates = EntityCoordinates.FromMap(grid.GridEntityId, mousePos, EntityManager);
|
||||
}
|
||||
else
|
||||
{
|
||||
coordinates = EntityCoordinates.FromMap(MapManager.GetMapEntityId(mousePos.MapId), mousePos, EntityManager);
|
||||
}
|
||||
|
||||
Sawmill.Debug($"Sending shoot request tick {Timing.CurTick} / {Timing.CurTime}");
|
||||
|
||||
EntityManager.RaisePredictiveEvent(new RequestShootEvent
|
||||
{
|
||||
Coordinates = coordinates,
|
||||
Gun = gun.Owner,
|
||||
});
|
||||
}
|
||||
|
||||
public override void Shoot(GunComponent gun, List<IShootable> ammo, EntityCoordinates fromCoordinates, EntityCoordinates toCoordinates, EntityUid? user = null)
|
||||
{
|
||||
// Rather than splitting client / server for every ammo provider it's easier
|
||||
// to just delete the spawned entities. This is for programmer sanity despite the wasted perf.
|
||||
// This also means any ammo specific stuff can be grabbed as necessary.
|
||||
foreach (var ent in ammo)
|
||||
{
|
||||
switch (ent)
|
||||
{
|
||||
case CartridgeAmmoComponent cartridge:
|
||||
if (!cartridge.Spent)
|
||||
{
|
||||
SetCartridgeSpent(cartridge, true);
|
||||
MuzzleFlash(gun.Owner, cartridge, user);
|
||||
|
||||
// TODO: Can't predict entity deletions.
|
||||
//if (cartridge.DeleteOnSpawn)
|
||||
// Del(cartridge.Owner);
|
||||
}
|
||||
else
|
||||
{
|
||||
PlaySound(gun.Owner, gun.SoundEmpty?.GetSound(Random, ProtoManager), user);
|
||||
}
|
||||
|
||||
if (cartridge.Owner.IsClientSide())
|
||||
Del(cartridge.Owner);
|
||||
|
||||
break;
|
||||
case AmmoComponent newAmmo:
|
||||
MuzzleFlash(gun.Owner, newAmmo, user);
|
||||
if (newAmmo.Owner.IsClientSide())
|
||||
Del(newAmmo.Owner);
|
||||
else
|
||||
RemComp<AmmoComponent>(newAmmo.Owner);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void PlaySound(EntityUid gun, string? sound, EntityUid? user = null)
|
||||
{
|
||||
if (sound == null || user == null || !Timing.IsFirstTimePredicted) return;
|
||||
SoundSystem.Play(Filter.Local(), sound, gun);
|
||||
}
|
||||
|
||||
protected override void Popup(string message, EntityUid? uid, EntityUid? user)
|
||||
{
|
||||
if (uid == null || user == null || !Timing.IsFirstTimePredicted) return;
|
||||
PopupSystem.PopupEntity(message, uid.Value, Filter.Entities(user.Value));
|
||||
}
|
||||
|
||||
protected override void CreateEffect(EffectSystemMessage message, EntityUid? user = null)
|
||||
{
|
||||
_effects.CreateEffect(message);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
using Content.Client.Clickable;
|
||||
using Content.Shared.Weapons.Ranged;
|
||||
using Content.Shared.Weapons.Ranged.Systems;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
@@ -7,7 +7,7 @@ using Robust.Shared.Input;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Weapons.Ranged;
|
||||
namespace Content.Client.Weapons.Ranged.Systems;
|
||||
|
||||
public sealed class TetherGunSystem : SharedTetherGunSystem
|
||||
{
|
||||
Reference in New Issue
Block a user