Item status!

This commit is contained in:
Pieter-Jan Briers
2020-01-09 00:27:52 +01:00
parent e984fc24b6
commit 411c23c46e
36 changed files with 1248 additions and 128 deletions

View File

@@ -33,4 +33,7 @@
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\Content.Shared\Content.Shared.csproj" /> <ProjectReference Include="..\Content.Shared\Content.Shared.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Animations" />
</ItemGroup>
</Project> </Project>

View File

@@ -74,7 +74,6 @@ namespace Content.Client
"Wirecutter", "Wirecutter",
"Screwdriver", "Screwdriver",
"Multitool", "Multitool",
"Welder",
"Wrench", "Wrench",
"Crowbar", "Crowbar",
"HitscanWeapon", "HitscanWeapon",
@@ -82,7 +81,6 @@ namespace Content.Client
"Projectile", "Projectile",
"MeleeWeapon", "MeleeWeapon",
"Storeable", "Storeable",
"Stack",
"Dice", "Dice",
"Construction", "Construction",
"Apc", "Apc",
@@ -90,12 +88,10 @@ namespace Content.Client
"PoweredLight", "PoweredLight",
"Smes", "Smes",
"Powercell", "Powercell",
"HandheldLight",
"LightBulb", "LightBulb",
"Healing", "Healing",
"Catwalk", "Catwalk",
"BallisticMagazine", "BallisticMagazine",
"BallisticMagazineWeapon",
"BallisticBullet", "BallisticBullet",
"HitscanWeaponCapacitor", "HitscanWeaponCapacitor",
"PowerCell", "PowerCell",

View File

@@ -0,0 +1,106 @@
using System;
using Content.Shared.GameObjects.Components;
using Robust.Client.Graphics.Drawing;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
using Robust.Shared.ViewVariables;
namespace Content.Client.GameObjects.Components
{
[RegisterComponent]
public sealed class HandheldLightComponent : SharedHandheldLightComponent, IItemStatus
{
[ViewVariables] public float? Charge { get; private set; }
public Control MakeControl()
{
return new StatusControl(this);
}
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
{
var cast = (HandheldLightComponentState) curState;
Charge = cast.Charge;
}
private sealed class StatusControl : Control
{
private const float TimerCycle = 1;
private readonly HandheldLightComponent _parent;
private readonly PanelContainer[] _sections = new PanelContainer[5];
private float _timer;
private static readonly StyleBoxFlat _styleBoxLit = new StyleBoxFlat
{
BackgroundColor = Color.Green
};
private static readonly StyleBoxFlat _styleBoxUnlit = new StyleBoxFlat
{
BackgroundColor = Color.Black
};
public StatusControl(HandheldLightComponent parent)
{
_parent = parent;
var wrapper = new HBoxContainer
{
SeparationOverride = 4,
SizeFlagsHorizontal = SizeFlags.ShrinkCenter
};
AddChild(wrapper);
for (var i = 0; i < _sections.Length; i++)
{
var panel = new PanelContainer {CustomMinimumSize = (20, 20)};
wrapper.AddChild(panel);
_sections[i] = panel;
}
}
protected override void Update(FrameEventArgs args)
{
base.Update(args);
_timer += args.DeltaSeconds;
_timer %= TimerCycle;
var charge = _parent.Charge ?? 0;
int level;
if (FloatMath.CloseTo(charge, 0))
{
level = 0;
}
else
{
level = 1 + (int) MathF.Round(charge * 6);
}
if (level == 1)
{
// Flash the last light.
_sections[0].PanelOverride = _timer > TimerCycle / 2 ? _styleBoxLit : _styleBoxUnlit;
}
else
{
_sections[0].PanelOverride = level > 2 ? _styleBoxLit : _styleBoxUnlit;
}
_sections[1].PanelOverride = level > 3 ? _styleBoxLit : _styleBoxUnlit;
_sections[2].PanelOverride = level > 4 ? _styleBoxLit : _styleBoxUnlit;
_sections[3].PanelOverride = level > 5 ? _styleBoxLit : _styleBoxUnlit;
_sections[4].PanelOverride = level > 6 ? _styleBoxLit : _styleBoxUnlit;
}
}
}
}

View File

@@ -0,0 +1,33 @@
using Robust.Client.UserInterface;
namespace Content.Client.GameObjects.Components
{
/// <summary>
/// Allows a component to provide status tooltips next to the hands interface.
/// </summary>
public interface IItemStatus
{
/// <summary>
/// Called to get a control that represents the status for this component.
/// </summary>
/// <returns>
/// The control to render as status.
/// </returns>
public Control MakeControl();
/// <summary>
/// Called when the item no longer needs this status (say, dropped from hand)
/// </summary>
/// <remarks>
/// <para>
/// Useful to allow you to drop the control for the GC, if you need to.
/// </para>
/// <para>
/// Note that this may be called after a second invocation of <see cref="MakeControl"/> (for example if the user switches the item between two hands).
/// </para>
/// </remarks>
public void DestroyControl(Control control)
{
}
}
}

View File

@@ -0,0 +1,12 @@
using Robust.Shared.GameObjects;
namespace Content.Client.GameObjects
{
[RegisterComponent]
public class ItemStatusComponent : Component
{
public override string Name => "ItemStatus";
}
}

View File

@@ -0,0 +1,60 @@
using Content.Client.UserInterface;
using Content.Client.Utility;
using Content.Shared.GameObjects.Components;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.GameObjects;
using Robust.Shared.Localization;
using Robust.Shared.Timing;
using Robust.Shared.ViewVariables;
namespace Content.Client.GameObjects.Components
{
[RegisterComponent]
public class StackComponent : SharedStackComponent, IItemStatus
{
[ViewVariables] public int Count { get; private set; }
[ViewVariables] public int MaxCount { get; private set; }
[ViewVariables(VVAccess.ReadWrite)] private bool _uiUpdateNeeded;
public Control MakeControl() => new StatusControl(this);
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
{
var cast = (StackComponentState) curState;
Count = cast.Count;
MaxCount = cast.MaxCount;
}
private sealed class StatusControl : Control
{
private readonly StackComponent _parent;
private readonly RichTextLabel _label;
public StatusControl(StackComponent parent)
{
_parent = parent;
_label = new RichTextLabel {StyleClasses = {NanoStyle.StyleClassItemStatus}};
AddChild(_label);
parent._uiUpdateNeeded = true;
}
protected override void Update(FrameEventArgs args)
{
base.Update(args);
if (!_parent._uiUpdateNeeded)
{
return;
}
_parent._uiUpdateNeeded = false;
_label.SetMarkup(Loc.GetString("Count: [color=white]{0}[/color]", _parent.Count));
}
}
}
}

View File

