Re-organize all projects (#4166)

This commit is contained in:
DrSmugleaf
2021-06-09 22:19:39 +02:00
committed by GitHub
parent 9f50e4061b
commit ff1a2d97ea
1773 changed files with 5258 additions and 5508 deletions

View File

@@ -0,0 +1,52 @@
using Robust.Client.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
namespace Content.Client.Weapons.Melee.Components
{
[RegisterComponent]
public sealed class MeleeLungeComponent : Component
{
public override string Name => "MeleeLunge";
private const float ResetTime = 0.3f;
private const float BaseOffset = 0.25f;
private Angle _angle;
private float _time;
public void SetData(Angle angle)
{
_angle = angle;
_time = 0;
}
public void Update(float frameTime)
{
_time += frameTime;
var offset = Vector2.Zero;
var deleteSelf = false;
if (_time > ResetTime)
{
deleteSelf = true;
}
else
{
offset = _angle.RotateVec((0, -BaseOffset));
offset *= (ResetTime - _time) / ResetTime;
}
if (Owner.TryGetComponent(out ISpriteComponent? spriteComponent))
{
spriteComponent.Offset = offset;
}
if (deleteSelf)
{
Owner.RemoveComponent<MeleeLungeComponent>();
}
}
}
}

View File

@@ -0,0 +1,78 @@
using Content.Shared.Weapons.Melee;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
namespace Content.Client.Weapons.Melee.Components
{
[RegisterComponent]
public sealed class MeleeWeaponArcAnimationComponent : Component
{
public override string Name => "MeleeWeaponArcAnimation";
private MeleeWeaponAnimationPrototype? _meleeWeaponAnimation;
private float _timer;
private SpriteComponent? _sprite;
private Angle _baseAngle;
public override void Initialize()
{
base.Initialize();
_sprite = Owner.GetComponent<SpriteComponent>();
}
public void SetData(MeleeWeaponAnimationPrototype prototype, Angle baseAngle, IEntity attacker, bool followAttacker = true)
{
_meleeWeaponAnimation = prototype;
_sprite?.AddLayer(new RSI.StateId(prototype.State));
_baseAngle = baseAngle;
if(followAttacker)
Owner.Transform.AttachParent(attacker);
}
internal void Update(float frameTime)
{
if (_meleeWeaponAnimation == null)
{
return;
}
_timer += frameTime;
var (r, g, b, a) =
Vector4.Clamp(_meleeWeaponAnimation.Color + _meleeWeaponAnimation.ColorDelta * _timer, Vector4.Zero, Vector4.One);
if (_sprite != null)
{
_sprite.Color = new Color(r, g, b, a);
}
switch (_meleeWeaponAnimation.ArcType)
{
case WeaponArcType.Slash:
var angle = Angle.FromDegrees(_meleeWeaponAnimation.Width)/2;
Owner.Transform.WorldRotation =
_baseAngle + Angle.Lerp(-angle, angle, (float) (_timer / _meleeWeaponAnimation.Length.TotalSeconds));
break;
case WeaponArcType.Poke:
Owner.Transform.WorldRotation = _baseAngle;
if (_sprite != null)
{
_sprite.Offset -= (0, _meleeWeaponAnimation.Speed * frameTime);
}
break;
}
if (_meleeWeaponAnimation.Length.TotalSeconds <= _timer)
{
Owner.Delete();
}
}
}
}

View File

@@ -0,0 +1,20 @@
using Content.Client.Weapons.Melee.Components;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
namespace Content.Client.Weapons.Melee
{
[UsedImplicitly]
public sealed class MeleeLungeSystem : EntitySystem
{
public override void FrameUpdate(float frameTime)
{
base.FrameUpdate(frameTime);
foreach (var meleeLungeComponent in EntityManager.ComponentManager.EntityQuery<MeleeLungeComponent>(true))
{
meleeLungeComponent.Update(frameTime);
}
}
}
}

View File

@@ -0,0 +1,128 @@
using System;
using Content.Client.Weapons.Melee.Components;
using Content.Shared.Weapons.Melee;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using static Content.Shared.Weapons.Melee.MeleeWeaponSystemMessages;
namespace Content.Client.Weapons.Melee
{
[UsedImplicitly]
public sealed class MeleeWeaponSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
public override void Initialize()
{
SubscribeNetworkEvent<PlayMeleeWeaponAnimationMessage>(PlayWeaponArc);
SubscribeNetworkEvent<PlayLungeAnimationMessage>(PlayLunge);
}
public override void FrameUpdate(float frameTime)
{
base.FrameUpdate(frameTime);
foreach (var arcAnimationComponent in EntityManager.ComponentManager.EntityQuery<MeleeWeaponArcAnimationComponent>(true))
{
arcAnimationComponent.Update(frameTime);
}
}
private void PlayWeaponArc(PlayMeleeWeaponAnimationMessage msg)
{
if (!_prototypeManager.TryIndex(msg.ArcPrototype, out MeleeWeaponAnimationPrototype? weaponArc))
{
Logger.Error("Tried to play unknown weapon arc prototype '{0}'", msg.ArcPrototype);
return;
}
if (!EntityManager.TryGetEntity(msg.Attacker, out var attacker))
{
// FIXME: This should never happen.
Logger.Error($"Tried to play a weapon arc {msg.ArcPrototype}, but the attacker does not exist. attacker={msg.Attacker}, source={msg.Source}");
return;
}
if (!attacker.Deleted)
{
var lunge = attacker.EnsureComponent<MeleeLungeComponent>();
lunge.SetData(msg.Angle);
var entity = EntityManager.SpawnEntity(weaponArc.Prototype, attacker.Transform.Coordinates);
entity.Transform.LocalRotation = msg.Angle;
var weaponArcAnimation = entity.GetComponent<MeleeWeaponArcAnimationComponent>();
weaponArcAnimation.SetData(weaponArc, msg.Angle, attacker, msg.ArcFollowAttacker);
// Due to ISpriteComponent limitations, weapons that don't use an RSI won't have this effect.
if (EntityManager.TryGetEntity(msg.Source, out var source) &&
msg.TextureEffect &&
source.TryGetComponent(out ISpriteComponent? sourceSprite) &&
sourceSprite.BaseRSI?.Path != null)
{
var sys = Get<EffectSystem>();
var curTime = _gameTiming.CurTime;
var effect = new EffectSystemMessage
{
EffectSprite = sourceSprite.BaseRSI.Path.ToString(),
RsiState = sourceSprite.LayerGetState(0).Name,
Coordinates = attacker.Transform.Coordinates,
Color = Vector4.Multiply(new Vector4(255, 255, 255, 125), 1.0f),
ColorDelta = Vector4.Multiply(new Vector4(0, 0, 0, -10), 1.0f),
Velocity = msg.Angle.ToWorldVec(),
Acceleration = msg.Angle.ToWorldVec() * 5f,
Born = curTime,
DeathTime = curTime.Add(TimeSpan.FromMilliseconds(300f)),
};
sys.CreateEffect(effect);
}
}
foreach (var uid in msg.Hits)
{
if (!EntityManager.TryGetEntity(uid, out var hitEntity) || hitEntity.Deleted)
{
continue;
}
if (!hitEntity.TryGetComponent(out ISpriteComponent? sprite))
{
continue;
}
var originalColor = sprite.Color;
var newColor = Color.Red * originalColor;
sprite.Color = newColor;
hitEntity.SpawnTimer(100, () =>
{
// Only reset back to the original color if something else didn't change the color in the mean time.
if (sprite.Color == newColor)
{
sprite.Color = originalColor;
}
});
}
}
private void PlayLunge(PlayLungeAnimationMessage msg)
{
if (EntityManager.TryGetEntity(msg.Source, out var entity))
{
entity.EnsureComponent<MeleeLungeComponent>().SetData(msg.Angle);
}
else
{
// FIXME: This should never happen.
Logger.Error($"Tried to play a lunge animation, but the entity \"{msg.Source}\" does not exist.");
}
}
}
}

