using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Content.Server.Atmos.EntitySystems;
using Content.Server.DoAfter;
using Content.Server.Popups;
using Content.Server.Tools.Components;
using Content.Shared.ActionBlocker;
using Content.Shared.Audio;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Tools;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
namespace Content.Server.Tools
{
public partial class ToolSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
public override void Initialize()
{
base.Initialize();
InitializeWelders();
InitializeMultipleTools();
}
///
/// Whether a tool entity has the specified quality or not.
///
public bool HasQuality(EntityUid uid, string quality, ToolComponent? tool = null)
{
return Resolve(uid, ref tool, false) && tool.Qualities.Contains(quality);
}
///
/// Whether a tool entity has all specified qualities or not.
///
public bool HasAllQualities(EntityUid uid, IEnumerable qualities, ToolComponent? tool = null)
{
return Resolve(uid, ref tool, false) && tool.Qualities.ContainsAll(qualities);
}
///
/// Sync version of UseTool.
///
/// The tool entity.
/// The entity using the tool.
/// Optionally, a target to use the tool on.
/// An optional amount of fuel or energy to consume-
/// A doAfter delay in seconds.
/// The tool qualities needed to use the tool.
/// An event to broadcast once the doAfter is completed successfully.
/// An event to broadcast once the doAfter is cancelled.
/// An optional check to perform for the doAfter.
/// The tool component.
/// Whether initially, using the tool succeeded. If there's a doAfter delay, you'll need to listen to
/// the and being broadcast
/// to see whether using the tool succeeded or not. If the is zero,
/// this simply returns whether using the tool succeeded or not.
public bool UseTool(EntityUid tool, EntityUid user, EntityUid? target, float fuel,
float doAfterDelay, IEnumerable toolQualitiesNeeded, object doAfterCompleteEvent, object doAfterCancelledEvent,
Func? doAfterCheck = null, ToolComponent? toolComponent = null)
{
// No logging here, after all that'd mean the caller would need to check if the component is there or not.
if (!Resolve(tool, ref toolComponent, false))
return false;
if (!ToolStartUse(tool, user, fuel, toolQualitiesNeeded, toolComponent))
return false;
if (doAfterDelay > 0f)
{
var doAfterArgs = new DoAfterEventArgs(user, doAfterDelay / toolComponent.SpeedModifier, default, target)
{
ExtraCheck = doAfterCheck,
BreakOnDamage = true,
BreakOnStun = true,
BreakOnTargetMove = true,
BreakOnUserMove = true,
NeedHand = true,
BroadcastFinishedEvent = doAfterCompleteEvent,
BroadcastCancelledEvent = doAfterCancelledEvent,
};
_doAfterSystem.DoAfter(doAfterArgs);
return true;
}
return ToolFinishUse(tool, user, fuel, toolComponent);
}
// This is hilariously long.
///
public bool UseTool(EntityUid tool, EntityUid user, EntityUid? target, float fuel,
float doAfterDelay, string toolQualityNeeded, object doAfterCompleteEvent, object doAfterCancelledEvent,
Func? doAfterCheck = null, ToolComponent? toolComponent = null)
{
return UseTool(tool, user, target, fuel, doAfterDelay, new[] { toolQualityNeeded },
doAfterCompleteEvent, doAfterCancelledEvent, doAfterCheck, toolComponent);
}
///
/// Async version of UseTool.
///
/// The tool entity.
/// The entity using the tool.
/// Optionally, a target to use the tool on.
/// An optional amount of fuel or energy to consume-
/// A doAfter delay in seconds.
/// The tool qualities needed to use the tool.
/// An optional check to perform for the doAfter.
/// The tool component.
/// Whether using the tool succeeded or not.
public async Task UseTool(EntityUid tool, EntityUid user, EntityUid? target, float fuel,
float doAfterDelay, IEnumerable toolQualitiesNeeded, Func? doAfterCheck = null,
ToolComponent? toolComponent = null)
{
// No logging here, after all that'd mean the caller would need to check if the component is there or not.
if (!Resolve(tool, ref toolComponent, false))
return false;
if (!ToolStartUse(tool, user, fuel, toolQualitiesNeeded, toolComponent))
return false;
if (doAfterDelay > 0f)
{
var doAfterArgs = new DoAfterEventArgs(user, doAfterDelay / toolComponent.SpeedModifier, default, target)
{
ExtraCheck = doAfterCheck,
BreakOnDamage = true,
BreakOnStun = true,
BreakOnTargetMove = true,
BreakOnUserMove = true,
NeedHand = true,
};
var result = await _doAfterSystem.WaitDoAfter(doAfterArgs);
if (result == DoAfterStatus.Cancelled)
return false;
}
return ToolFinishUse(tool, user, fuel, toolComponent);
}
// This is hilariously long.
///
public Task UseTool(EntityUid tool, EntityUid user, EntityUid? target, float fuel,
float doAfterDelay, string toolQualityNeeded, Func? doAfterCheck = null,
ToolComponent? toolComponent = null)
{
return UseTool(tool, user, target, fuel, doAfterDelay, new [] {toolQualityNeeded}, doAfterCheck, toolComponent);
}
private bool ToolStartUse(EntityUid tool, EntityUid user, float fuel, IEnumerable toolQualitiesNeeded, ToolComponent? toolComponent = null)
{
if (!Resolve(tool, ref toolComponent))
return false;
if (!toolComponent.Qualities.ContainsAll(toolQualitiesNeeded) || !_actionBlockerSystem.CanInteract(user))
return false;
var beforeAttempt = new ToolUseAttemptEvent(fuel, user);
RaiseLocalEvent(tool, beforeAttempt, false);
return !beforeAttempt.Cancelled;
}
private bool ToolFinishUse(EntityUid tool, EntityUid user, float fuel, ToolComponent? toolComponent = null)
{
if (!Resolve(tool, ref toolComponent))
return false;
var afterAttempt = new ToolUseFinishAttemptEvent(fuel, user);
RaiseLocalEvent(tool, afterAttempt, false);
if (afterAttempt.Cancelled)
return false;
if (toolComponent.UseSound != null)
PlayToolSound(tool, toolComponent);
return true;
}
public void PlayToolSound(EntityUid uid, ToolComponent? tool = null)
{
if (!Resolve(uid, ref tool))
return;
if (tool.UseSound is not {} sound)
return;
// Pass tool.Owner to Filter.Pvs to avoid a TryGetEntity call.
SoundSystem.Play(Filter.Pvs(tool.Owner), sound.GetSound(), uid,
AudioHelpers.WithVariation(0.175f).WithVolume(-5f));
}
public override void Update(float frameTime)
{
base.Update(frameTime);
UpdateWelders(frameTime);
}
}
///
/// Attempt event called *before* any do afters to see if the tool usage should succeed or not.
/// You can change the fuel consumption by changing the Fuel property.
///
public class ToolUseAttemptEvent : CancellableEntityEventArgs
{
public float Fuel { get; set; }
public EntityUid User { get; }
public ToolUseAttemptEvent(float fuel, EntityUid user)
{
Fuel = fuel;
User = user;
}
}
///
/// Attempt event called *after* any do afters to see if the tool usage should succeed or not.
/// You can use this event to consume any fuel needed.
///
public class ToolUseFinishAttemptEvent : CancellableEntityEventArgs
{
public float Fuel { get; }
public EntityUid User { get; }
public ToolUseFinishAttemptEvent(float fuel, EntityUid user)
{
Fuel = fuel;
}
}
}