@@ -0,0 +1,284 @@
using System;
using Content.Client.Animations;
using Content.Client.UserInterface;
using Content.Client.Utility;
using Content.Shared.GameObjects;
using Content.Shared.GameObjects.Components.Weapons.Ranged;
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.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Network;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
using static Content.Client.StaticIoC;
namespace Content.Client.GameObjects.Components.Weapons.Ranged
{
[RegisterComponent]
public class BallisticMagazineWeaponComponent : Component, IItemStatus
{
private static readonly Animation AlarmAnimationSmg = new Animation
{
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 Animation
{
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 => "BallisticMagazineWeapon";
public override uint? NetID => ContentNetIDs.BALLISTIC_MAGAZINE_WEAPON;
public override Type StateType => typeof(BallisticMagazineWeaponComponentState);
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)] private bool _isLmgAlarmAnimation;
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _isLmgAlarmAnimation, "lmg_alarm_animation", false);
}
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
{
var cast = (BallisticMagazineWeaponComponentState) curState;
Chambered = cast.Chambered;
MagazineCount = cast.MagazineCount;
_statusControl?.Update();
}
public override void HandleMessage(ComponentMessage message, INetChannel netChannel = null,
IComponent component = null)
{
switch (message)
{
case BmwComponentAutoEjectedMessage _:
_statusControl?.PlayAlarmAnimation();
return;
}
base.HandleMessage(message, netChannel, component);
}
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 BallisticMagazineWeaponComponent _parent;
private readonly HBoxContainer _bulletsListTop;
private readonly HBoxContainer _bulletsListBottom;
private readonly TextureRect _chamberedBullet;
private readonly Label _noMagazineLabel;
public StatusControl(BallisticMagazineWeaponComponent parent)
{
_parent = parent;
SizeFlagsHorizontal = SizeFlags.FillExpand;
SizeFlagsVertical = SizeFlags.ShrinkCenter;
AddChild(new VBoxContainer
{
SizeFlagsHorizontal = SizeFlags.FillExpand,
SizeFlagsVertical = SizeFlags.ShrinkCenter,
SeparationOverride = 0,
Children =
{
(_bulletsListTop = new HBoxContainer {SeparationOverride = 0}),
new HBoxContainer
{
SizeFlagsHorizontal = SizeFlags.FillExpand,
Children =
{
new Control
{
SizeFlagsHorizontal = SizeFlags.FillExpand,
Children =
{
(_bulletsListBottom = new HBoxContainer
{
SizeFlagsVertical = SizeFlags.ShrinkCenter,
SeparationOverride = 0
}),
(_noMagazineLabel = new Label
{
Text = "No Magazine!",
StyleClasses = {NanoStyle.StyleClassItemStatus}
})
}
},
(_chamberedBullet = new TextureRect
{
Texture = ResC.GetTexture("/Textures/UserInterface/status/bullets/chambered.png"),
SizeFlagsVertical = SizeFlags.ShrinkCenter,
SizeFlagsHorizontal = SizeFlags.ShrinkEnd | SizeFlags.Fill,
})
}
}
}
});
}
public void Update()
{
_chamberedBullet.ModulateSelfOverride =
_parent.Chambered ? 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/UserInterface/status/bullets/normal.png";
}
else if (capacity <= 30)
{
texturePath = "/Textures/UserInterface/status/bullets/small.png";
}
else
{
texturePath = "/Textures/UserInterface/status/bullets/tiny.png";
}
var texture = 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;
}
}
protected override Vector2 CalculateMinimumSize()
{
return Vector2.ComponentMax((0, 15), base.CalculateMinimumSize());
}
public void PlayAlarmAnimation()
{
var animation = _parent._isLmgAlarmAnimation ? AlarmAnimationLmg : AlarmAnimationSmg;
_noMagazineLabel.PlayAnimation(animation, "alarm");
}
}
}
}

View File

@@ -0,0 +1,74 @@
using System;
using Content.Client.UserInterface;
using Content.Client.Utility;
using Content.Shared.GameObjects;
using Content.Shared.GameObjects.Components;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.GameObjects;
using Robust.Shared.Localization;
using Robust.Shared.Timing;
using Robust.Shared.ViewVariables;
namespace Content.Client.GameObjects.Components
{
[RegisterComponent]
public class WelderComponent : Component, IItemStatus
{
public override string Name => "Welder";
public override uint? NetID => ContentNetIDs.WELDER;
public override Type StateType => typeof(WelderComponentState);
[ViewVariables] public float FuelCapacity { get; private set; }
[ViewVariables] public float Fuel { get; private set; }
[ViewVariables] public bool Activated { get; private set; }
[ViewVariables(VVAccess.ReadWrite)] private bool _uiUpdateNeeded;
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
{
var cast = (WelderComponentState) curState;
FuelCapacity = cast.FuelCapacity;
Fuel = cast.Fuel;
Activated = cast.Activated;
_uiUpdateNeeded = true;
}
public Control MakeControl() => new StatusControl(this);
private sealed class StatusControl : Control
{
private readonly WelderComponent _parent;
private readonly RichTextLabel _label;
public StatusControl(WelderComponent parent)
{
_parent = parent;
_label = new RichTextLabel {StyleClasses = {NanoStyle.StyleClassItemStatus}};
AddChild(_label);
parent._uiUpdateNeeded = true;
}
protected override void Update(FrameEventArgs args)
{
base.Update(args);
if (!_parent._uiUpdateNeeded)
{
return;
}
_parent._uiUpdateNeeded = false;
var fuelCap = _parent.FuelCapacity;
var fuel = _parent.Fuel;
_label.SetMarkup(Loc.GetString("Fuel: [color={0}]{1}/{2}[/color]",
fuel < fuelCap / 4f ? "darkorange" : "orange", Math.Round(fuel), fuelCap));
}
}
}
}

View File

@@ -0,0 +1,10 @@
using Robust.Client.Interfaces.ResourceManagement;
using Robust.Shared.IoC;
namespace Content.Client
{
public static class StaticIoC
{
public static IResourceCache ResC => IoCManager.Resolve<IResourceCache>();
}
}

View File

@@ -214,11 +214,6 @@ namespace Content.Client.UserInterface
SizeFlagsVertical = Control.SizeFlags.ShrinkEnd SizeFlagsVertical = Control.SizeFlags.ShrinkEnd
}; };
HandsContainer = new MarginContainer
{
SizeFlagsVertical = Control.SizeFlags.ShrinkEnd
};
_combatPanelContainer = new VBoxContainer _combatPanelContainer = new VBoxContainer
{ {
Children = Children =
@@ -235,9 +230,20 @@ namespace Content.Client.UserInterface
_combatModeButton.OnToggled += args => OnCombatModeChanged?.Invoke(args.Pressed); _combatModeButton.OnToggled += args => OnCombatModeChanged?.Invoke(args.Pressed);
_targetingDoll.OnZoneChanged += args => OnTargetingZoneChanged?.Invoke(args); _targetingDoll.OnZoneChanged += args => OnTargetingZoneChanged?.Invoke(args);
inventoryContainer.Children.Add(HandsContainer);
inventoryContainer.Children.Add(InventoryQuickButtonContainer); inventoryContainer.Children.Add(InventoryQuickButtonContainer);
inventoryContainer.Children.Add(_combatPanelContainer); inventoryContainer.Children.Add(_combatPanelContainer);
HandsContainer = new MarginContainer
{
SizeFlagsVertical = Control.SizeFlags.ShrinkEnd
};
RootControl.AddChild(HandsContainer);
LayoutContainer.SetAnchorAndMarginPreset(HandsContainer, LayoutContainer.LayoutPreset.CenterBottom);
LayoutContainer.SetGrowHorizontal(HandsContainer, LayoutContainer.GrowDirection.Both);
LayoutContainer.SetGrowVertical(HandsContainer, LayoutContainer.GrowDirection.Begin);
} }
private void ButtonTutorialOnOnToggled() private void ButtonTutorialOnOnToggled()