View File

@@ -0,0 +1,159 @@
using System;
using Content.Client.Items.Components;
using Content.Client.Stylesheets;
using Content.Shared.NetIDs;
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.Maths;
using Robust.Shared.ViewVariables;
namespace Content.Client.Weapons.Ranged.Barrels.Components
{
[RegisterComponent]
public class ClientBatteryBarrelComponent : Component, IItemStatus
{
public override string Name => "BatteryBarrel";
public override uint? NetID => ContentNetIDs.BATTERY_BARREL;
private StatusControl? _statusControl;
/// <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 BatteryBarrelComponentState cast)
return;
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 ClientBatteryBarrelComponent _parent;
private readonly HBoxContainer _bulletsList;
private readonly Label _noBatteryLabel;
private readonly Label _ammoCount;
public StatusControl(ClientBatteryBarrelComponent parent)
{
MinHeight = 15;
_parent = parent;
HorizontalExpand = true;
VerticalAlignment = VAlignment.Center;
AddChild(new HBoxContainer
{
HorizontalExpand = true,
Children =
{
new Control
{
HorizontalExpand = true,
Children =
{
(_bulletsList = new HBoxContainer
{
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()
{
_bulletsList.RemoveAllChildren();
if (_parent.MagazineCount == null)
{
_noBatteryLabel.Visible = true;
_ammoCount.Visible = false;
return;
}
var (count, capacity) = _parent.MagazineCount.Value;
_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),
});
}
}
}
}
}

View File

@@ -0,0 +1,206 @@
using System;
using Content.Client.IoC;
using Content.Client.Items.Components;
using Content.Client.Resources;
using Content.Client.Stylesheets;
using Content.Shared.NetIDs;
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.Maths;
using Robust.Shared.ViewVariables;
namespace Content.Client.Weapons.Ranged.Barrels.Components
{
[RegisterComponent]
public class ClientBoltActionBarrelComponent : Component, IItemStatus
{
public override string Name => "BoltActionBarrel";
public override uint? NetID => ContentNetIDs.BOLTACTION_BARREL;
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 HBoxContainer _bulletsListTop;
private readonly HBoxContainer _bulletsListBottom;
private readonly TextureRect _chamberedBullet;
private readonly Label _noMagazineLabel;
public StatusControl(ClientBoltActionBarrelComponent parent)
{
MinHeight = 15;
_parent = parent;
HorizontalExpand = true;
VerticalAlignment = VAlignment.Center;
AddChild(new VBoxContainer
{
HorizontalExpand = true,
VerticalAlignment = VAlignment.Center,
SeparationOverride = 0,
Children =
{
(_bulletsListTop = new HBoxContainer {SeparationOverride = 0}),
new HBoxContainer
{
HorizontalExpand = true,
Children =
{
new Control
{
HorizontalExpand = true,
Children =
{
(_bulletsListBottom = new HBoxContainer
{
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;
}
}
}
}
}

View File

@@ -0,0 +1,259 @@
using System;
using Content.Client.IoC;
using Content.Client.Items.Components;
using Content.Client.Resources;
using Content.Client.Stylesheets;
using Content.Shared.NetIDs;
using Content.Shared.Weapons.Ranged;
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.Maths;
using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Client.Weapons.Ranged.Barrels.Components
{
[RegisterComponent]
public 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),
}
}
}
};
public override string Name => "MagazineBarrel";
public override uint? NetID => ContentNetIDs.MAGAZINE_BARREL;
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 override void HandleNetworkMessage(ComponentMessage message, INetChannel channel, ICommonSession? session = null)
{
base.HandleNetworkMessage(message, channel, session);
switch (message)
{
case MagazineAutoEjectMessage _:
_statusControl?.PlayAlarmAnimation();
return;
}
}
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 HBoxContainer _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 HBoxContainer
{
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 HBoxContainer
{
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");
}
}
}
}

View File

@@ -0,0 +1,206 @@
using System;
using Content.Client.IoC;
using Content.Client.Items.Components;
using Content.Client.Resources;
using Content.Client.Stylesheets;
using Content.Shared.NetIDs;
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.Maths;
using Robust.Shared.ViewVariables;
namespace Content.Client.Weapons.Ranged.Barrels.Components
{
[RegisterComponent]
public class ClientPumpBarrelComponent : Component, IItemStatus
{
public override string Name => "PumpBarrel";
public override uint? NetID => ContentNetIDs.PUMP_BARREL;
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 HBoxContainer _bulletsListTop;
private readonly HBoxContainer _bulletsListBottom;
private readonly TextureRect _chamberedBullet;
private readonly Label _noMagazineLabel;
public StatusControl(ClientPumpBarrelComponent parent)
{
MinHeight = 15;
_parent = parent;
HorizontalExpand = true;
VerticalAlignment = VAlignment.Center;
AddChild(new VBoxContainer
{
HorizontalExpand = true,
VerticalAlignment = VAlignment.Center,
SeparationOverride = 0,
Children =
{
(_bulletsListTop = new HBoxContainer {SeparationOverride = 0}),
new HBoxContainer
{
HorizontalExpand = true,
Children =
{
new Control
{
HorizontalExpand = true,
Children =
{
(_bulletsListBottom = new HBoxContainer
{
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;
}
}
}
}
}

View File

@@ -0,0 +1,168 @@
using Content.Client.IoC;
using Content.Client.Items.Components;
using Content.Client.Resources;
using Content.Shared.NetIDs;
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.Maths;
using Robust.Shared.ViewVariables;
namespace Content.Client.Weapons.Ranged.Barrels.Components
{
[RegisterComponent]
public class ClientRevolverBarrelComponent : Component, IItemStatus
{
public override string Name => "RevolverBarrel";
public override uint? NetID => ContentNetIDs.REVOLVER_BARREL;
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 HBoxContainer _bulletsList;
public StatusControl(ClientRevolverBarrelComponent parent)
{
MinHeight = 15;
_parent = parent;
HorizontalExpand = true;
VerticalAlignment = VAlignment.Center;
AddChild((_bulletsList = new HBoxContainer
{
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.Green,
});
}
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);
}
}
}
}
}

View File

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

View File

@@ -0,0 +1,105 @@
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.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(IEntity entity)
{
base.InitializeEntity(entity);
var sprite = entity.GetComponent<ISpriteComponent>();
if (sprite.LayerMapTryGet(RangedBarrelVisualLayers.Mag, out _))
{
sprite.LayerSetState(RangedBarrelVisualLayers.Mag, $"{_magState}-{_magSteps-1}");
sprite.LayerSetVisible(RangedBarrelVisualLayers.Mag, false);
}
if (sprite.LayerMapTryGet(RangedBarrelVisualLayers.MagUnshaded, out _))
{
sprite.LayerSetState(RangedBarrelVisualLayers.MagUnshaded, $"{_magState}-unshaded-{_magSteps-1}");
sprite.LayerSetVisible(RangedBarrelVisualLayers.MagUnshaded, false);
}
}
public override void OnChangeData(AppearanceComponent component)
{
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 = component.Owner.GetComponent<ISpriteComponent>();
component.TryGetData(MagazineBarrelVisuals.MagLoaded, out _magLoaded);
if (_magLoaded)
{
if (!component.TryGetData(AmmoVisuals.AmmoMax, out int capacity))
{
return;
}
if (!component.TryGetData(AmmoVisuals.AmmoCount, out int current))
{
return;
}
var step = ContentHelpers.RoundToLevels(current, capacity, _magSteps);
if (step == 0 && !_zeroVisible)
{
if (sprite.LayerMapTryGet(RangedBarrelVisualLayers.Mag, out _))
{
sprite.LayerSetVisible(RangedBarrelVisualLayers.Mag, false);
}
if (sprite.LayerMapTryGet(RangedBarrelVisualLayers.MagUnshaded, out _))
{
sprite.LayerSetVisible(RangedBarrelVisualLayers.MagUnshaded, false);
}
return;
}
if (sprite.LayerMapTryGet(RangedBarrelVisualLayers.Mag, out _))
{
sprite.LayerSetVisible(RangedBarrelVisualLayers.Mag, true);
sprite.LayerSetState(RangedBarrelVisualLayers.Mag, $"{_magState}-{step}");
}
if (sprite.LayerMapTryGet(RangedBarrelVisualLayers.MagUnshaded, out _))
{
sprite.LayerSetVisible(RangedBarrelVisualLayers.MagUnshaded, true);
sprite.LayerSetState(RangedBarrelVisualLayers.MagUnshaded, $"{_magState}-unshaded-{step}");
}
}
else
{
if (sprite.LayerMapTryGet(RangedBarrelVisualLayers.Mag, out _))
{
sprite.LayerSetVisible(RangedBarrelVisualLayers.Mag, false);
}
if (sprite.LayerMapTryGet(RangedBarrelVisualLayers.MagUnshaded, out _))
{
sprite.LayerSetVisible(RangedBarrelVisualLayers.MagUnshaded, false);
}
}
}
}
}

View File

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

View File

@@ -0,0 +1,39 @@
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.Safety;
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
{
base.HandleComponentState(curState, nextState);
if (curState is not RangedWeaponComponentState rangedState)
{
return;
}
FireRateSelector = rangedState.FireRateSelector;
}
public void SyncFirePos(GridId targetGrid, Vector2 targetPosition)
{
SendNetworkMessage(new FirePosComponentMessage(targetGrid, targetPosition));
}
}
}

View File

@@ -0,0 +1,107 @@
using System;
using Content.Client.CombatMode;
using Content.Client.Hands;
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 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!;
private InputSystem _inputSystem = default!;
private CombatModeSystem _combatModeSystem = default!;
private bool _blocked;
private int _shotCounter;
public override void Initialize()
{
base.Initialize();
IoCManager.InjectDependencies(this);
_inputSystem = Get<InputSystem>();
_combatModeSystem = Get<CombatModeSystem>();
}
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 (entity == null || !entity.TryGetComponent(out HandsComponent? hands))
{
return;
}
var held = hands.ActiveHand;
if (held == null || !held.TryGetComponent(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 worldPos = _eyeManager.ScreenToMap(_inputManager.MouseScreenPosition);
if (!_mapManager.TryFindGridAt(worldPos, out var grid))
{
weapon.SyncFirePos(GridId.Invalid, worldPos.Position);
}
else
{
weapon.SyncFirePos(grid.Index, grid.MapToGrid(worldPos).Position);
}
}
}
}