* A big hecking chemistry-related refactor. Changed SolutionContainerCaps. It now describes "stock" behavior for interacting with solutions that is pre-implemented by SolutionContainerComponent. As such things like syringes do not check it anymore (on themselves) to see "can we remove reagent from ourselves". That's assumed by it... being a syringe. SolutionContainerCaps now has different flags more accurately describing possible reagent interaction behaviors. ISolutionInteractionsComponent is the interface that describes the common behaviors like "what happens when injected with a syringe". This is implemented by SolutionContainerComponent but could be implemented by other classes. One notable example that drove me to making this interface was the /vg/station circuit imprinter which splits reagent poured in into its two reservoir beakers. Having this interface allows us to do this "proxying" behavior hack-free. (the hacks in /vg/ code were somewhat dirty...). PourableComponent has been replaced SolutionTransferComponent. It now describes both give-and-take behavior for the common reagent containers. This is in line with /vg/'s /obj/item/weapon/reagent_containers architecture. "Taking" in this context is ONLY from reagent tanks like fuel tanks. Oh, should I mention that fuel tanks and such have a proper component now? They do. Because of this behavioral change, reagent tanks DO NOT have Pourable anymore. Removing from reagent tanks is now in the hands of the item used on them. Welders and fire extinguishers now have code for removing from them. This sounds bad at first but remember that all have quite unique behavior related to this: Welders cause explosions if lit and can ONLY be fueled at fuel tanks. Extinguishers can be filled at any tank, etc... The code for this is also simpler due to ISolutionInteractionsComponent now so... IAfterInteract now works like IInteractUsing with the Priority levels and "return true to block further handlers" behavior. This was necessary to make extinguishers prioritize taking from tanks over spraying. Explicitly coded interactions like welders refueling also means they refuse instantly to full now, which they didn't before. And it plays the sound. Etc... Probably more stuff I'm forgetting. * Review improvements.
335 lines
11 KiB
C#
335 lines
11 KiB
C#
#nullable enable
|
|
using System;
|
|
using System.Threading.Tasks;
|
|
using Content.Server.Atmos;
|
|
using Content.Server.Explosions;
|
|
using Content.Server.GameObjects.Components.Chemistry;
|
|
using Content.Server.GameObjects.Components.Items.Storage;
|
|
using Content.Server.GameObjects.EntitySystems;
|
|
using Content.Server.Interfaces.Chat;
|
|
using Content.Server.Interfaces.GameObjects;
|
|
using Content.Server.Utility;
|
|
using Content.Shared.Chemistry;
|
|
using Content.Shared.GameObjects;
|
|
using Content.Shared.GameObjects.Components.Chemistry;
|
|
using Content.Shared.GameObjects.Components.Interactable;
|
|
using Content.Shared.GameObjects.EntitySystems;
|
|
using Content.Shared.Interfaces;
|
|
using Content.Shared.Interfaces.GameObjects.Components;
|
|
using Robust.Server.GameObjects;
|
|
using Robust.Server.GameObjects.EntitySystems;
|
|
using Robust.Shared.GameObjects;
|
|
using Robust.Shared.GameObjects.Systems;
|
|
using Robust.Shared.Interfaces.GameObjects;
|
|
using Robust.Shared.IoC;
|
|
using Robust.Shared.Localization;
|
|
using Robust.Shared.Serialization;
|
|
using Robust.Shared.Utility;
|
|
using Robust.Shared.ViewVariables;
|
|
|
|
namespace Content.Server.GameObjects.Components.Interactable
|
|
{
|
|
[RegisterComponent]
|
|
[ComponentReference(typeof(ToolComponent))]
|
|
[ComponentReference(typeof(IToolComponent))]
|
|
[ComponentReference(typeof(IHotItem))]
|
|
public class WelderComponent : ToolComponent, IExamine, IUse, ISuicideAct, ISolutionChange, IHotItem, IAfterInteract
|
|
{
|
|
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
|
|
|
public override string Name => "Welder";
|
|
public override uint? NetID => ContentNetIDs.WELDER;
|
|
|
|
/// <summary>
|
|
/// Default Cost of using the welder fuel for an action
|
|
/// </summary>
|
|
public const float DefaultFuelCost = 10;
|
|
|
|
/// <summary>
|
|
/// Rate at which we expunge fuel from ourselves when activated
|
|
/// </summary>
|
|
public const float FuelLossRate = 0.5f;
|
|
|
|
private bool _welderLit;
|
|
private WelderSystem _welderSystem = default!;
|
|
private SpriteComponent? _spriteComponent;
|
|
private SolutionContainerComponent? _solutionComponent;
|
|
private PointLightComponent? _pointLightComponent;
|
|
|
|
public string? WeldSoundCollection { get; set; }
|
|
|
|
[ViewVariables]
|
|
public float Fuel => _solutionComponent?.Solution?.GetReagentQuantity("chem.WeldingFuel").Float() ?? 0f;
|
|
|
|
[ViewVariables]
|
|
public float FuelCapacity => _solutionComponent?.MaxVolume.Float() ?? 0f;
|
|
|
|
/// <summary>
|
|
/// Status of welder, whether it is ignited
|
|
/// </summary>
|
|
[ViewVariables]
|
|
public bool WelderLit
|
|
{
|
|
get => _welderLit;
|
|
private set
|
|
{
|
|
_welderLit = value;
|
|
Dirty();
|
|
}
|
|
}
|
|
|
|
bool IHotItem.IsCurrentlyHot()
|
|
{
|
|
return WelderLit;
|
|
}
|
|
|
|
public override void Initialize()
|
|
{
|
|
base.Initialize();
|
|
|
|
AddQuality(ToolQuality.Welding);
|
|
|
|
_welderSystem = _entitySystemManager.GetEntitySystem<WelderSystem>();
|
|
|
|
Owner.TryGetComponent(out _solutionComponent);
|
|
Owner.TryGetComponent(out _spriteComponent);
|
|
Owner.TryGetComponent(out _pointLightComponent);
|
|
}
|
|
|
|
public override void ExposeData(ObjectSerializer serializer)
|
|
{
|
|
serializer.DataField(this, collection => WeldSoundCollection, "weldSoundCollection", string.Empty);
|
|
}
|
|
|
|
public override ComponentState GetComponentState()
|
|
{
|
|
return new WelderComponentState(FuelCapacity, Fuel, WelderLit);
|
|
}
|
|
|
|
public override async Task<bool> UseTool(IEntity user, IEntity target, float doAfterDelay, ToolQuality toolQualityNeeded, Func<bool>? doAfterCheck = null)
|
|
{
|
|
bool ExtraCheck()
|
|
{
|
|
var extraCheck = doAfterCheck?.Invoke() ?? true;
|
|
|
|
if (!CanWeld(DefaultFuelCost))
|
|
{
|
|
target.PopupMessage(user, "Can't weld!");
|
|
|
|
return false;
|
|
}
|
|
|
|
return extraCheck;
|
|
}
|
|
|
|
var canUse = await base.UseTool(user, target, doAfterDelay, toolQualityNeeded, ExtraCheck);
|
|
|
|
return toolQualityNeeded.HasFlag(ToolQuality.Welding) ? canUse && TryWeld(DefaultFuelCost, user) : canUse;
|
|
}
|
|
|
|
public async Task<bool> UseTool(IEntity user, IEntity target, float doAfterDelay, ToolQuality toolQualityNeeded, float fuelConsumed, Func<bool>? doAfterCheck = null)
|
|
{
|
|
bool ExtraCheck()
|
|
{
|
|
var extraCheck = doAfterCheck?.Invoke() ?? true;
|
|
|
|
return extraCheck && CanWeld(fuelConsumed);
|
|
}
|
|
|
|
return await base.UseTool(user, target, doAfterDelay, toolQualityNeeded, ExtraCheck) && TryWeld(fuelConsumed, user);
|
|
}
|
|
|
|
private bool TryWeld(float value, IEntity? user = null, bool silent = false)
|
|
{
|
|
if (!WelderLit)
|
|
{
|
|
if(!silent) Owner.PopupMessage(user, Loc.GetString("The welder is turned off!"));
|
|
return false;
|
|
}
|
|
|
|
if (!CanWeld(value))
|
|
{
|
|
if(!silent) Owner.PopupMessage(user, Loc.GetString("The welder does not have enough fuel for that!"));
|
|
return false;
|
|
}
|
|
|
|
if (_solutionComponent == null)
|
|
return false;
|
|
|
|
bool succeeded = _solutionComponent.TryRemoveReagent("chem.WeldingFuel", ReagentUnit.New(value));
|
|
|
|
if (succeeded && !silent)
|
|
{
|
|
PlaySoundCollection(WeldSoundCollection);
|
|
}
|
|
return succeeded;
|
|
}
|
|
|
|
private bool CanWeld(float value)
|
|
{
|
|
return Fuel > value || Qualities != ToolQuality.Welding;
|
|
}
|
|
|
|
private bool CanLitWelder()
|
|
{
|
|
return Fuel > 0 || Qualities != ToolQuality.Welding;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deactivates welding tool if active, activates welding tool if possible
|
|
/// </summary>
|
|
private bool ToggleWelderStatus(IEntity? user = null)
|
|
{
|
|
var item = Owner.GetComponent<ItemComponent>();
|
|
|
|
if (WelderLit)
|
|
{
|
|
WelderLit = false;
|
|
// Layer 1 is the flame.
|
|
item.EquippedPrefix = "off";
|
|
_spriteComponent?.LayerSetVisible(1, false);
|
|
|
|
if (_pointLightComponent != null) _pointLightComponent.Enabled = false;
|
|
|
|
PlaySoundCollection("WelderOff", -5);
|
|
_welderSystem.Unsubscribe(this);
|
|
return true;
|
|
}
|
|
|
|
if (!CanLitWelder())
|
|
{
|
|
Owner.PopupMessage(user, Loc.GetString("The welder has no fuel left!"));
|
|
return false;
|
|
}
|
|
|
|
WelderLit = true;
|
|
item.EquippedPrefix = "on";
|
|
_spriteComponent?.LayerSetVisible(1, true);
|
|
|
|
if (_pointLightComponent != null) _pointLightComponent.Enabled = true;
|
|
|
|
PlaySoundCollection("WelderOn", -5);
|
|
_welderSystem.Subscribe(this);
|
|
|
|
Owner.Transform.Coordinates
|
|
.GetTileAtmosphere()?.HotspotExpose(700f, 50f, true);
|
|
|
|
return true;
|
|
}
|
|
|
|
public bool UseEntity(UseEntityEventArgs eventArgs)
|
|
{
|
|
return ToggleWelderStatus(eventArgs.User);
|
|
}
|
|
|
|
public void Examine(FormattedMessage message, bool inDetailsRange)
|
|
{
|
|
if (WelderLit)
|
|
{
|
|
message.AddMarkup(Loc.GetString("[color=orange]Lit[/color]\n"));
|
|
}
|
|
else
|
|
{
|
|
message.AddText(Loc.GetString("Not lit\n"));
|
|
}
|
|
|
|
if (inDetailsRange)
|
|
{
|
|
message.AddMarkup(Loc.GetString("Fuel: [color={0}]{1}/{2}[/color].",
|
|
Fuel < FuelCapacity / 4f ? "darkorange" : "orange", Math.Round(Fuel), FuelCapacity));
|
|
}
|
|
}
|
|
|
|
protected override void Shutdown()
|
|
{
|
|
base.Shutdown();
|
|
_welderSystem.Unsubscribe(this);
|
|
}
|
|
|
|
public void OnUpdate(float frameTime)
|
|
{
|
|
if (!HasQuality(ToolQuality.Welding) || !WelderLit || Owner.Deleted)
|
|
return;
|
|
|
|
_solutionComponent?.TryRemoveReagent("chem.WeldingFuel", ReagentUnit.New(FuelLossRate * frameTime));
|
|
|
|
Owner.Transform.Coordinates
|
|
.GetTileAtmosphere()?.HotspotExpose(700f, 50f, true);
|
|
|
|
if (Fuel == 0)
|
|
ToggleWelderStatus();
|
|
|
|
}
|
|
|
|
public SuicideKind Suicide(IEntity victim, IChatManager chat)
|
|
{
|
|
string othersMessage;
|
|
string selfMessage;
|
|
|
|
if (TryWeld(5, victim, silent: true))
|
|
{
|
|
PlaySoundCollection(WeldSoundCollection);
|
|
|
|
othersMessage =
|
|
Loc.GetString(
|
|
"{0:theName} welds {0:their} every orifice closed! It looks like {0:theyre} trying to commit suicide!",
|
|
victim);
|
|
victim.PopupMessageOtherClients(othersMessage);
|
|
|
|
selfMessage = Loc.GetString("You weld your every orifice closed!");
|
|
victim.PopupMessage(selfMessage);
|
|
|
|
return SuicideKind.Heat;
|
|
}
|
|
|
|
othersMessage = Loc.GetString("{0:theName} bashes themselves with the unlit welding torch!", victim);
|
|
victim.PopupMessageOtherClients(othersMessage);
|
|
|
|
selfMessage = Loc.GetString("You bash yourself with the unlit welding torch!");
|
|
victim.PopupMessage(selfMessage);
|
|
|
|
return SuicideKind.Blunt;
|
|
}
|
|
|
|
public void SolutionChanged(SolutionChangeEventArgs eventArgs)
|
|
{
|
|
Dirty();
|
|
}
|
|
|
|
|
|
async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
|
{
|
|
if (eventArgs.Target == null || !eventArgs.CanReach)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (eventArgs.Target.TryGetComponent(out ReagentTankComponent? tank)
|
|
&& tank.TankType == ReagentTankType.Fuel
|
|
&& eventArgs.Target.TryGetComponent(out ISolutionInteractionsComponent? targetSolution)
|
|
&& targetSolution.CanDrain
|
|
&& _solutionComponent != null)
|
|
{
|
|
if (WelderLit)
|
|
{
|
|
// Oh no no
|
|
eventArgs.Target.SpawnExplosion();
|
|
return true;
|
|
}
|
|
|
|
var trans = ReagentUnit.Min(_solutionComponent.EmptyVolume, targetSolution.DrainAvailable);
|
|
if (trans > 0)
|
|
{
|
|
var drained = targetSolution.Drain(trans);
|
|
_solutionComponent.TryAddSolution(drained);
|
|
|
|
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Effects/refill.ogg", Owner);
|
|
eventArgs.Target.PopupMessage(eventArgs.User, Loc.GetString("Welder refueled"));
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|