View File

@@ -23,9 +23,10 @@ namespace Content.Client.UserInterface
{ {
public class HandsGui : Control public class HandsGui : Control
{ {
private const string HandNameLeft = "left";
private const string HandNameRight = "right";
private const int CooldownLevels = 8; private const int CooldownLevels = 8;
private const int BoxSpacing = 0;
private const int BoxSize = 64;
#pragma warning disable 0649 #pragma warning disable 0649
[Dependency] private readonly IPlayerManager _playerManager; [Dependency] private readonly IPlayerManager _playerManager;
@@ -40,8 +41,6 @@ namespace Content.Client.UserInterface
private IEntity LeftHand; private IEntity LeftHand;
private IEntity RightHand; private IEntity RightHand;
private UIBox2i _handL;
private UIBox2i _handR;
private readonly SpriteView LeftSpriteView; private readonly SpriteView LeftSpriteView;
private readonly SpriteView RightSpriteView; private readonly SpriteView RightSpriteView;
@@ -53,13 +52,13 @@ namespace Content.Client.UserInterface
private readonly Control _leftContainer; private readonly Control _leftContainer;
private readonly Control _rightContainer; private readonly Control _rightContainer;
private readonly ItemStatusPanel _rightStatusPanel;
private readonly ItemStatusPanel _leftStatusPanel;
public HandsGui() public HandsGui()
{ {
IoCManager.InjectDependencies(this); IoCManager.InjectDependencies(this);
_handR = new UIBox2i(0, 0, BoxSize, BoxSize);
_handL = _handR.Translated((BoxSize + BoxSpacing, 0));
MouseFilter = MouseFilterMode.Stop; MouseFilter = MouseFilterMode.Stop;
TextureHandLeft = _resourceCache.GetTexture("/Textures/UserInterface/Inventory/hand_l.png"); TextureHandLeft = _resourceCache.GetTexture("/Textures/UserInterface/Inventory/hand_l.png");
@@ -73,30 +72,37 @@ namespace Content.Client.UserInterface
_resourceCache.GetTexture($"/Textures/UserInterface/Inventory/cooldown-{i}.png"); _resourceCache.GetTexture($"/Textures/UserInterface/Inventory/cooldown-{i}.png");
} }
_rightStatusPanel = new ItemStatusPanel(true);
_leftStatusPanel = new ItemStatusPanel(false);
_leftContainer = new Control {MouseFilter = MouseFilterMode.Ignore}; _leftContainer = new Control {MouseFilter = MouseFilterMode.Ignore};
_rightContainer = new Control {MouseFilter = MouseFilterMode.Ignore}; _rightContainer = new Control {MouseFilter = MouseFilterMode.Ignore};
var hBox = new HBoxContainer var hBox = new HBoxContainer
{ {
SeparationOverride = 0, SeparationOverride = 0,
Children = {_rightContainer, _leftContainer}, Children = {_rightStatusPanel, _rightContainer, _leftContainer, _leftStatusPanel},
MouseFilter = MouseFilterMode.Ignore MouseFilter = MouseFilterMode.Ignore
}; };
AddChild(hBox); AddChild(hBox);
_leftContainer.AddChild(new TextureRect var textureLeft = new TextureRect
{ {
MouseFilter = MouseFilterMode.Ignore,
Texture = TextureHandLeft, Texture = TextureHandLeft,
TextureScale = (2, 2) TextureScale = (2, 2)
}); };
textureLeft.OnKeyBindDown += args => HandKeyBindDown(args, HandNameLeft);
_rightContainer.AddChild(new TextureRect _leftContainer.AddChild(textureLeft);
var textureRight = new TextureRect
{ {
MouseFilter = MouseFilterMode.Ignore,
Texture = TextureHandRight, Texture = TextureHandRight,
TextureScale = (2, 2) TextureScale = (2, 2)
}); };
textureRight.OnKeyBindDown += args => HandKeyBindDown(args, HandNameRight);
_rightContainer.AddChild(textureRight);
_leftContainer.AddChild(ActiveHandRect = new TextureRect _leftContainer.AddChild(ActiveHandRect = new TextureRect
{ {
@@ -127,6 +133,7 @@ namespace Content.Client.UserInterface
SizeFlagsHorizontal = SizeFlags.ShrinkCenter, SizeFlagsHorizontal = SizeFlags.ShrinkCenter,
SizeFlagsVertical = SizeFlags.ShrinkCenter, SizeFlagsVertical = SizeFlags.ShrinkCenter,
MouseFilter = MouseFilterMode.Ignore, MouseFilter = MouseFilterMode.Ignore,
Stretch = TextureRect.StretchMode.KeepCentered,
TextureScale = (2, 2), TextureScale = (2, 2),
Visible = false, Visible = false,
}); });
@@ -136,6 +143,7 @@ namespace Content.Client.UserInterface
SizeFlagsHorizontal = SizeFlags.ShrinkCenter, SizeFlagsHorizontal = SizeFlags.ShrinkCenter,
SizeFlagsVertical = SizeFlags.ShrinkCenter, SizeFlagsVertical = SizeFlags.ShrinkCenter,
MouseFilter = MouseFilterMode.Ignore, MouseFilter = MouseFilterMode.Ignore,
Stretch = TextureRect.StretchMode.KeepCentered,
TextureScale = (2, 2), TextureScale = (2, 2),
Visible = false Visible = false
}); });
@@ -166,12 +174,13 @@ namespace Content.Client.UserInterface
if (!TryGetHands(out var hands)) if (!TryGetHands(out var hands))
return; return;
var left = hands.GetEntity("left"); var left = hands.GetEntity(HandNameLeft);
var right = hands.GetEntity("right"); var right = hands.GetEntity(HandNameRight);
ActiveHandRect.Parent.RemoveChild(ActiveHandRect); ActiveHandRect.Parent.RemoveChild(ActiveHandRect);
var parent = hands.ActiveIndex == "left" ? _leftContainer : _rightContainer; var parent = hands.ActiveIndex == HandNameLeft ? _leftContainer : _rightContainer;
parent.AddChild(ActiveHandRect); parent.AddChild(ActiveHandRect);
ActiveHandRect.SetPositionInParent(1);
if (left != null) if (left != null)
{ {
@@ -230,43 +239,13 @@ namespace Content.Client.UserInterface
hands.AttackByInHand(hand); hands.AttackByInHand(hand);
} }
protected override bool HasPoint(Vector2 point) private void HandKeyBindDown(GUIBoundKeyEventArgs args, string handIndex)
{ {
return _handL.Contains((Vector2i) point) || _handR.Contains((Vector2i) point);
}
protected override void KeyBindDown(GUIBoundKeyEventArgs args)
{
base.KeyBindDown(args);
if (!args.CanFocus)
{
return;
}
var leftHandContains = _handL.Contains((Vector2i) args.RelativePosition);
var rightHandContains = _handR.Contains((Vector2i) args.RelativePosition);
string handIndex;
if (leftHandContains)
{
handIndex = "left";
}
else if (rightHandContains)
{
handIndex = "right";
}
else
{
return;
}
if (args.Function == EngineKeyFunctions.Use) if (args.Function == EngineKeyFunctions.Use)
{ {
if (!TryGetHands(out var hands)) if (!TryGetHands(out var hands))
return; return;
if (hands.ActiveIndex == handIndex) if (hands.ActiveIndex == handIndex)
{ {
UseActiveHand(); UseActiveHand();
@@ -276,21 +255,18 @@ namespace Content.Client.UserInterface
AttackByInHand(handIndex); AttackByInHand(handIndex);
} }
} }
else if (args.Function == ContentKeyFunctions.ExamineEntity) else if (args.Function == ContentKeyFunctions.ExamineEntity)
{ {
var examine = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ExamineSystem>(); var examine = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ExamineSystem>();
if (leftHandContains) if (handIndex == HandNameLeft)
examine.DoExamine(LeftHand); examine.DoExamine(LeftHand);
else if (rightHandContains) else if (handIndex == HandNameRight)
examine.DoExamine(RightHand); examine.DoExamine(RightHand);
} }
else if (args.Function == ContentKeyFunctions.MouseMiddle) else if (args.Function == ContentKeyFunctions.MouseMiddle)
{ {
SendSwitchHandTo(handIndex); SendSwitchHandTo(handIndex);
} }
else if (args.Function == ContentKeyFunctions.OpenContextMenu) else if (args.Function == ContentKeyFunctions.OpenContextMenu)
{ {
if (!TryGetHands(out var hands)) if (!TryGetHands(out var hands))
@@ -316,6 +292,9 @@ namespace Content.Client.UserInterface
UpdateCooldown(CooldownCircleLeft, LeftHand); UpdateCooldown(CooldownCircleLeft, LeftHand);
UpdateCooldown(CooldownCircleRight, RightHand); UpdateCooldown(CooldownCircleRight, RightHand);
_rightStatusPanel.Update(RightHand);
_leftStatusPanel.Update(LeftHand);
} }
private void UpdateCooldown(TextureRect cooldownTexture, IEntity entity) private void UpdateCooldown(TextureRect cooldownTexture, IEntity entity)

View File

@@ -0,0 +1,121 @@
using System.Collections.Generic;
using Content.Client.GameObjects.Components;
using Content.Client.Utility;
using Robust.Client.Graphics.Drawing;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using static Content.Client.StaticIoC;
namespace Content.Client.UserInterface
{
public class ItemStatusPanel : Control
{
[ViewVariables]
private readonly List<(IItemStatus, Control)> _activeStatusComponents = new List<(IItemStatus, Control)>();
[ViewVariables]
private readonly Label _itemNameLabel;
[ViewVariables]
private readonly VBoxContainer _statusContents;
[ViewVariables]
private readonly PanelContainer _panel;
[ViewVariables]
private IEntity _entity;
public ItemStatusPanel(bool isRightHand)
{
// isRightHand means on the LEFT of the screen.
// Keep that in mind.
var panel = new StyleBoxTexture
{
Texture = ResC.GetTexture(isRightHand
? "/Nano/item_status_right.svg.96dpi.png"
: "/Nano/item_status_left.svg.96dpi.png")
};
panel.SetContentMarginOverride(StyleBox.Margin.Vertical, 4);
panel.SetContentMarginOverride(StyleBox.Margin.Horizontal, 6);
panel.SetPatchMargin((isRightHand ? StyleBox.Margin.Left : StyleBox.Margin.Right) | StyleBox.Margin.Top,
13);
AddChild(_panel = new PanelContainer
{
PanelOverride = panel,
ModulateSelfOverride = Color.White.WithAlpha(0.9f),
Children =
{
new VBoxContainer
{
SeparationOverride = 0,
Children =
{
(_statusContents = new VBoxContainer()),
(_itemNameLabel = new Label
{
ClipText = true,
StyleClasses = {NanoStyle.StyleClassItemStatus}
})
}
}
}
});
SizeFlagsVertical = SizeFlags.ShrinkEnd;
}
public void Update(IEntity entity)
{
if (entity == null)
{
ClearOldStatus();
_entity = null;
_panel.Visible = false;
return;
}
if (entity != _entity)
{
_entity = entity;
BuildNewEntityStatus();
}
_panel.Visible = true;
_itemNameLabel.Text = entity.Name;
}
private void ClearOldStatus()
{
_statusContents.RemoveAllChildren();
foreach (var (itemStatus, control) in _activeStatusComponents)
{
itemStatus.DestroyControl(control);
}
_activeStatusComponents.Clear();
}
private void BuildNewEntityStatus()
{
DebugTools.AssertNotNull(_entity);
ClearOldStatus();
foreach (var statusComponent in _entity.GetAllComponents<IItemStatus>())
{
var control = statusComponent.MakeControl();
_statusContents.AddChild(control);
_activeStatusComponents.Add((statusComponent, control));
}
}
protected override Vector2 CalculateMinimumSize()
{
return Vector2.ComponentMax(base.CalculateMinimumSize(), (150, 00));
}
}
}

View File

@@ -30,11 +30,14 @@ namespace Content.Client.UserInterface
public const string StyleClassPowerStateLow = "PowerStateLow"; public const string StyleClassPowerStateLow = "PowerStateLow";
public const string StyleClassPowerStateGood = "PowerStateGood"; public const string StyleClassPowerStateGood = "PowerStateGood";
public const string StyleClassItemStatus = "ItemStatus";
public Stylesheet Stylesheet { get; } public Stylesheet Stylesheet { get; }
public NanoStyle() public NanoStyle()
{ {
var resCache = IoCManager.Resolve<IResourceCache>(); var resCache = IoCManager.Resolve<IResourceCache>();
var notoSans8 = resCache.GetFont("/Nano/NotoSans/NotoSans-Regular.ttf", 8);
var notoSans10 = resCache.GetFont("/Nano/NotoSans/NotoSans-Regular.ttf", 10); var notoSans10 = resCache.GetFont("/Nano/NotoSans/NotoSans-Regular.ttf", 10);
var notoSans12 = resCache.GetFont("/Nano/NotoSans/NotoSans-Regular.ttf", 12); var notoSans12 = resCache.GetFont("/Nano/NotoSans/NotoSans-Regular.ttf", 12);
var notoSansBold12 = resCache.GetFont("/Nano/NotoSans/NotoSans-Bold.ttf", 12); var notoSansBold12 = resCache.GetFont("/Nano/NotoSans/NotoSans-Bold.ttf", 12);
@@ -108,7 +111,8 @@ namespace Content.Client.UserInterface
var vScrollBarGrabberNormal = new StyleBoxFlat var vScrollBarGrabberNormal = new StyleBoxFlat
{ {
BackgroundColor = Color.Gray.WithAlpha(0.35f), ContentMarginLeftOverride = 10, ContentMarginTopOverride = 10 BackgroundColor = Color.Gray.WithAlpha(0.35f), ContentMarginLeftOverride = 10,
ContentMarginTopOverride = 10
}; };
var vScrollBarGrabberHover = new StyleBoxFlat var vScrollBarGrabberHover = new StyleBoxFlat
{ {
@@ -482,7 +486,8 @@ namespace Content.Client.UserInterface
}), }),
// Bigger Label // Bigger Label
new StyleRule(new SelectorElement(typeof(Label), new[] {StyleClassLabelHeadingBigger}, null, null), new[] new StyleRule(new SelectorElement(typeof(Label), new[] {StyleClassLabelHeadingBigger}, null, null),
new[]
{ {
new StyleProperty(Label.StylePropertyFont, notoSansBold20), new StyleProperty(Label.StylePropertyFont, notoSansBold20),
new StyleProperty(Label.StylePropertyFontColor, NanoGold), new StyleProperty(Label.StylePropertyFontColor, NanoGold),
@@ -502,7 +507,8 @@ namespace Content.Client.UserInterface
new StyleProperty(Label.StylePropertyFontColor, NanoGold) new StyleProperty(Label.StylePropertyFontColor, NanoGold)
}), }),
new StyleRule(new SelectorElement(typeof(Label), new[] {StyleClassLabelSecondaryColor}, null, null), new[] new StyleRule(new SelectorElement(typeof(Label), new[] {StyleClassLabelSecondaryColor}, null, null),
new[]
{ {
new StyleProperty(Label.StylePropertyFont, notoSans12), new StyleProperty(Label.StylePropertyFont, notoSans12),
new StyleProperty(Label.StylePropertyFontColor, Color.DarkGray), new StyleProperty(Label.StylePropertyFontColor, Color.DarkGray),
@@ -608,6 +614,12 @@ namespace Content.Client.UserInterface
{ {
new StyleProperty("font", notoSans16), new StyleProperty("font", notoSans16),
}), }),
// StyleClassItemStatus
new StyleRule(SelectorElement.Class(StyleClassItemStatus), new[]
{
new StyleProperty("font", notoSans10),
}),
}); });
} }
} }

