+++ /dev/null
-using Content.Client.Tools.UI;
-using Content.Shared.Tools.Components;
-
-namespace Content.Client.Tools.Components
-{
- [RegisterComponent, Access(typeof(ToolSystem), typeof(WelderStatusControl))]
- public sealed partial class WelderComponent : SharedWelderComponent
- {
- [ViewVariables(VVAccess.ReadWrite)]
- public bool UiUpdateNeeded { get; set; }
-
- [ViewVariables]
- public float FuelCapacity { get; set; }
-
- [ViewVariables]
- public float Fuel { get; set; }
- }
-}
using Content.Client.Items;
using Content.Client.Tools.Components;
using Content.Client.Tools.UI;
-using Content.Shared.Item;
using Content.Shared.Tools.Components;
using Robust.Client.GameObjects;
-using Robust.Shared.GameStates;
using SharedToolSystem = Content.Shared.Tools.Systems.SharedToolSystem;
namespace Content.Client.Tools
{
base.Initialize();
- SubscribeLocalEvent<WelderComponent, ComponentHandleState>(OnWelderHandleState);
Subs.ItemStatus<WelderComponent>(ent => new WelderStatusControl(ent));
Subs.ItemStatus<MultipleToolComponent>(ent => new MultipleToolStatusControl(ent));
}
sprite.LayerSetSprite(0, current.Sprite);
}
}
-
- private void OnWelderHandleState(EntityUid uid, WelderComponent welder, ref ComponentHandleState args)
- {
- if (args.Current is not WelderComponentState state)
- return;
-
- welder.FuelCapacity = state.FuelCapacity;
- welder.Fuel = state.Fuel;
- welder.UiUpdateNeeded = true;
- }
-
- protected override bool IsWelder(EntityUid uid)
- {
- return HasComp<WelderComponent>(uid);
- }
}
}
using Content.Client.Message;
using Content.Client.Stylesheets;
-using Content.Client.Tools.Components;
-using Content.Shared.Item;
+using Content.Shared.Tools.Components;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Timing;
-using ItemToggleComponent = Content.Shared.Item.ItemToggle.Components.ItemToggleComponent;
namespace Content.Client.Tools.UI;
public sealed class WelderStatusControl : Control
{
- [Dependency] private readonly IEntityManager _entMan = default!;
+ [Dependency] private readonly IGameTiming _gameTiming = default!;
- private readonly WelderComponent _parent;
- private readonly ItemToggleComponent? _toggleComponent;
+ private readonly ToolSystem _tool;
+
+ private readonly Entity<WelderComponent> _parent;
private readonly RichTextLabel _label;
public WelderStatusControl(Entity<WelderComponent> parent)
{
+ IoCManager.InjectDependencies(this);
+
_parent = parent;
- _entMan = IoCManager.Resolve<IEntityManager>();
- if (_entMan.TryGetComponent<ItemToggleComponent>(parent, out var itemToggle))
- _toggleComponent = itemToggle;
+ var entMan = IoCManager.Resolve<IEntityManager>();
+ _tool = entMan.System<ToolSystem>();
+
_label = new RichTextLabel { StyleClasses = { StyleNano.StyleClassItemStatus } };
AddChild(_label);
{
base.FrameUpdate(args);
- if (!_parent.UiUpdateNeeded)
- {
- return;
- }
Update();
}
public void Update()
{
- _parent.UiUpdateNeeded = false;
+ if (!_gameTiming.IsFirstTimePredicted)
+ return;
- var fuelCap = _parent.FuelCapacity;
- var fuel = _parent.Fuel;
- var lit = false;
- if (_toggleComponent != null)
- {
- lit = _toggleComponent.Activated;
- }
+ var (fuel, fuelCap) = _tool.GetWelderFuelAndCapacity(_parent, _parent);
+ var lit = _parent.Comp.Enabled;
_label.SetMarkup(Loc.GetString("welder-component-on-examine-detailed-message",
("colorName", fuel < fuelCap / 4f ? "darkorange" : "orange"),
- ("fuelLeft", Math.Round(fuel, 1)),
+ ("fuelLeft", Math.Round(fuel.Float(), 1)),
("fuelCapacity", fuelCap),
("status", Loc.GetString(lit ? "welder-component-on-examine-welder-lit-message" : "welder-component-on-examine-welder-not-lit-message"))));
}
using Content.IntegrationTests.Tests.Interaction;
using Content.IntegrationTests.Tests.Weldable;
using Content.Shared.Tools.Components;
-using Content.Server.Tools.Components;
-using Content.Shared.DoAfter;
namespace Content.IntegrationTests.Tests.DoAfter;
+++ /dev/null
-using Content.Shared.FixedPoint;
-
-
-namespace Content.Server.Chemistry.Components
-{
- [RegisterComponent]
- public sealed partial class ReagentTankComponent : Component
- {
- [DataField("transferAmount")]
- [ViewVariables(VVAccess.ReadWrite)]
- public FixedPoint2 TransferAmount { get; set; } = FixedPoint2.New(10);
-
- [DataField("tankType")]
- [ViewVariables(VVAccess.ReadWrite)]
- public ReagentTankType TankType { get; set; } = ReagentTankType.Unspecified;
- }
-
- public enum ReagentTankType : byte
- {
- Unspecified,
- Fuel
- }
-}
using Content.Shared.Tools;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+using Robust.Shared.Prototypes;
-namespace Content.Server.Construction.Components
+namespace Content.Server.Construction.Components;
+
+/// <summary>
+/// Used for something that can be refined by welder.
+/// For example, glass shard can be refined to glass sheet.
+/// </summary>
+[RegisterComponent]
+public sealed partial class WelderRefinableComponent : Component
{
- /// <summary>
- /// Used for something that can be refined by welder.
- /// For example, glass shard can be refined to glass sheet.
- /// </summary>
- [RegisterComponent]
- public sealed partial class WelderRefinableComponent : Component
- {
- [DataField("refineResult")]
- public HashSet<string>? RefineResult = new();
+ [DataField]
+ public HashSet<EntProtoId>? RefineResult = new();
+
+ [DataField]
+ public float RefineTime = 2f;
- [DataField("refineTime")]
- public float RefineTime = 2f;
+ [DataField]
+ public float RefineFuel;
- [DataField("qualityNeeded", customTypeSerializer:typeof(PrototypeIdSerializer<ToolQualityPrototype>))]
- public string QualityNeeded = "Welding";
- }
+ [DataField]
+ public ProtoId<ToolQualityPrototype> QualityNeeded = "Welding";
}
using Content.Shared.Interaction;
using Content.Shared.Prying.Systems;
using Content.Shared.Radio.EntitySystems;
-using Content.Shared.Tools.Components;
using Content.Shared.Tools.Systems;
using Robust.Shared.Containers;
-using Robust.Shared.Map;
using Robust.Shared.Utility;
#if EXCEPTION_TOLERANCE
// ReSharper disable once RedundantUsingDirective
TimeSpan.FromSeconds(toolInsertStep.DoAfter),
new [] { toolInsertStep.Tool },
new ConstructionInteractDoAfterEvent(EntityManager, interactUsing),
- out var doAfter);
+ out var doAfter,
+ toolInsertStep.Fuel);
return result && doAfter != null ? HandleResult.DoAfter : HandleResult.False;
}
using Content.Server.Construction.Components;
using Content.Server.Stack;
using Content.Shared.Construction;
-using Content.Shared.DoAfter;
using Content.Shared.Interaction;
using Content.Shared.Stacks;
-using Content.Shared.Tools;
-using Robust.Shared.Serialization;
using SharedToolSystem = Content.Shared.Tools.Systems.SharedToolSystem;
namespace Content.Server.Construction
if (args.Handled)
return;
- args.Handled = _toolSystem.UseTool(args.Used, args.User, uid, component.RefineTime, component.QualityNeeded, new WelderRefineDoAfterEvent());
+ args.Handled = _toolSystem.UseTool(args.Used, args.User, uid, component.RefineTime, component.QualityNeeded, new WelderRefineDoAfterEvent(), fuel: component.RefineFuel);
}
private void OnDoAfter(EntityUid uid, WelderRefinableComponent component, WelderRefineDoAfterEvent args)
// spawn each result after refine
foreach (var result in component.RefineResult!)
{
- var droppedEnt = EntityManager.SpawnEntity(result, resultPosition);
+ var droppedEnt = Spawn(result, resultPosition);
// TODO: If something has a stack... Just use a prototype with a single thing in the stack.
// This is not a good way to do it.
using Content.Server.Administration.Logs;
using Content.Server.Damage.Components;
-using Content.Server.Tools.Components;
-using Content.Shared.Item;
using Content.Shared.Damage;
using Content.Shared.Database;
using Content.Shared.Interaction;
using Content.Shared.Damage;
using Content.Shared.Tools;
+using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Repairable
/// If this data-field is specified, it will change damage by this amount instead of setting all damage to 0.
/// in order to heal/repair the damage values have to be negative.
/// </remarks>
- [ViewVariables(VVAccess.ReadWrite)] [DataField("damage")]
+ [DataField]
public DamageSpecifier? Damage;
- [ViewVariables(VVAccess.ReadWrite)] [DataField("fuelCost")]
+ [DataField]
public int FuelCost = 5;
- [ViewVariables(VVAccess.ReadWrite)] [DataField("qualityNeeded", customTypeSerializer:typeof(PrototypeIdSerializer<ToolQualityPrototype>))]
- public string QualityNeeded = "Welding";
+ [DataField]
+ public ProtoId<ToolQualityPrototype> QualityNeeded = "Welding";
- [ViewVariables(VVAccess.ReadWrite)] [DataField("doAfterDelay")]
+ [DataField]
public int DoAfterDelay = 1;
/// <summary>
/// A multiplier that will be applied to the above if an entity is repairing themselves.
/// </summary>
- [ViewVariables(VVAccess.ReadWrite)] [DataField("selfRepairPenalty")]
+ [DataField]
public float SelfRepairPenalty = 3f;
/// <summary>
/// Whether or not an entity is allowed to repair itself.
/// </summary>
- [DataField("allowSelfRepair")]
+ [DataField]
public bool AllowSelfRepair = true;
}
}
using Content.Shared.Interaction;
using Content.Shared.Popups;
using Content.Shared.Repairable;
-using Content.Shared.Tools;
using SharedToolSystem = Content.Shared.Tools.Systems.SharedToolSystem;
namespace Content.Server.Repairable
}
// Run the repairing doafter
- args.Handled = _toolSystem.UseTool(args.Used, args.User, uid, delay, component.QualityNeeded, new RepairFinishedEvent());
+ args.Handled = _toolSystem.UseTool(args.Used, args.User, uid, delay, component.QualityNeeded, new RepairFinishedEvent(), component.FuelCost);
}
}
}
+++ /dev/null
-using Content.Shared.Chemistry.Components;
-using Content.Shared.Chemistry.Reagent;
-using Content.Shared.FixedPoint;
-using Content.Shared.Tools.Components;
-using Robust.Shared.Audio;
-using Robust.Shared.Prototypes;
-
-namespace Content.Server.Tools.Components
-{
- [RegisterComponent]
- public sealed partial class WelderComponent : SharedWelderComponent
- {
- /// <summary>
- /// Name of <see cref="FuelSolution"/>.
- /// </summary>
- [DataField("fuelSolution"), ViewVariables(VVAccess.ReadWrite)]
- public string FuelSolutionName = "Welder";
-
- /// <summary>
- /// Solution on the entity that contains the fuel.
- /// </summary>
- [DataField("fuelSolutionRef")]
- public Entity<SolutionComponent>? FuelSolution = null;
-
- /// <summary>
- /// Reagent that will be used as fuel for welding.
- /// </summary>
- [DataField, ViewVariables(VVAccess.ReadWrite)]
- public ProtoId<ReagentPrototype> FuelReagent = "WeldingFuel";
-
- /// <summary>
- /// Fuel consumption per second while the welder is active.
- /// </summary>
- [DataField, ViewVariables(VVAccess.ReadWrite)]
- public FixedPoint2 FuelConsumption = FixedPoint2.New(2.0f);
-
- /// <summary>
- /// A fuel amount to be consumed when the welder goes from being unlit to being lit.
- /// </summary>
- [DataField, ViewVariables(VVAccess.ReadWrite)]
- public FixedPoint2 FuelLitCost = FixedPoint2.New(0.5f);
-
- /// <summary>
- /// Sound played when refilling the welder.
- /// </summary>
- [DataField]
- public SoundSpecifier WelderRefill = new SoundPathSpecifier("/Audio/Effects/refill.ogg");
-
- /// <summary>
- /// Whether the item is safe to refill while lit without exploding the tank.
- /// </summary>
- [DataField]
- public bool TankSafe = false; //I have no idea what I'm doing
-
- }
-}
+++ /dev/null
-using Content.Server.Chemistry.Components;
-using Content.Server.IgnitionSource;
-using Content.Server.Tools.Components;
-using Content.Shared.Chemistry.Components.SolutionManager;
-using Content.Shared.Database;
-using Content.Shared.DoAfter;
-using Content.Shared.Examine;
-using Content.Shared.FixedPoint;
-using Content.Shared.Interaction;
-using Content.Shared.Item.ItemToggle;
-using Content.Shared.Tools.Components;
-using Robust.Shared.GameStates;
-using System.Linq;
-using Content.Shared.Item.ItemToggle.Components;
-
-namespace Content.Server.Tools
-{
- public sealed partial class ToolSystem
- {
- [Dependency] private readonly SharedItemToggleSystem _itemToggle = default!;
- [Dependency] private readonly IgnitionSourceSystem _ignitionSource = default!;
- private readonly HashSet<EntityUid> _activeWelders = new();
-
- private const float WelderUpdateTimer = 1f;
- private float _welderTimer;
-
- public void InitializeWelders()
- {
- SubscribeLocalEvent<WelderComponent, ExaminedEvent>(OnWelderExamine);
- SubscribeLocalEvent<WelderComponent, AfterInteractEvent>(OnWelderAfterInteract);
- SubscribeLocalEvent<WelderComponent, DoAfterAttemptEvent<ToolDoAfterEvent>>(OnWelderToolUseAttempt);
- SubscribeLocalEvent<WelderComponent, ComponentShutdown>(OnWelderShutdown);
- SubscribeLocalEvent<WelderComponent, ComponentGetState>(OnWelderGetState);
- SubscribeLocalEvent<WelderComponent, ItemToggledEvent>(OnToggle);
- SubscribeLocalEvent<WelderComponent, ItemToggleActivateAttemptEvent>(OnActivateAttempt);
- }
-
- public (FixedPoint2 fuel, FixedPoint2 capacity) GetWelderFuelAndCapacity(EntityUid uid, WelderComponent? welder = null, SolutionContainerManagerComponent? solutionContainer = null)
- {
- if (!Resolve(uid, ref welder, ref solutionContainer)
- || !_solutionContainer.ResolveSolution((uid, solutionContainer), welder.FuelSolutionName, ref welder.FuelSolution, out var fuelSolution))
- return (FixedPoint2.Zero, FixedPoint2.Zero);
-
- return (fuelSolution.GetTotalPrototypeQuantity(welder.FuelReagent), fuelSolution.MaxVolume);
- }
-
- private void OnToggle(Entity<WelderComponent> entity, ref ItemToggledEvent args)
- {
- if (args.Activated)
- TurnOn(entity, args.User);
- else
- TurnOff(entity, args.User);
- }
-
- private void OnActivateAttempt(Entity<WelderComponent> entity, ref ItemToggleActivateAttemptEvent args)
- {
- if (!_solutionContainer.ResolveSolution(entity.Owner, entity.Comp.FuelSolutionName, ref entity.Comp.FuelSolution, out var solution))
- {
- args.Cancelled = true;
- args.Popup = Loc.GetString("welder-component-no-fuel-message");
- return;
- }
-
- var fuel = solution.GetTotalPrototypeQuantity(entity.Comp.FuelReagent);
- if (fuel == FixedPoint2.Zero || fuel < entity.Comp.FuelLitCost)
- {
- args.Popup = Loc.GetString("welder-component-no-fuel-message");
- args.Cancelled = true;
- }
- }
-
- public void TurnOn(Entity<WelderComponent> entity, EntityUid? user)
- {
- if (!_solutionContainer.ResolveSolution(entity.Owner, entity.Comp.FuelSolutionName, ref entity.Comp.FuelSolution))
- return;
-
- _solutionContainer.RemoveReagent(entity.Comp.FuelSolution.Value, entity.Comp.FuelReagent, entity.Comp.FuelLitCost);
- AdminLogger.Add(LogType.InteractActivate, LogImpact.Low,
- $"{ToPrettyString(user):user} toggled {ToPrettyString(entity.Owner):welder} on");
-
- var xform = Transform(entity);
- if (xform.GridUid is { } gridUid)
- {
- var position = _transformSystem.GetGridOrMapTilePosition(entity.Owner, xform);
- _atmosphereSystem.HotspotExpose(gridUid, position, 700, 50, entity.Owner, true);
- }
-
- _activeWelders.Add(entity);
- }
-
- public void TurnOff(Entity<WelderComponent> entity, EntityUid? user)
- {
- AdminLogger.Add(LogType.InteractActivate, LogImpact.Low,
- $"{ToPrettyString(user):user} toggled {ToPrettyString(entity.Owner):welder} off");
- _activeWelders.Remove(entity);
- }
-
- private void OnWelderExamine(Entity<WelderComponent> entity, ref ExaminedEvent args)
- {
- using (args.PushGroup(nameof(WelderComponent)))
- {
- if (_itemToggle.IsActivated(entity.Owner))
- {
- args.PushMarkup(Loc.GetString("welder-component-on-examine-welder-lit-message"));
- }
- else
- {
- args.PushMarkup(Loc.GetString("welder-component-on-examine-welder-not-lit-message"));
- }
-
- if (args.IsInDetailsRange)
- {
- var (fuel, capacity) = GetWelderFuelAndCapacity(entity.Owner, entity.Comp);
-
- args.PushMarkup(Loc.GetString("welder-component-on-examine-detailed-message",
- ("colorName", fuel < capacity / FixedPoint2.New(4f) ? "darkorange" : "orange"),
- ("fuelLeft", fuel),
- ("fuelCapacity", capacity),
- ("status", string.Empty))); // Lit status is handled above
- }
- }
- }
-
- private void OnWelderAfterInteract(Entity<WelderComponent> entity, ref AfterInteractEvent args)
- {
- if (args.Handled)
- return;
-
- if (args.Target is not { Valid: true } target || !args.CanReach)
- return;
-
- if (TryComp(target, out ReagentTankComponent? tank)
- && tank.TankType == ReagentTankType.Fuel
- && _solutionContainer.TryGetDrainableSolution(target, out var targetSoln, out var targetSolution)
- && _solutionContainer.ResolveSolution(entity.Owner, entity.Comp.FuelSolutionName, ref entity.Comp.FuelSolution, out var welderSolution))
- {
- var trans = FixedPoint2.Min(welderSolution.AvailableVolume, targetSolution.Volume);
- if (trans > 0)
- {
- var drained = _solutionContainer.Drain(target, targetSoln.Value, trans);
- _solutionContainer.TryAddSolution(entity.Comp.FuelSolution.Value, drained);
- _audio.PlayPvs(entity.Comp.WelderRefill, entity);
- _popup.PopupEntity(Loc.GetString("welder-component-after-interact-refueled-message"), entity, args.User);
- }
- else if (welderSolution.AvailableVolume <= 0)
- {
- _popup.PopupEntity(Loc.GetString("welder-component-already-full"), entity, args.User);
- }
- else
- {
- _popup.PopupEntity(Loc.GetString("welder-component-no-fuel-in-tank", ("owner", args.Target)), entity, args.User);
- }
-
- args.Handled = true;
- }
- }
-
- private void OnWelderToolUseAttempt(Entity<WelderComponent> entity, ref DoAfterAttemptEvent<ToolDoAfterEvent> args)
- {
- var user = args.DoAfter.Args.User;
-
- if (!_itemToggle.IsActivated(entity.Owner))
- {
- _popup.PopupEntity(Loc.GetString("welder-component-welder-not-lit-message"), entity, user);
- args.Cancel();
- }
- }
-
- private void OnWelderShutdown(Entity<WelderComponent> entity, ref ComponentShutdown args)
- {
- _activeWelders.Remove(entity);
- }
-
- private void OnWelderGetState(Entity<WelderComponent> entity, ref ComponentGetState args)
- {
- var (fuel, capacity) = GetWelderFuelAndCapacity(entity.Owner, entity.Comp);
- args.State = new WelderComponentState(capacity.Float(), fuel.Float());
- }
-
- private void UpdateWelders(float frameTime)
- {
- _welderTimer += frameTime;
-
- if (_welderTimer < WelderUpdateTimer)
- return;
-
- // TODO Serialization. _activeWelders is not serialized.
- // Need to use some "active" component, and EntityQuery over that.
- // Probably best to generalize it to a "ToggleableFuelDrain" component.
- foreach (var tool in _activeWelders.ToArray())
- {
- if (!TryComp(tool, out WelderComponent? welder)
- || !TryComp(tool, out SolutionContainerManagerComponent? solutionContainer))
- continue;
-
- if (!_solutionContainer.ResolveSolution((tool, solutionContainer), welder.FuelSolutionName, ref welder.FuelSolution, out var solution))
- continue;
-
- _solutionContainer.RemoveReagent(welder.FuelSolution.Value, welder.FuelReagent, welder.FuelConsumption * _welderTimer);
-
- if (solution.GetTotalPrototypeQuantity(welder.FuelReagent) <= FixedPoint2.Zero)
- {
- _itemToggle.Toggle(tool, predicted: false);
- }
-
- Dirty(tool, welder);
- }
- _welderTimer -= WelderUpdateTimer;
- }
- }
-}
using Content.Server.Atmos.EntitySystems;
-using Content.Server.Chemistry.Containers.EntitySystems;
-using Content.Server.Popups;
-using Content.Server.Tools.Components;
+using Content.Shared.Chemistry.Components.SolutionManager;
+using Content.Shared.FixedPoint;
+using Content.Shared.Tools.Components;
using Robust.Server.GameObjects;
-using Robust.Shared.Audio.Systems;
using SharedToolSystem = Content.Shared.Tools.Systems.SharedToolSystem;
-namespace Content.Server.Tools
+namespace Content.Server.Tools;
+
+public sealed class ToolSystem : SharedToolSystem
{
- // TODO move tool system to shared, and make it a friend of Tool Component.
- public sealed partial class ToolSystem : SharedToolSystem
- {
- [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
- [Dependency] private readonly PopupSystem _popup = default!;
- [Dependency] private readonly SharedAudioSystem _audio = default!;
- [Dependency] private readonly SolutionContainerSystem _solutionContainer = default!;
- [Dependency] private readonly TransformSystem _transformSystem = default!;
+ [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
+ [Dependency] private readonly TransformSystem _transformSystem = default!;
- public override void Initialize()
+ public override void TurnOn(Entity<WelderComponent> entity, EntityUid? user)
+ {
+ base.TurnOn(entity, user);
+ var xform = Transform(entity);
+ if (xform.GridUid is { } gridUid)
{
- base.Initialize();
-
- InitializeWelders();
+ var position = _transformSystem.GetGridOrMapTilePosition(entity.Owner, xform);
+ _atmosphereSystem.HotspotExpose(gridUid, position, 700, 50, entity.Owner, true);
}
+ }
- public override void Update(float frameTime)
- {
- base.Update(frameTime);
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
- UpdateWelders(frameTime);
- }
+ UpdateWelders(frameTime);
+ }
- protected override bool IsWelder(EntityUid uid)
+ //todo move to shared once you can remove reagents from shared without it freaking out.
+ private void UpdateWelders(float frameTime)
+ {
+ var query = EntityQueryEnumerator<WelderComponent, SolutionContainerManagerComponent>();
+ while (query.MoveNext(out var uid, out var welder, out var solutionContainer))
{
- return HasComp<WelderComponent>(uid);
+ if (!welder.Enabled)
+ continue;
+
+ welder.WelderTimer += frameTime;
+
+ if (welder.WelderTimer < welder.WelderUpdateTimer)
+ continue;
+
+ if (!SolutionContainerSystem.ResolveSolution((uid, solutionContainer), welder.FuelSolutionName, ref welder.FuelSolution, out var solution))
+ continue;
+
+ SolutionContainerSystem.RemoveReagent(welder.FuelSolution.Value, welder.FuelReagent, welder.FuelConsumption * welder.WelderTimer);
+
+ if (solution.GetTotalPrototypeQuantity(welder.FuelReagent) <= FixedPoint2.Zero)
+ {
+ ItemToggle.Toggle(uid, predicted: false);
+ }
+
+ Dirty(uid, welder);
+ welder.WelderTimer -= welder.WelderUpdateTimer;
}
}
}
+
--- /dev/null
+using Content.Shared.FixedPoint;
+using Robust.Shared.GameStates;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Chemistry.Components;
+
+[RegisterComponent, NetworkedComponent]
+public sealed partial class ReagentTankComponent : Component
+{
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public FixedPoint2 TransferAmount { get; set; } = FixedPoint2.New(10);
+
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public ReagentTankType TankType { get; set; } = ReagentTankType.Unspecified;
+}
+
+[Serializable, NetSerializable]
+public enum ReagentTankType : byte
+{
+ Unspecified,
+ Fuel
+}
+++ /dev/null
-using Robust.Shared.GameStates;
-using Robust.Shared.Serialization;
-
-namespace Content.Shared.Tools.Components
-{
- [NetworkedComponent]
- public abstract partial class SharedWelderComponent : Component { }
-
- [NetSerializable, Serializable]
- public sealed class WelderComponentState : ComponentState
- {
- public float FuelCapacity { get; }
- public float Fuel { get; }
-
- public WelderComponentState(float fuelCapacity, float fuel)
- {
- FuelCapacity = fuelCapacity;
- Fuel = fuel;
- }
- }
-}
using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Shared.Tools.Components;
/// <summary>
/// Tool quality for welding.
/// </summary>
- [DataField("weldingQuality", customTypeSerializer: typeof(PrototypeIdSerializer<ToolQualityPrototype>))]
- [ViewVariables(VVAccess.ReadWrite)]
- public string WeldingQuality = "Welding";
+ [DataField]
+ public ProtoId<ToolQualityPrototype> WeldingQuality = "Welding";
/// <summary>
/// How much time does it take to weld/unweld entity.
/// </summary>
- [DataField("time")]
- [ViewVariables(VVAccess.ReadWrite)]
- [AutoNetworkedField]
- public TimeSpan WeldingTime = TimeSpan.FromSeconds(1f);
+ [DataField, AutoNetworkedField]
+ public TimeSpan Time = TimeSpan.FromSeconds(1f);
+
+ /// <summary>
+ /// How much fuel does it take to weld/unweld entity.
+ /// </summary>
+ [DataField]
+ public float Fuel = 3f;
/// <summary>
/// Shown when welded entity is examined.
/// </summary>
- [DataField("weldedExamineMessage")]
- [ViewVariables(VVAccess.ReadWrite)]
- public string? WeldedExamineMessage = "weldable-component-examine-is-welded";
+ [DataField]
+ public LocId? WeldedExamineMessage = "weldable-component-examine-is-welded";
/// <summary>
/// Is this entity currently welded shut?
/// </summary>
- [DataField("isWelded"), AutoNetworkedField]
+ [DataField, AutoNetworkedField]
public bool IsWelded;
}
--- /dev/null
+using Content.Shared.Chemistry.Components;
+using Content.Shared.Chemistry.Reagent;
+using Content.Shared.FixedPoint;
+using Content.Shared.Tools.Systems;
+using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Tools.Components;
+
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true), Access(typeof(SharedToolSystem))]
+public sealed partial class WelderComponent : Component
+{
+ [DataField, AutoNetworkedField]
+ public bool Enabled;
+
+ [DataField]
+ public float WelderTimer;
+
+ /// <summary>
+ /// Name of <see cref="FuelSolution"/>.
+ /// </summary>
+ [DataField]
+ public string FuelSolutionName = "Welder";
+
+ /// <summary>
+ /// Solution on the entity that contains the fuel.
+ /// </summary>
+ [ViewVariables(VVAccess.ReadWrite)]
+ public Entity<SolutionComponent>? FuelSolution;
+
+ /// <summary>
+ /// Reagent that will be used as fuel for welding.
+ /// </summary>
+ [DataField]
+ public ProtoId<ReagentPrototype> FuelReagent = "WeldingFuel";
+
+ /// <summary>
+ /// Fuel consumption per second while the welder is active.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public FixedPoint2 FuelConsumption = FixedPoint2.New(1.0f);
+
+ /// <summary>
+ /// A fuel amount to be consumed when the welder goes from being unlit to being lit.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public FixedPoint2 FuelLitCost = FixedPoint2.New(0.5f);
+
+ /// <summary>
+ /// Sound played when refilling the welder.
+ /// </summary>
+ [DataField]
+ public SoundSpecifier WelderRefill = new SoundPathSpecifier("/Audio/Effects/refill.ogg");
+
+ /// <summary>
+ /// Whether the item is safe to refill while lit without exploding the tank.
+ /// </summary>
+ [DataField]
+ public bool TankSafe;
+
+ [DataField]
+ public float WelderUpdateTimer = 1f;
+}
--- /dev/null
+using Content.Shared.Chemistry.Components;
+using Content.Shared.Chemistry.Components.SolutionManager;
+using Content.Shared.Database;
+using Content.Shared.DoAfter;
+using Content.Shared.Examine;
+using Content.Shared.FixedPoint;
+using Content.Shared.Interaction;
+using Content.Shared.Item.ItemToggle.Components;
+using Content.Shared.Tools.Components;
+
+namespace Content.Shared.Tools.Systems;
+
+public abstract partial class SharedToolSystem
+{
+ public void InitializeWelder()
+ {
+ SubscribeLocalEvent<WelderComponent, ExaminedEvent>(OnWelderExamine);
+ SubscribeLocalEvent<WelderComponent, AfterInteractEvent>(OnWelderAfterInteract);
+ SubscribeLocalEvent<WelderComponent, DoAfterAttemptEvent<ToolDoAfterEvent>>(OnWelderToolUseAttempt);
+ SubscribeLocalEvent<WelderComponent, ToolDoAfterEvent>(OnWelderDoAfter);
+ SubscribeLocalEvent<WelderComponent, ItemToggledEvent>(OnToggle);
+ SubscribeLocalEvent<WelderComponent, ItemToggleActivateAttemptEvent>(OnActivateAttempt);
+ }
+
+ public virtual void TurnOn(Entity<WelderComponent> entity, EntityUid? user)
+ {
+ if (!SolutionContainerSystem.ResolveSolution(entity.Owner, entity.Comp.FuelSolutionName, ref entity.Comp.FuelSolution))
+ return;
+
+ SolutionContainerSystem.RemoveReagent(entity.Comp.FuelSolution.Value, entity.Comp.FuelReagent, entity.Comp.FuelLitCost);
+ AdminLogger.Add(LogType.InteractActivate, LogImpact.Low,
+ $"{ToPrettyString(user):user} toggled {ToPrettyString(entity.Owner):welder} on");
+
+ entity.Comp.Enabled = true;
+ Dirty(entity, entity.Comp);
+ }
+
+ public void TurnOff(Entity<WelderComponent> entity, EntityUid? user)
+ {
+ AdminLogger.Add(LogType.InteractActivate, LogImpact.Low,
+ $"{ToPrettyString(user):user} toggled {ToPrettyString(entity.Owner):welder} off");
+ entity.Comp.Enabled = false;
+ Dirty(entity, entity.Comp);
+ }
+
+ public (FixedPoint2 fuel, FixedPoint2 capacity) GetWelderFuelAndCapacity(EntityUid uid, WelderComponent? welder = null, SolutionContainerManagerComponent? solutionContainer = null)
+ {
+ if (!Resolve(uid, ref welder, ref solutionContainer)
+ || !SolutionContainerSystem.ResolveSolution((uid, solutionContainer), welder.FuelSolutionName, ref welder.FuelSolution, out var fuelSolution))
+ return (FixedPoint2.Zero, FixedPoint2.Zero);
+
+ return (fuelSolution.GetTotalPrototypeQuantity(welder.FuelReagent), fuelSolution.MaxVolume);
+ }
+
+ private void OnWelderExamine(Entity<WelderComponent> entity, ref ExaminedEvent args)
+ {
+ using (args.PushGroup(nameof(WelderComponent)))
+ {
+ if (ItemToggle.IsActivated(entity.Owner))
+ {
+ args.PushMarkup(Loc.GetString("welder-component-on-examine-welder-lit-message"));
+ }
+ else
+ {
+ args.PushMarkup(Loc.GetString("welder-component-on-examine-welder-not-lit-message"));
+ }
+
+ if (args.IsInDetailsRange)
+ {
+ var (fuel, capacity) = GetWelderFuelAndCapacity(entity.Owner, entity.Comp);
+
+ args.PushMarkup(Loc.GetString("welder-component-on-examine-detailed-message",
+ ("colorName", fuel < capacity / FixedPoint2.New(4f) ? "darkorange" : "orange"),
+ ("fuelLeft", fuel),
+ ("fuelCapacity", capacity),
+ ("status", string.Empty))); // Lit status is handled above
+ }
+ }
+ }
+
+ private void OnWelderAfterInteract(Entity<WelderComponent> entity, ref AfterInteractEvent args)
+ {
+ if (args.Handled)
+ return;
+
+ if (args.Target is not { Valid: true } target || !args.CanReach)
+ return;
+
+ if (TryComp(target, out ReagentTankComponent? tank)
+ && tank.TankType == ReagentTankType.Fuel
+ && SolutionContainerSystem.TryGetDrainableSolution(target, out var targetSoln, out var targetSolution)
+ && SolutionContainerSystem.ResolveSolution(entity.Owner, entity.Comp.FuelSolutionName, ref entity.Comp.FuelSolution, out var welderSolution))
+ {
+ var trans = FixedPoint2.Min(welderSolution.AvailableVolume, targetSolution.Volume);
+ if (trans > 0)
+ {
+ var drained = SolutionContainerSystem.Drain(target, targetSoln.Value, trans);
+ SolutionContainerSystem.TryAddSolution(entity.Comp.FuelSolution.Value, drained);
+ _audioSystem.PlayPredicted(entity.Comp.WelderRefill, entity, user: args.User);
+ _popup.PopupClient(Loc.GetString("welder-component-after-interact-refueled-message"), entity, args.User);
+ }
+ else if (welderSolution.AvailableVolume <= 0)
+ {
+ _popup.PopupClient(Loc.GetString("welder-component-already-full"), entity, args.User);
+ }
+ else
+ {
+ _popup.PopupClient(Loc.GetString("welder-component-no-fuel-in-tank", ("owner", args.Target)), entity, args.User);
+ }
+
+ args.Handled = true;
+ }
+ }
+
+ private void OnWelderToolUseAttempt(Entity<WelderComponent> entity, ref DoAfterAttemptEvent<ToolDoAfterEvent> args)
+ {
+ var user = args.DoAfter.Args.User;
+
+ if (!ItemToggle.IsActivated(entity.Owner))
+ {
+ _popup.PopupClient(Loc.GetString("welder-component-welder-not-lit-message"), entity, user);
+ args.Cancel();
+ return;
+ }
+
+ var (fuel, _) = GetWelderFuelAndCapacity(entity);
+
+ if (args.Event.Fuel > fuel)
+ {
+ _popup.PopupClient(Loc.GetString("welder-component-cannot-weld-message"), entity, user);
+ args.Cancel();
+ }
+ }
+
+ private void OnWelderDoAfter(Entity<WelderComponent> ent, ref ToolDoAfterEvent args)
+ {
+ if (args.Cancelled)
+ return;
+
+ if (!SolutionContainerSystem.TryGetSolution(ent.Owner, ent.Comp.FuelSolutionName, out var solution))
+ return;
+
+ SolutionContainerSystem.RemoveReagent(solution.Value, ent.Comp.FuelReagent, FixedPoint2.New(args.Fuel));
+ }
+
+ private void OnToggle(Entity<WelderComponent> entity, ref ItemToggledEvent args)
+ {
+ if (args.Activated)
+ TurnOn(entity, args.User);
+ else
+ TurnOff(entity, args.User);
+ }
+
+ private void OnActivateAttempt(Entity<WelderComponent> entity, ref ItemToggleActivateAttemptEvent args)
+ {
+ if (!SolutionContainerSystem.ResolveSolution(entity.Owner, entity.Comp.FuelSolutionName, ref entity.Comp.FuelSolution, out var solution))
+ {
+ args.Cancelled = true;
+ args.Popup = Loc.GetString("welder-component-no-fuel-message");
+ return;
+ }
+
+ var fuel = solution.GetTotalPrototypeQuantity(entity.Comp.FuelReagent);
+ if (fuel == FixedPoint2.Zero || fuel < entity.Comp.FuelLitCost)
+ {
+ args.Popup = Loc.GetString("welder-component-no-fuel-message");
+ args.Cancelled = true;
+ }
+ }
+}
using Content.Shared.Administration.Logs;
+using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.DoAfter;
using Content.Shared.Interaction;
+using Content.Shared.Item.ItemToggle;
using Content.Shared.Maps;
+using Content.Shared.Popups;
using Content.Shared.Tools.Components;
+using JetBrains.Annotations;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
{
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IPrototypeManager _protoMan = default!;
- [Dependency] protected readonly ISharedAdminLogManager AdminLogger = default!;
+ [Dependency] protected readonly ISharedAdminLogManager AdminLogger = default!;
[Dependency] private readonly ITileDefinitionManager _tileDefManager = default!;
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] protected readonly SharedInteractionSystem InteractionSystem = default!;
+ [Dependency] protected readonly SharedItemToggleSystem ItemToggle = default!;
[Dependency] private readonly SharedMapSystem _maps = default!;
+ [Dependency] private readonly SharedPopupSystem _popup = default!;
+ [Dependency] protected readonly SharedSolutionContainerSystem SolutionContainerSystem = default!;
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
[Dependency] private readonly TileSystem _tiles = default!;
[Dependency] private readonly TurfSystem _turfs = default!;
{
InitializeMultipleTool();
InitializeTile();
+ InitializeWelder();
SubscribeLocalEvent<ToolComponent, ToolDoAfterEvent>(OnDoAfter);
}
/// <param name="toolQualitiesNeeded">The qualities needed for this tool to work.</param>
/// <param name="doAfterEv">The event that will be raised when the tool has finished (including cancellation). Event
/// will be directed at the tool target.</param>
+ /// <param name="fuel">Amount of fuel that should be taken from the tool.</param>
/// <param name="toolComponent">The tool component.</param>
/// <returns>Returns true if any interaction takes place.</returns>
public bool UseTool(
float doAfterDelay,
IEnumerable<string> toolQualitiesNeeded,
DoAfterEvent doAfterEv,
+ float fuel = 0,
ToolComponent? toolComponent = null)
{
return UseTool(tool,
toolQualitiesNeeded,
doAfterEv,
out _,
+ fuel,
toolComponent);
}
/// will be directed at the tool target.</param>
/// <param name="id">The id of the DoAfter that was created. This may be null even if the function returns true in
/// the event that this tool-use cancelled an existing DoAfter</param>
+ /// <param name="fuel">Amount of fuel that should be taken from the tool.</param>
/// <param name="toolComponent">The tool component.</param>
/// <returns>Returns true if any interaction takes place.</returns>
public bool UseTool(
IEnumerable<string> toolQualitiesNeeded,
DoAfterEvent doAfterEv,
out DoAfterId? id,
+ float fuel = 0,
ToolComponent? toolComponent = null)
{
id = null;
if (!Resolve(tool, ref toolComponent, false))
return false;
- if (!CanStartToolUse(tool, user, target, toolQualitiesNeeded, toolComponent))
+ if (!CanStartToolUse(tool, user, target, fuel, toolQualitiesNeeded, toolComponent))
return false;
- var toolEvent = new ToolDoAfterEvent(doAfterEv, GetNetEntity(target));
+ var toolEvent = new ToolDoAfterEvent(fuel, doAfterEv, GetNetEntity(target));
var doAfterArgs = new DoAfterArgs(EntityManager, user, delay / toolComponent.SpeedModifier, toolEvent, tool, target: target, used: tool)
{
BreakOnDamage = true,
BreakOnMove = true,
BreakOnWeightlessMove = false,
NeedHand = tool != user,
- AttemptFrequency = IsWelder(tool) ? AttemptFrequency.EveryTick : AttemptFrequency.Never
+ AttemptFrequency = fuel > 0 ? AttemptFrequency.EveryTick : AttemptFrequency.Never
};
_doAfterSystem.TryStartDoAfter(doAfterArgs, out id);
return true;
}
- protected abstract bool IsWelder(EntityUid uid);
-
/// <summary>
/// Attempts to use a tool on some entity, which will start a DoAfter. Returns true if an interaction occurred.
/// Note that this does not mean the interaction was successful, you need to listen for the DoAfter event.
/// <param name="toolQualityNeeded">The quality needed for this tool to work.</param>
/// <param name="doAfterEv">The event that will be raised when the tool has finished (including cancellation). Event
/// will be directed at the tool target.</param>
+ /// <param name="fuel">Amount of fuel that should be taken from the tool.</param>
/// <param name="toolComponent">The tool component.</param>
/// <returns>Returns true if any interaction takes place.</returns>
public bool UseTool(
float doAfterDelay,
string toolQualityNeeded,
DoAfterEvent doAfterEv,
+ float fuel = 0,
ToolComponent? toolComponent = null)
{
return UseTool(tool,
new[] { toolQualityNeeded },
doAfterEv,
out _,
+ fuel,
toolComponent);
}
/// <summary>
/// Whether a tool entity has all specified qualities or not.
/// </summary>
+ [PublicAPI]
public bool HasAllQualities(EntityUid uid, IEnumerable<string> qualities, ToolComponent? tool = null)
{
return Resolve(uid, ref tool, false) && tool.Qualities.ContainsAll(qualities);
}
- private bool CanStartToolUse(EntityUid tool, EntityUid user, EntityUid? target, IEnumerable<string> toolQualitiesNeeded, ToolComponent? toolComponent = null)
+ private bool CanStartToolUse(EntityUid tool, EntityUid user, EntityUid? target, float fuel, IEnumerable<string> toolQualitiesNeeded, ToolComponent? toolComponent = null)
{
if (!Resolve(tool, ref toolComponent))
return false;
[Serializable, NetSerializable]
protected sealed partial class ToolDoAfterEvent : DoAfterEvent
{
+ [DataField]
+ public float Fuel;
+
/// <summary>
/// Entity that the wrapped do after event will get directed at. If null, event will be broadcast.
/// </summary>
{
}
- public ToolDoAfterEvent(DoAfterEvent wrappedEvent, NetEntity? originalTarget)
+ public ToolDoAfterEvent(float fuel, DoAfterEvent wrappedEvent, NetEntity? originalTarget)
{
DebugTools.Assert(wrappedEvent.GetType().HasCustomAttribute<NetSerializableAttribute>(), "Tool event is not serializable");
+ Fuel = fuel;
WrappedEvent = wrappedEvent;
OriginalTarget = originalTarget;
}
if (evClone == WrappedEvent)
return this;
- return new ToolDoAfterEvent(evClone, OriginalTarget);
+ return new ToolDoAfterEvent(Fuel, evClone, OriginalTarget);
}
}
[Serializable, NetSerializable]
protected sealed partial class LatticeCuttingCompleteEvent : DoAfterEvent
{
- [DataField("coordinates", required:true)]
+ [DataField(required:true)]
public NetCoordinates Coordinates;
private LatticeCuttingCompleteEvent()
}
[Serializable, NetSerializable]
-public sealed partial class CableCuttingFinishedEvent : SimpleDoAfterEvent
-{
-}
+public sealed partial class CableCuttingFinishedEvent : SimpleDoAfterEvent;
#endregion
if (!CanWeld(uid, tool, user, component))
return false;
- if (!_toolSystem.UseTool(tool, user, uid, component.WeldingTime.Seconds, component.WeldingQuality, new WeldFinishedEvent()))
+ if (!_toolSystem.UseTool(tool, user, uid, component.Time.Seconds, component.WeldingQuality, new WeldFinishedEvent(), component.Fuel))
return false;
// Log attempt
if (!_query.Resolve(uid, ref component))
return;
- if (component.WeldingTime.Equals(time))
+ if (component.Time.Equals(time))
return;
- component.WeldingTime = time;
+ component.Time = time;
Dirty(uid, component);
}
}
group: GenericNumber
- type: Repairable
doAfterDelay: 8
+ fuelCost: 15
- type: Pullable
- type: Tag
tags:
sprite: Objects/Tools/Cowtools/cowelder.rsi
- type: Tool
speed: 0.05
- - type: Welder
- litMeleeDamageBonus:
- types: # When lit, negate standard melee damage and replace with heat
- Heat: 0.5
- Blunt: -5
- type: entity
name: milkalyzer
containers:
board: !type:Container
- type: Weldable
+ fuel: 5
time: 3
- type: Airlock
- type: NavMapDoor
- type: RCDDeconstructable
cost: 6
delay: 8
- fx: EffectRCDDeconstruct8
+ fx: EffectRCDDeconstruct8
- type: Destructible
thresholds:
- trigger:
- type: BlockWeather
placement:
mode: SnapgridCenter
-
+
- type: entity
id: AirlockRCDResistant
parent: Airlock
- type: Tag
tags:
- GlassAirlock
- # This tag is used to nagivate the Airlock construction graph. It's needed because the construction graph is shared between Airlock, AirlockGlass, and HighSecDoor
\ No newline at end of file
+ # This tag is used to nagivate the Airlock construction graph. It's needed because the construction graph is shared between Airlock, AirlockGlass, and HighSecDoor
denySound:
path: /Audio/Machines/airlock_deny.ogg
- type: Weldable
+ fuel: 10
time: 10
- type: Airlock
- type: NavMapDoor
openingAnimationTime: 0.6
closingAnimationTime: 0.6
- type: Weldable
+ fuel: 5
time: 3
- type: Firelock
- type: Appearance