View File

@@ -0,0 +1,13 @@
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Utility;
namespace Content.Client.Utility
{
public static class RichTextLabelExt
{
public static void SetMarkup(this RichTextLabel label, string markup)
{
label.SetMessage(FormattedMessage.FromMarkup(markup));
}
}
}

View File

@@ -3,6 +3,7 @@ using Content.Server.GameObjects.Components.Sound;
using Content.Server.GameObjects.EntitySystems; using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces.GameObjects; using Content.Server.Interfaces.GameObjects;
using Content.Shared.GameObjects; using Content.Shared.GameObjects;
using Content.Shared.GameObjects.Components;
using Content.Shared.Interfaces; using Content.Shared.Interfaces;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Server.GameObjects.Components.Container; using Robust.Server.GameObjects.Components.Container;
@@ -20,7 +21,7 @@ namespace Content.Server.GameObjects.Components.Interactable
/// Component that represents a handheld lightsource which can be toggled on and off. /// Component that represents a handheld lightsource which can be toggled on and off.
/// </summary> /// </summary>
[RegisterComponent] [RegisterComponent]
internal class HandheldLightComponent : Component, IUse, IExamine, IAttackBy, IMapInit internal sealed class HandheldLightComponent : SharedHandheldLightComponent, IUse, IExamine, IAttackBy, IMapInit
{ {
#pragma warning disable 649 #pragma warning disable 649
[Dependency] private readonly ISharedNotifyManager _notifyManager; [Dependency] private readonly ISharedNotifyManager _notifyManager;
@@ -45,9 +46,6 @@ namespace Content.Server.GameObjects.Components.Interactable
} }
} }
public override string Name => "HandheldLight";
/// <summary> /// <summary>
/// Status of light, whether or not it is emitting light. /// Status of light, whether or not it is emitting light.
/// </summary> /// </summary>
@@ -73,7 +71,6 @@ namespace Content.Server.GameObjects.Components.Interactable
} }
return true; return true;
} }
void IExamine.Examine(FormattedMessage message) void IExamine.Examine(FormattedMessage message)
@@ -153,6 +150,7 @@ namespace Content.Server.GameObjects.Components.Interactable
{ {
soundComponent.Play("/Audio/machines/button.ogg"); soundComponent.Play("/Audio/machines/button.ogg");
} }
_notifyManager.PopupMessage(Owner, user, _localizationManager.GetString("Cell missing...")); _notifyManager.PopupMessage(Owner, user, _localizationManager.GetString("Cell missing..."));
return; return;
} }
@@ -166,6 +164,7 @@ namespace Content.Server.GameObjects.Components.Interactable
{ {
soundComponent.Play("/Audio/machines/button.ogg"); soundComponent.Play("/Audio/machines/button.ogg");
} }
_notifyManager.PopupMessage(Owner, user, _localizationManager.GetString("Dead cell...")); _notifyManager.PopupMessage(Owner, user, _localizationManager.GetString("Dead cell..."));
return; return;
} }
@@ -195,6 +194,8 @@ namespace Content.Server.GameObjects.Components.Interactable
var cell = Cell; var cell = Cell;
if (cell == null || !cell.TryDeductWattage(Wattage, frameTime)) TurnOff(); if (cell == null || !cell.TryDeductWattage(Wattage, frameTime)) TurnOff();
Dirty();
} }
private void EjectCell(IEntity user) private void EjectCell(IEntity user)
@@ -227,6 +228,23 @@ namespace Content.Server.GameObjects.Components.Interactable
} }
} }
public override ComponentState GetComponentState()
{
if (Cell == null)
{
return new HandheldLightComponentState(null);
}
if (Cell.AvailableCharge(1) < Wattage)
{
// Practically zero.
// This is so the item status works correctly.
return new HandheldLightComponentState(0);
}
return new HandheldLightComponentState(Cell.Charge / Cell.Capacity);
}
[Verb] [Verb]
public sealed class EjectCellVerb : Verb<HandheldLightComponent> public sealed class EjectCellVerb : Verb<HandheldLightComponent>
{ {
@@ -252,6 +270,7 @@ namespace Content.Server.GameObjects.Components.Interactable
{ {
return; return;
} }
var cell = Owner.EntityManager.SpawnEntity("PowerCellSmallHyper", Owner.Transform.GridPosition); var cell = Owner.EntityManager.SpawnEntity("PowerCellSmallHyper", Owner.Transform.GridPosition);
_cellContainer.Insert(cell); _cellContainer.Insert(cell);
} }

View File

@@ -1,6 +1,8 @@
using System; using System;
using Content.Server.GameObjects.EntitySystems; using Content.Server.GameObjects.EntitySystems;
using Content.Shared.Audio; using Content.Shared.Audio;
using Content.Shared.GameObjects;
using Content.Shared.GameObjects.Components;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Server.GameObjects.EntitySystems; using Robust.Server.GameObjects.EntitySystems;
using Robust.Shared.Audio; using Robust.Shared.Audio;
@@ -32,6 +34,7 @@ namespace Content.Server.GameObjects.Components.Interactable.Tools
#pragma warning restore 649 #pragma warning restore 649
public override string Name => "Welder"; public override string Name => "Welder";
public override uint? NetID => ContentNetIDs.WELDER;
/// <summary> /// <summary>
/// Maximum fuel capacity the welder can hold /// Maximum fuel capacity the welder can hold
@@ -40,8 +43,13 @@ namespace Content.Server.GameObjects.Components.Interactable.Tools
public float FuelCapacity public float FuelCapacity
{ {
get => _fuelCapacity; get => _fuelCapacity;
set => _fuelCapacity = value; set
{
_fuelCapacity = value;
Dirty();
} }
}
private float _fuelCapacity = 50; private float _fuelCapacity = 50;
/// <summary> /// <summary>
@@ -51,9 +59,15 @@ namespace Content.Server.GameObjects.Components.Interactable.Tools
public float Fuel public float Fuel
{ {
get => _fuel; get => _fuel;
set => _fuel = value; set
{
_fuel = value;
Dirty();
} }
}
private float _fuel = 0; private float _fuel = 0;
private bool _activated = false;
/// <summary> /// <summary>
/// Default Cost of using the welder fuel for an action /// Default Cost of using the welder fuel for an action
@@ -69,7 +83,15 @@ namespace Content.Server.GameObjects.Components.Interactable.Tools
/// Status of welder, whether it is ignited /// Status of welder, whether it is ignited
/// </summary> /// </summary>
[ViewVariables] [ViewVariables]
public bool Activated { get; private set; } = false; public bool Activated
{
get => _activated;
private set
{
_activated = value;
Dirty();
}
}
//private string OnSprite { get; set; } //private string OnSprite { get; set; }
//private string OffSprite { get; set; } //private string OffSprite { get; set; }
@@ -87,6 +109,7 @@ namespace Content.Server.GameObjects.Components.Interactable.Tools
serializer.DataField(ref _fuelCapacity, "Capacity", 50); serializer.DataField(ref _fuelCapacity, "Capacity", 50);
serializer.DataField(ref _fuel, "Fuel", FuelCapacity); serializer.DataField(ref _fuel, "Fuel", FuelCapacity);
serializer.DataField(ref _activated, "Activated", false);
} }
public void OnUpdate(float frameTime) public void OnUpdate(float frameTime)
@@ -185,5 +208,10 @@ namespace Content.Server.GameObjects.Components.Interactable.Tools
_entitySystemManager.GetEntitySystem<AudioSystem>() _entitySystemManager.GetEntitySystem<AudioSystem>()
.Play(file, AudioParams.Default.WithVolume(volume)); .Play(file, AudioParams.Default.WithVolume(volume));
} }
public override ComponentState GetComponentState()
{
return new WelderComponentState(FuelCapacity, Fuel, Activated);
}
} }
} }

View File

@@ -1,5 +1,6 @@
using System; using System;
using Content.Server.GameObjects.EntitySystems; using Content.Server.GameObjects.EntitySystems;
using Content.Shared.GameObjects.Components;
using Content.Shared.Interfaces; using Content.Shared.Interfaces;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.Reflection; using Robust.Shared.Interfaces.Reflection;
@@ -16,7 +17,7 @@ namespace Content.Server.GameObjects.Components.Stack
// TODO: Naming and presentation and such could use some improvement. // TODO: Naming and presentation and such could use some improvement.
[RegisterComponent] [RegisterComponent]
public class StackComponent : Component, IAttackBy, IExamine public class StackComponent : SharedStackComponent, IAttackBy, IExamine
{ {
#pragma warning disable 649 #pragma warning disable 649
[Dependency] private readonly ISharedNotifyManager _sharedNotifyManager; [Dependency] private readonly ISharedNotifyManager _sharedNotifyManager;
@@ -26,8 +27,6 @@ namespace Content.Server.GameObjects.Components.Stack
private int _count = 50; private int _count = 50;
private int _maxCount = 50; private int _maxCount = 50;
public override string Name => "Stack";
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]
public int Count public int Count
{ {
@@ -39,11 +38,20 @@ namespace Content.Server.GameObjects.Components.Stack
{ {
Owner.Delete(); Owner.Delete();
} }
Dirty();
} }
} }
[ViewVariables] [ViewVariables]
public int MaxCount { get => _maxCount; private set => _maxCount = value; } public int MaxCount
{
get => _maxCount;
private set
{
_maxCount = value;
Dirty();
}
}
[ViewVariables] [ViewVariables]
public int AvailableSpace => MaxCount - Count; public int AvailableSpace => MaxCount - Count;
@@ -155,6 +163,11 @@ namespace Content.Server.GameObjects.Components.Stack
"There is [color=lightgray]1[/color] thing in the stack", "There is [color=lightgray]1[/color] thing in the stack",
"There are [color=lightgray]{0}[/color] things in the stack.", Count, Count)); "There are [color=lightgray]{0}[/color] things in the stack.", Count, Count));
} }
public override ComponentState GetComponentState()
{
return new StackComponentState(Count, MaxCount);
}
} }
public enum StackType public enum StackType

View File

@@ -24,33 +24,27 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile
[RegisterComponent] [RegisterComponent]
public class BallisticMagazineWeaponComponent : BallisticWeaponComponent, IUse, IAttackBy, IMapInit public class BallisticMagazineWeaponComponent : BallisticWeaponComponent, IUse, IAttackBy, IMapInit
{ {
private const float BulletOffset = 0.2f;
public override string Name => "BallisticMagazineWeapon"; public override string Name => "BallisticMagazineWeapon";
public override uint? NetID => ContentNetIDs.BALLISTIC_MAGAZINE_WEAPON;
[ViewVariables] [ViewVariables] private string _defaultMagazine;
private string _defaultMagazine;
[ViewVariables] [ViewVariables] private ContainerSlot _magazineSlot;
private ContainerSlot _magazineSlot;
private List<BallisticMagazineType> _magazineTypes; private List<BallisticMagazineType> _magazineTypes;
[ViewVariables] [ViewVariables] public List<BallisticMagazineType> MagazineTypes => _magazineTypes;
public List<BallisticMagazineType> MagazineTypes => _magazineTypes; [ViewVariables] private IEntity Magazine => _magazineSlot.ContainedEntity;
[ViewVariables]
private IEntity Magazine => _magazineSlot.ContainedEntity;
#pragma warning disable 649 #pragma warning disable 649
[Dependency] private readonly IRobustRandom _bulletDropRandom; [Dependency] private readonly IRobustRandom _bulletDropRandom;
#pragma warning restore 649 #pragma warning restore 649
[ViewVariables] [ViewVariables] private string _magInSound;
private string _magInSound; [ViewVariables] private string _magOutSound;
[ViewVariables] [ViewVariables] private string _autoEjectSound;
private string _magOutSound; [ViewVariables] private bool _autoEjectMagazine;
[ViewVariables] [ViewVariables] private AppearanceComponent _appearance;
private string _autoEjectSound;
[ViewVariables]
private bool _autoEjectMagazine;
[ViewVariables]
private AppearanceComponent _appearance;
private static readonly Direction[] _randomBulletDirs = private static readonly Direction[] _randomBulletDirs =
{ {
@@ -137,6 +131,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile
} }
_updateAppearance(); _updateAppearance();
Dirty();
return true; return true;
} }
@@ -153,15 +148,17 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile
entity.Transform.GridPosition = Owner.Transform.GridPosition; entity.Transform.GridPosition = Owner.Transform.GridPosition;
if (_magOutSound != null) if (_magOutSound != null)
{ {
Owner.GetComponent<SoundComponent>().Play(_magOutSound); Owner.GetComponent<SoundComponent>().Play(_magOutSound, AudioParams.Default.WithVolume(20));
} }
_updateAppearance(); _updateAppearance();
Dirty();
entity.GetComponent<BallisticMagazineComponent>().OnAmmoCountChanged -= _magazineAmmoCountChanged; entity.GetComponent<BallisticMagazineComponent>().OnAmmoCountChanged -= _magazineAmmoCountChanged;
return true; return true;
} }
_updateAppearance(); _updateAppearance();
Dirty();
return false; return false;
} }
@@ -171,7 +168,8 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile
// Eject chambered bullet. // Eject chambered bullet.
var entity = RemoveFromChamber(chamber); var entity = RemoveFromChamber(chamber);
entity.Transform.GridPosition = Owner.Transform.GridPosition; var offsetPos = (CalcBulletOffset(), CalcBulletOffset());
entity.Transform.GridPosition = Owner.Transform.GridPosition.Offset(offsetPos);
entity.Transform.LocalRotation = _bulletDropRandom.Pick(_randomBulletDirs).ToAngle(); entity.Transform.LocalRotation = _bulletDropRandom.Pick(_randomBulletDirs).ToAngle();
var effect = $"/Audio/Guns/Casings/casingfall{_bulletDropRandom.Next(1, 4)}.ogg"; var effect = $"/Audio/Guns/Casings/casingfall{_bulletDropRandom.Next(1, 4)}.ogg";
Owner.GetComponent<SoundComponent>().Play(effect, AudioParams.Default.WithVolume(-3)); Owner.GetComponent<SoundComponent>().Play(effect, AudioParams.Default.WithVolume(-3));
@@ -187,15 +185,29 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile
if (magComponent.CountLoaded == 0 && _autoEjectMagazine) if (magComponent.CountLoaded == 0 && _autoEjectMagazine)
{ {
DoAutoEject();
}
}
Dirty();
_updateAppearance();
}
private float CalcBulletOffset()
{
return _bulletDropRandom.NextFloat() * (BulletOffset * 2) - BulletOffset;
}
private void DoAutoEject()
{
SendNetworkMessage(new BmwComponentAutoEjectedMessage());
EjectMagazine(); EjectMagazine();
if (_autoEjectSound != null) if (_autoEjectSound != null)
{ {
Owner.GetComponent<SoundComponent>().Play(_autoEjectSound, AudioParams.Default.WithVolume(-5)); Owner.GetComponent<SoundComponent>().Play(_autoEjectSound, AudioParams.Default.WithVolume(-5));
} }
}
}
_updateAppearance(); Dirty();
} }
public bool UseEntity(UseEntityEventArgs eventArgs) public bool UseEntity(UseEntityEventArgs eventArgs)
@@ -237,6 +249,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile
private void _magazineAmmoCountChanged() private void _magazineAmmoCountChanged()
{ {
Dirty();
_updateAppearance(); _updateAppearance();
} }
@@ -257,6 +270,21 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Projectile
} }
} }
public override ComponentState GetComponentState()
{
var chambered = GetChambered(0) != null;
(int, int)? count = null;
if (Magazine != null)
{
var magComponent = Magazine.GetComponent<BallisticMagazineComponent>();
count = (magComponent.CountLoaded, magComponent.Capacity);
}
return new BallisticMagazineWeaponComponentState(chambered, count);
}
[Verb] [Verb]
public sealed class EjectMagazineVerb : Verb<BallisticMagazineWeaponComponent> public sealed class EjectMagazineVerb : Verb<BallisticMagazineWeaponComponent>
{ {

View File

@@ -0,0 +1,24 @@
using System;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
namespace Content.Shared.GameObjects.Components
{
public abstract class SharedHandheldLightComponent : Component
{
public sealed override string Name => "HandheldLight";
public sealed override uint? NetID => ContentNetIDs.HANDHELD_LIGHT;
public sealed override Type StateType => typeof(HandheldLightComponentState);
[Serializable, NetSerializable]
protected sealed class HandheldLightComponentState : ComponentState
{
public HandheldLightComponentState(float? charge) : base(ContentNetIDs.HANDHELD_LIGHT)
{
Charge = charge;
}
public float? Charge { get; }
}
}
}

View File

@@ -0,0 +1,26 @@
using System;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
namespace Content.Shared.GameObjects.Components
{
public abstract class SharedStackComponent : Component
{
public sealed override string Name => "Stack";
public sealed override uint? NetID => ContentNetIDs.STACK;
public sealed override Type StateType => typeof(StackComponentState);
[Serializable, NetSerializable]
protected sealed class StackComponentState : ComponentState
{
public int Count { get; }
public int MaxCount { get; }
public StackComponentState(int count, int maxCount) : base(ContentNetIDs.STACK)
{
Count = count;
MaxCount = maxCount;
}
}
}
}

View File

@@ -0,0 +1,39 @@
using System;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
namespace Content.Shared.GameObjects.Components.Weapons.Ranged
{
[Serializable, NetSerializable]
public class BallisticMagazineWeaponComponentState : ComponentState
{
/// <summary>
/// True if a bullet is chambered.
/// </summary>
public bool Chambered { get; }
/// <summary>
/// Count of bullets in the magazine.
/// </summary>
/// <remarks>
/// Null if no magazine is inserted.
/// </remarks>
public (int count, int max)? MagazineCount { get; }
public BallisticMagazineWeaponComponentState(bool chambered, (int count, int max)? magazineCount) : base(ContentNetIDs.BALLISTIC_MAGAZINE_WEAPON)
{
Chambered = chambered;
MagazineCount = magazineCount;
}
}
// BMW is "Ballistic Magazine Weapon" here.
/// <summary>
/// Fired server -> client when the magazine in a Ballistic Magazine Weapon got auto-ejected.
/// </summary>
[Serializable, NetSerializable]
public sealed class BmwComponentAutoEjectedMessage : ComponentMessage
{
}
}

View File

@@ -0,0 +1,21 @@
using System;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
namespace Content.Shared.GameObjects.Components
{
[NetSerializable, Serializable]
public class WelderComponentState : ComponentState
{
public float FuelCapacity { get; }
public float Fuel { get; }
public bool Activated { get; }
public WelderComponentState(float fuelCapacity, float fuel, bool activated) : base(ContentNetIDs.WELDER)
{
FuelCapacity = fuelCapacity;
Fuel = fuel;
Activated = activated;
}
}
}

View File

@@ -36,5 +36,9 @@
public const uint GALACTIC_MARKET = 1031; public const uint GALACTIC_MARKET = 1031;
public const uint HAIR = 1032; public const uint HAIR = 1032;
public const uint INSTRUMENTS = 1033; public const uint INSTRUMENTS = 1033;
public const uint WELDER = 1034;
public const uint STACK = 1035;
public const uint HANDHELD_LIGHT = 1036;
public const uint BALLISTIC_MAGAZINE_WEAPON = 1037;
} }
} }

View File

@@ -0,0 +1,100 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="16"
height="16"
viewBox="0 0 4.2333337 4.2333334"
version="1.1"
id="svg8"
inkscape:version="0.92.4 5da689c313, 2019-01-14"
sodipodi:docname="item_status_left.svg"
inkscape:export-filename="/home/pj/Projects/space-station-14/Resources/Nano/item_status_left.svg.96dpi.png"
inkscape:export-xdpi="96.000008"
inkscape:export-ydpi="96.000008">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="22.627417"
inkscape:cx="9.7147126"
inkscape:cy="13.122502"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:pagecheckerboard="true"
units="px"
showguides="true"
inkscape:window-width="1920"
inkscape:window-height="1043"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:guide-bbox="true"
inkscape:snap-intersection-paths="true"
inkscape:snap-bbox="true"
inkscape:snap-page="true">
<inkscape:grid
type="xygrid"
id="grid817" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-26.597681,-208.71661)">
<path
style="opacity:1;vector-effect:none;fill:#25252a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.26458335;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
d="m 26.597681,212.94995 h 4.233334 v -1.58751 l -2.645833,-2.64583 -1.587501,-1e-5 z"
id="rect815"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccc" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#474747;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.26458335;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 45.902791,212.76114 v 3.23047 l 1.003906,1.00195 h 3.228516 v -3.22852 l -1.001953,-1.0039 h -0.05469 z m 0.263672,0.26367 h 2.855469 l 0.849609,0.84961 v 2.85547 h -2.855468 l -0.84961,-0.84961 z"
id="rect815-3"
inkscape:connector-curvature="0" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#474747;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.26458335;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 46.344997,207.34832 -0.290893,0.17906 1.133594,1.1102 v 2.91047 h 0.264579 v -3.04271 z"
id="rect815-6"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccc" />
<path
style="fill:none;stroke:#474747;stroke-width:0.5291667;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 38.473823,212.26371 v -3.175 l -1.058333,-1.05833"
id="path856-6"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccc" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#474747;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.5291667;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 28.185182,208.71661 h -0.748024 l 2.86456,2.86457 v 1.36874 h 0.529297 v -1.58749 z"
id="path856-7"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 B

View File

@@ -0,0 +1,105 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="16"
height="16"
viewBox="0 0 4.2333337 4.2333334"
version="1.1"
id="svg8"
inkscape:version="0.92.4 5da689c313, 2019-01-14"
sodipodi:docname="item_status_right.svg"
inkscape:export-filename="/home/pj/Projects/space-station-14/Resources/Nano/item_status_right.svg.96dpi.png"
inkscape:export-xdpi="96.000008"
inkscape:export-ydpi="96.000008">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="22.627417"
inkscape:cx="-2.4386852"
inkscape:cy="13.122502"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:pagecheckerboard="true"
units="px"
showguides="true"
inkscape:window-width="1920"
inkscape:window-height="1043"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:guide-bbox="true"
inkscape:snap-intersection-paths="true"
inkscape:snap-bbox="true"
inkscape:snap-page="true">
<inkscape:grid
type="xygrid"
id="grid817" />
<sodipodi:guide
position="2.645833,3.0000002e-07"
orientation="0.70710806,0.7071055"
id="guide819"
inkscape:locked="false" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-26.597681,-208.71661)">
<path
style="opacity:1;vector-effect:none;fill:#25252a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.26458335;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
d="m 30.831015,212.94995 h -4.233334 v -1.58751 l 2.645833,-2.64583 1.587501,-1e-5 z"
id="rect815"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccc" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#474747;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.26458335;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 45.902791,212.76114 v 3.23047 l 1.003906,1.00195 h 3.228516 v -3.22852 l -1.001953,-1.0039 h -0.05469 z m 0.263672,0.26367 h 2.855469 l 0.849609,0.84961 v 2.85547 h -2.855468 l -0.84961,-0.84961 z"
id="rect815-3"
inkscape:connector-curvature="0" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#474747;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.26458335;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 46.344997,207.34832 -0.290893,0.17906 1.133594,1.1102 v 2.91047 h 0.264579 v -3.04271 z"
id="rect815-6"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccc" />
<path
style="fill:none;stroke:#474747;stroke-width:0.5291667;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 38.473823,212.26371 v -3.175 l -1.058333,-1.05833"
id="path856-6"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccc" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#474747;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.5291667;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 29.243514,208.71661 h 0.748024 l -2.86456,2.86457 v 1.36874 h -0.529297 v -1.58749 z"
id="path856-7"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 B

View File

@@ -206,6 +206,7 @@
- A65mm - A65mm
default_magazine: magazine_65mm default_magazine: magazine_65mm
auto_eject_magazine: true auto_eject_magazine: true
lmg_alarm_animation: true
sound_auto_eject: /Audio/Guns/EmptyAlarm/lmg_empty_alarm.ogg sound_auto_eject: /Audio/Guns/EmptyAlarm/lmg_empty_alarm.ogg
sound_gunshot: /Audio/Guns/Gunshots/rifle.ogg sound_gunshot: /Audio/Guns/Gunshots/rifle.ogg
- type: Appearance - type: Appearance
@@ -240,6 +241,7 @@
- A65mm - A65mm
default_magazine: magazine_65mm default_magazine: magazine_65mm
auto_eject_magazine: true auto_eject_magazine: true
lmg_alarm_animation: true
sound_auto_eject: /Audio/Guns/EmptyAlarm/lmg_empty_alarm.ogg sound_auto_eject: /Audio/Guns/EmptyAlarm/lmg_empty_alarm.ogg
sound_gunshot: /Audio/Guns/Gunshots/rifle.ogg sound_gunshot: /Audio/Guns/Gunshots/rifle.ogg
- type: Appearance - type: Appearance

View File

@@ -5,6 +5,7 @@
components: components:
- type: Stack - type: Stack
- type: Material - type: Material
- type: ItemStatus
- type: entity - type: entity
name: Steel Sheet name: Steel Sheet

View File

@@ -27,7 +27,7 @@
- type: MeleeWeapon - type: MeleeWeapon
- type: entity - type: entity
name: Welder name: Welding Tool
parent: BaseItem parent: BaseItem
id: Welder id: Welder
description: Melts anything as long as it's fueled, don't forget your eye protection! description: Melts anything as long as it's fueled, don't forget your eye protection!
@@ -46,6 +46,7 @@
state: welder state: welder
- type: ItemCooldown - type: ItemCooldown
- type: MeleeWeapon - type: MeleeWeapon
- type: ItemStatus
- type: entity - type: entity
name: Wrench name: Wrench

Binary file not shown.

Before

Width:  |  Height:  |  Size: 954 B

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 926 B

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 496 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 498 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 497 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 479 B